1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-02 16:42:50 -05:00

Revert filescoped (#2227)

* Revert "Add git blame entry (#2226)"

This reverts commit 239286737d.

* Revert "Turn on file scoped namespaces (#2225)"

This reverts commit 34fb4cca2a.
This commit is contained in:
Justin Baur
2022-08-29 15:53:48 -04:00
committed by GitHub
parent 239286737d
commit bae03feffe
1208 changed files with 74317 additions and 73126 deletions

View File

@ -9,60 +9,61 @@ using Bit.Core.Utilities;
using Bit.SharedWeb.Utilities;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Identity.Controllers;
[Route("accounts")]
[ExceptionHandlerFilter]
public class AccountsController : Controller
namespace Bit.Identity.Controllers
{
private readonly ILogger<AccountsController> _logger;
private readonly IUserRepository _userRepository;
private readonly IUserService _userService;
public AccountsController(
ILogger<AccountsController> logger,
IUserRepository userRepository,
IUserService userService)
[Route("accounts")]
[ExceptionHandlerFilter]
public class AccountsController : Controller
{
_logger = logger;
_userRepository = userRepository;
_userService = userService;
}
private readonly ILogger<AccountsController> _logger;
private readonly IUserRepository _userRepository;
private readonly IUserService _userService;
// Moved from API, If you modify this endpoint, please update API as well.
[HttpPost("register")]
[CaptchaProtected]
public async Task PostRegister([FromBody] RegisterRequestModel model)
{
var result = await _userService.RegisterUserAsync(model.ToUser(), model.MasterPasswordHash,
model.Token, model.OrganizationUserId);
if (result.Succeeded)
public AccountsController(
ILogger<AccountsController> logger,
IUserRepository userRepository,
IUserService userService)
{
return;
_logger = logger;
_userRepository = userRepository;
_userService = userService;
}
foreach (var error in result.Errors.Where(e => e.Code != "DuplicateUserName"))
// Moved from API, If you modify this endpoint, please update API as well.
[HttpPost("register")]
[CaptchaProtected]
public async Task PostRegister([FromBody] RegisterRequestModel model)
{
ModelState.AddModelError(string.Empty, error.Description);
}
await Task.Delay(2000);
throw new BadRequestException(ModelState);
}
// Moved from API, If you modify this endpoint, please update API as well.
[HttpPost("prelogin")]
public async Task<PreloginResponseModel> PostPrelogin([FromBody] PreloginRequestModel model)
{
var kdfInformation = await _userRepository.GetKdfInformationByEmailAsync(model.Email);
if (kdfInformation == null)
{
kdfInformation = new UserKdfInformation
var result = await _userService.RegisterUserAsync(model.ToUser(), model.MasterPasswordHash,
model.Token, model.OrganizationUserId);
if (result.Succeeded)
{
Kdf = KdfType.PBKDF2_SHA256,
KdfIterations = 100000,
};
return;
}
foreach (var error in result.Errors.Where(e => e.Code != "DuplicateUserName"))
{
ModelState.AddModelError(string.Empty, error.Description);
}
await Task.Delay(2000);
throw new BadRequestException(ModelState);
}
// Moved from API, If you modify this endpoint, please update API as well.
[HttpPost("prelogin")]
public async Task<PreloginResponseModel> PostPrelogin([FromBody] PreloginRequestModel model)
{
var kdfInformation = await _userRepository.GetKdfInformationByEmailAsync(model.Email);
if (kdfInformation == null)
{
kdfInformation = new UserKdfInformation
{
Kdf = KdfType.PBKDF2_SHA256,
KdfIterations = 100000,
};
}
return new PreloginResponseModel(kdfInformation);
}
return new PreloginResponseModel(kdfInformation);
}
}

View File

@ -1,20 +1,21 @@
using Bit.Core.Utilities;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Identity.Controllers;
public class InfoController : Controller
namespace Bit.Identity.Controllers
{
[HttpGet("~/alive")]
[HttpGet("~/now")]
public DateTime GetAlive()
public class InfoController : Controller
{
return DateTime.UtcNow;
}
[HttpGet("~/alive")]
[HttpGet("~/now")]
public DateTime GetAlive()
{
return DateTime.UtcNow;
}
[HttpGet("~/version")]
public JsonResult GetVersion()
{
return Json(CoreHelpers.GetVersion());
[HttpGet("~/version")]
public JsonResult GetVersion()
{
return Json(CoreHelpers.GetVersion());
}
}
}

View File

@ -11,264 +11,265 @@ using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Localization;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Identity.Controllers;
// TODO: 2022-01-12, Remove account alias
[Route("account/[action]")]
[Route("sso/[action]")]
public class SsoController : Controller
namespace Bit.Identity.Controllers
{
private readonly IIdentityServerInteractionService _interaction;
private readonly ILogger<SsoController> _logger;
private readonly ISsoConfigRepository _ssoConfigRepository;
private readonly IUserRepository _userRepository;
private readonly IHttpClientFactory _clientFactory;
public SsoController(
IIdentityServerInteractionService interaction,
ILogger<SsoController> logger,
ISsoConfigRepository ssoConfigRepository,
IUserRepository userRepository,
IHttpClientFactory clientFactory)
// TODO: 2022-01-12, Remove account alias
[Route("account/[action]")]
[Route("sso/[action]")]
public class SsoController : Controller
{
_interaction = interaction;
_logger = logger;
_ssoConfigRepository = ssoConfigRepository;
_userRepository = userRepository;
_clientFactory = clientFactory;
}
private readonly IIdentityServerInteractionService _interaction;
private readonly ILogger<SsoController> _logger;
private readonly ISsoConfigRepository _ssoConfigRepository;
private readonly IUserRepository _userRepository;
private readonly IHttpClientFactory _clientFactory;
[HttpGet]
public async Task<IActionResult> PreValidate(string domainHint)
{
if (string.IsNullOrWhiteSpace(domainHint))
public SsoController(
IIdentityServerInteractionService interaction,
ILogger<SsoController> logger,
ISsoConfigRepository ssoConfigRepository,
IUserRepository userRepository,
IHttpClientFactory clientFactory)
{
Response.StatusCode = 400;
return Json(new ErrorResponseModel("No domain hint was provided"));
_interaction = interaction;
_logger = logger;
_ssoConfigRepository = ssoConfigRepository;
_userRepository = userRepository;
_clientFactory = clientFactory;
}
try
{
// Calls Sso Pre-Validate, assumes baseUri set
var requestCultureFeature = Request.HttpContext.Features.Get<IRequestCultureFeature>();
var culture = requestCultureFeature.RequestCulture.Culture.Name;
var requestPath = $"/Account/PreValidate?domainHint={domainHint}&culture={culture}";
var httpClient = _clientFactory.CreateClient("InternalSso");
// Forward the internal SSO result
using var responseMessage = await httpClient.GetAsync(requestPath);
var responseJson = await responseMessage.Content.ReadAsStringAsync();
Response.StatusCode = (int)responseMessage.StatusCode;
return Content(responseJson, "application/json");
}
catch (Exception ex)
[HttpGet]
public async Task<IActionResult> PreValidate(string domainHint)
{
_logger.LogError(ex, "Error pre-validating against SSO service");
Response.StatusCode = 500;
return Json(new ErrorResponseModel("Error pre-validating SSO authentication")
if (string.IsNullOrWhiteSpace(domainHint))
{
ExceptionMessage = ex.Message,
ExceptionStackTrace = ex.StackTrace,
InnerExceptionMessage = ex.InnerException?.Message,
Response.StatusCode = 400;
return Json(new ErrorResponseModel("No domain hint was provided"));
}
try
{
// Calls Sso Pre-Validate, assumes baseUri set
var requestCultureFeature = Request.HttpContext.Features.Get<IRequestCultureFeature>();
var culture = requestCultureFeature.RequestCulture.Culture.Name;
var requestPath = $"/Account/PreValidate?domainHint={domainHint}&culture={culture}";
var httpClient = _clientFactory.CreateClient("InternalSso");
// Forward the internal SSO result
using var responseMessage = await httpClient.GetAsync(requestPath);
var responseJson = await responseMessage.Content.ReadAsStringAsync();
Response.StatusCode = (int)responseMessage.StatusCode;
return Content(responseJson, "application/json");
}
catch (Exception ex)
{
_logger.LogError(ex, "Error pre-validating against SSO service");
Response.StatusCode = 500;
return Json(new ErrorResponseModel("Error pre-validating SSO authentication")
{
ExceptionMessage = ex.Message,
ExceptionStackTrace = ex.StackTrace,
InnerExceptionMessage = ex.InnerException?.Message,
});
}
}
[HttpGet]
public async Task<IActionResult> Login(string returnUrl)
{
var context = await _interaction.GetAuthorizationContextAsync(returnUrl);
var domainHint = context.Parameters.AllKeys.Contains("domain_hint") ?
context.Parameters["domain_hint"] : null;
var ssoToken = context.Parameters[SsoTokenable.TokenIdentifier];
if (string.IsNullOrWhiteSpace(domainHint))
{
throw new Exception("No domain_hint provided");
}
var userIdentifier = context.Parameters.AllKeys.Contains("user_identifier") ?
context.Parameters["user_identifier"] : null;
return RedirectToAction(nameof(ExternalChallenge), new
{
domainHint = domainHint,
returnUrl,
userIdentifier,
ssoToken,
});
}
}
[HttpGet]
public async Task<IActionResult> Login(string returnUrl)
{
var context = await _interaction.GetAuthorizationContextAsync(returnUrl);
var domainHint = context.Parameters.AllKeys.Contains("domain_hint") ?
context.Parameters["domain_hint"] : null;
var ssoToken = context.Parameters[SsoTokenable.TokenIdentifier];
if (string.IsNullOrWhiteSpace(domainHint))
[HttpGet]
public async Task<IActionResult> ExternalChallenge(string domainHint, string returnUrl,
string userIdentifier, string ssoToken)
{
throw new Exception("No domain_hint provided");
}
var userIdentifier = context.Parameters.AllKeys.Contains("user_identifier") ?
context.Parameters["user_identifier"] : null;
return RedirectToAction(nameof(ExternalChallenge), new
{
domainHint = domainHint,
returnUrl,
userIdentifier,
ssoToken,
});
}
[HttpGet]
public async Task<IActionResult> ExternalChallenge(string domainHint, string returnUrl,
string userIdentifier, string ssoToken)
{
if (string.IsNullOrWhiteSpace(domainHint))
{
throw new Exception("Invalid organization reference id.");
}
var ssoConfig = await _ssoConfigRepository.GetByIdentifierAsync(domainHint);
if (ssoConfig == null || !ssoConfig.Enabled)
{
throw new Exception("Organization not found or SSO configuration not enabled");
}
var organizationId = ssoConfig.OrganizationId.ToString();
var scheme = "sso";
var props = new AuthenticationProperties
{
RedirectUri = Url.Action(nameof(ExternalCallback)),
Items =
if (string.IsNullOrWhiteSpace(domainHint))
{
{ "return_url", returnUrl },
{ "domain_hint", domainHint },
{ "organizationId", organizationId },
{ "scheme", scheme },
},
Parameters =
{
{ "ssoToken", ssoToken },
}
};
if (!string.IsNullOrWhiteSpace(userIdentifier))
{
props.Items.Add("user_identifier", userIdentifier);
}
return Challenge(props, scheme);
}
[HttpGet]
public async Task<ActionResult> ExternalCallback()
{
// Read external identity from the temporary cookie
var result = await HttpContext.AuthenticateAsync(
Core.AuthenticationSchemes.BitwardenExternalCookieAuthenticationScheme);
if (result?.Succeeded != true)
{
throw new Exception("External authentication error");
}
// Debugging
var externalClaims = result.Principal.Claims.Select(c => $"{c.Type}: {c.Value}");
_logger.LogDebug("External claims: {@claims}", externalClaims);
var (user, provider, providerUserId, claims) = await FindUserFromExternalProviderAsync(result);
if (user == null)
{
// Should never happen
throw new Exception("Cannot find user.");
}
// This allows us to collect any additional claims or properties
// for the specific protocols used and store them in the local auth cookie.
// this is typically used to store data needed for signout from those protocols.
var additionalLocalClaims = new List<Claim>();
var localSignInProps = new AuthenticationProperties
{
IsPersistent = true,
ExpiresUtc = DateTimeOffset.UtcNow.AddMinutes(1)
};
if (result.Properties != null && result.Properties.Items.TryGetValue("organizationId", out var organization))
{
additionalLocalClaims.Add(new Claim("organizationId", organization));
}
ProcessLoginCallback(result, additionalLocalClaims, localSignInProps);
// Issue authentication cookie for user
await HttpContext.SignInAsync(new IdentityServerUser(user.Id.ToString())
{
DisplayName = user.Email,
IdentityProvider = provider,
AdditionalClaims = additionalLocalClaims.ToArray()
}, localSignInProps);
// Delete temporary cookie used during external authentication
await HttpContext.SignOutAsync(Core.AuthenticationSchemes.BitwardenExternalCookieAuthenticationScheme);
// Retrieve return URL
var returnUrl = result.Properties.Items["return_url"] ?? "~/";
var context = await _interaction.GetAuthorizationContextAsync(returnUrl);
if (context != null)
{
if (IsNativeClient(context))
{
// The client is native, so this change in how to
// return the response is for better UX for the end user.
HttpContext.Response.StatusCode = 200;
HttpContext.Response.Headers["Location"] = string.Empty;
return View("Redirect", new RedirectViewModel { RedirectUrl = returnUrl });
throw new Exception("Invalid organization reference id.");
}
// We can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null
return Redirect(returnUrl);
var ssoConfig = await _ssoConfigRepository.GetByIdentifierAsync(domainHint);
if (ssoConfig == null || !ssoConfig.Enabled)
{
throw new Exception("Organization not found or SSO configuration not enabled");
}
var organizationId = ssoConfig.OrganizationId.ToString();
var scheme = "sso";
var props = new AuthenticationProperties
{
RedirectUri = Url.Action(nameof(ExternalCallback)),
Items =
{
{ "return_url", returnUrl },
{ "domain_hint", domainHint },
{ "organizationId", organizationId },
{ "scheme", scheme },
},
Parameters =
{
{ "ssoToken", ssoToken },
}
};
if (!string.IsNullOrWhiteSpace(userIdentifier))
{
props.Items.Add("user_identifier", userIdentifier);
}
return Challenge(props, scheme);
}
// Request for a local page
if (Url.IsLocalUrl(returnUrl))
[HttpGet]
public async Task<ActionResult> ExternalCallback()
{
return Redirect(returnUrl);
// Read external identity from the temporary cookie
var result = await HttpContext.AuthenticateAsync(
Core.AuthenticationSchemes.BitwardenExternalCookieAuthenticationScheme);
if (result?.Succeeded != true)
{
throw new Exception("External authentication error");
}
// Debugging
var externalClaims = result.Principal.Claims.Select(c => $"{c.Type}: {c.Value}");
_logger.LogDebug("External claims: {@claims}", externalClaims);
var (user, provider, providerUserId, claims) = await FindUserFromExternalProviderAsync(result);
if (user == null)
{
// Should never happen
throw new Exception("Cannot find user.");
}
// This allows us to collect any additional claims or properties
// for the specific protocols used and store them in the local auth cookie.
// this is typically used to store data needed for signout from those protocols.
var additionalLocalClaims = new List<Claim>();
var localSignInProps = new AuthenticationProperties
{
IsPersistent = true,
ExpiresUtc = DateTimeOffset.UtcNow.AddMinutes(1)
};
if (result.Properties != null && result.Properties.Items.TryGetValue("organizationId", out var organization))
{
additionalLocalClaims.Add(new Claim("organizationId", organization));
}
ProcessLoginCallback(result, additionalLocalClaims, localSignInProps);
// Issue authentication cookie for user
await HttpContext.SignInAsync(new IdentityServerUser(user.Id.ToString())
{
DisplayName = user.Email,
IdentityProvider = provider,
AdditionalClaims = additionalLocalClaims.ToArray()
}, localSignInProps);
// Delete temporary cookie used during external authentication
await HttpContext.SignOutAsync(Core.AuthenticationSchemes.BitwardenExternalCookieAuthenticationScheme);
// Retrieve return URL
var returnUrl = result.Properties.Items["return_url"] ?? "~/";
var context = await _interaction.GetAuthorizationContextAsync(returnUrl);
if (context != null)
{
if (IsNativeClient(context))
{
// The client is native, so this change in how to
// return the response is for better UX for the end user.
HttpContext.Response.StatusCode = 200;
HttpContext.Response.Headers["Location"] = string.Empty;
return View("Redirect", new RedirectViewModel { RedirectUrl = returnUrl });
}
// We can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null
return Redirect(returnUrl);
}
// Request for a local page
if (Url.IsLocalUrl(returnUrl))
{
return Redirect(returnUrl);
}
else if (string.IsNullOrEmpty(returnUrl))
{
return Redirect("~/");
}
else
{
// User might have clicked on a malicious link - should be logged
throw new Exception("invalid return URL");
}
}
else if (string.IsNullOrEmpty(returnUrl))
private async Task<(User user, string provider, string providerUserId, IEnumerable<Claim> claims)>
FindUserFromExternalProviderAsync(AuthenticateResult result)
{
return Redirect("~/");
var externalUser = result.Principal;
// Try to determine the unique id of the external user (issued by the provider)
// the most common claim type for that are the sub claim and the NameIdentifier
// depending on the external provider, some other claim type might be used
var userIdClaim = externalUser.FindFirst(JwtClaimTypes.Subject) ??
externalUser.FindFirst(ClaimTypes.NameIdentifier) ??
throw new Exception("Unknown userid");
// remove the user id claim so we don't include it as an extra claim if/when we provision the user
var claims = externalUser.Claims.ToList();
claims.Remove(userIdClaim);
var provider = result.Properties.Items["scheme"];
var providerUserId = userIdClaim.Value;
var user = await _userRepository.GetByIdAsync(new Guid(providerUserId));
return (user, provider, providerUserId, claims);
}
else
private void ProcessLoginCallback(AuthenticateResult externalResult, List<Claim> localClaims,
AuthenticationProperties localSignInProps)
{
// User might have clicked on a malicious link - should be logged
throw new Exception("invalid return URL");
// If the external system sent a session id claim, copy it over
// so we can use it for single sign-out
var sid = externalResult.Principal.Claims.FirstOrDefault(x => x.Type == JwtClaimTypes.SessionId);
if (sid != null)
{
localClaims.Add(new Claim(JwtClaimTypes.SessionId, sid.Value));
}
// If the external provider issued an idToken, we'll keep it for signout
var idToken = externalResult.Properties.GetTokenValue("id_token");
if (idToken != null)
{
localSignInProps.StoreTokens(
new[] { new AuthenticationToken { Name = "id_token", Value = idToken } });
}
}
}
private async Task<(User user, string provider, string providerUserId, IEnumerable<Claim> claims)>
FindUserFromExternalProviderAsync(AuthenticateResult result)
{
var externalUser = result.Principal;
// Try to determine the unique id of the external user (issued by the provider)
// the most common claim type for that are the sub claim and the NameIdentifier
// depending on the external provider, some other claim type might be used
var userIdClaim = externalUser.FindFirst(JwtClaimTypes.Subject) ??
externalUser.FindFirst(ClaimTypes.NameIdentifier) ??
throw new Exception("Unknown userid");
// remove the user id claim so we don't include it as an extra claim if/when we provision the user
var claims = externalUser.Claims.ToList();
claims.Remove(userIdClaim);
var provider = result.Properties.Items["scheme"];
var providerUserId = userIdClaim.Value;
var user = await _userRepository.GetByIdAsync(new Guid(providerUserId));
return (user, provider, providerUserId, claims);
}
private void ProcessLoginCallback(AuthenticateResult externalResult, List<Claim> localClaims,
AuthenticationProperties localSignInProps)
{
// If the external system sent a session id claim, copy it over
// so we can use it for single sign-out
var sid = externalResult.Principal.Claims.FirstOrDefault(x => x.Type == JwtClaimTypes.SessionId);
if (sid != null)
private bool IsNativeClient(IdentityServer4.Models.AuthorizationRequest context)
{
localClaims.Add(new Claim(JwtClaimTypes.SessionId, sid.Value));
return !context.RedirectUri.StartsWith("https", StringComparison.Ordinal)
&& !context.RedirectUri.StartsWith("http", StringComparison.Ordinal);
}
// If the external provider issued an idToken, we'll keep it for signout
var idToken = externalResult.Properties.GetTokenValue("id_token");
if (idToken != null)
{
localSignInProps.StoreTokens(
new[] { new AuthenticationToken { Name = "id_token", Value = idToken } });
}
}
private bool IsNativeClient(IdentityServer4.Models.AuthorizationRequest context)
{
return !context.RedirectUri.StartsWith("https", StringComparison.Ordinal)
&& !context.RedirectUri.StartsWith("http", StringComparison.Ordinal);
}
}

View File

@ -1,6 +1,7 @@
namespace Bit.Identity.Models;
public class RedirectViewModel
namespace Bit.Identity.Models
{
public string RedirectUrl { get; set; }
public class RedirectViewModel
{
public string RedirectUrl { get; set; }
}
}

View File

@ -2,43 +2,44 @@
using Bit.Core.Utilities;
using Serilog.Events;
namespace Bit.Identity;
public class Program
namespace Bit.Identity
{
public static void Main(string[] args)
public class Program
{
CreateHostBuilder(args)
.Build()
.Run();
}
public static void Main(string[] args)
{
CreateHostBuilder(args)
.Build()
.Run();
}
public static IHostBuilder CreateHostBuilder(string[] args)
{
return Host
.CreateDefaultBuilder(args)
.ConfigureCustomAppConfiguration(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
webBuilder.ConfigureLogging((hostingContext, logging) =>
logging.AddSerilog(hostingContext, e =>
{
var context = e.Properties["SourceContext"].ToString();
if (context.Contains(typeof(IpRateLimitMiddleware).FullName) &&
e.Level == LogEventLevel.Information)
public static IHostBuilder CreateHostBuilder(string[] args)
{
return Host
.CreateDefaultBuilder(args)
.ConfigureCustomAppConfiguration(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
webBuilder.ConfigureLogging((hostingContext, logging) =>
logging.AddSerilog(hostingContext, e =>
{
return true;
}
var context = e.Properties["SourceContext"].ToString();
if (context.Contains(typeof(IpRateLimitMiddleware).FullName) &&
e.Level == LogEventLevel.Information)
{
return true;
}
if (context.Contains("IdentityServer4.Validation.TokenValidator") ||
context.Contains("IdentityServer4.Validation.TokenRequestValidator"))
{
return e.Level > LogEventLevel.Error;
}
if (context.Contains("IdentityServer4.Validation.TokenValidator") ||
context.Contains("IdentityServer4.Validation.TokenRequestValidator"))
{
return e.Level > LogEventLevel.Error;
}
return e.Level >= LogEventLevel.Error;
}));
});
return e.Level >= LogEventLevel.Error;
}));
});
}
}
}

View File

@ -13,213 +13,214 @@ using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.IdentityModel.Logging;
using Microsoft.OpenApi.Models;
namespace Bit.Identity;
public class Startup
namespace Bit.Identity
{
public Startup(IWebHostEnvironment env, IConfiguration configuration)
public class Startup
{
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)
public Startup(IWebHostEnvironment env, IConfiguration configuration)
{
services.Configure<IpRateLimitOptions>(Configuration.GetSection("IpRateLimitOptions"));
services.Configure<IpRateLimitPolicies>(Configuration.GetSection("IpRateLimitPolicies"));
CultureInfo.DefaultThreadCurrentCulture = new CultureInfo("en-US");
Configuration = configuration;
Environment = env;
}
// Data Protection
services.AddCustomDataProtectionServices(Environment, globalSettings);
public IConfiguration Configuration { get; private set; }
public IWebHostEnvironment Environment { get; set; }
// Repositories
services.AddSqlServerRepositories(globalSettings);
// Context
services.AddScoped<ICurrentContext, CurrentContext>();
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
// Caching
services.AddMemoryCache();
services.AddDistributedCache(globalSettings);
// Mvc
services.AddMvc(config =>
public void ConfigureServices(IServiceCollection services)
{
config.Filters.Add(new ModelStateValidationFilterAttribute());
});
// Options
services.AddOptions();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "Bitwarden Identity", Version = "v1" });
});
if (!globalSettings.SelfHosted)
{
services.AddIpRateLimiting(globalSettings);
}
// Cookies
if (Environment.IsDevelopment())
{
services.Configure<CookiePolicyOptions>(options =>
// Settings
var globalSettings = services.AddGlobalSettingsServices(Configuration, Environment);
if (!globalSettings.SelfHosted)
{
options.MinimumSameSitePolicy = Microsoft.AspNetCore.Http.SameSiteMode.Unspecified;
options.OnAppendCookie = ctx =>
{
ctx.CookieOptions.SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Unspecified;
};
services.Configure<IpRateLimitOptions>(Configuration.GetSection("IpRateLimitOptions"));
services.Configure<IpRateLimitPolicies>(Configuration.GetSection("IpRateLimitPolicies"));
}
// Data Protection
services.AddCustomDataProtectionServices(Environment, globalSettings);
// Repositories
services.AddSqlServerRepositories(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());
});
}
JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
// Authentication
services
.AddDistributedIdentityServices(globalSettings)
.AddAuthentication()
.AddCookie(AuthenticationSchemes.BitwardenExternalCookieAuthenticationScheme)
.AddOpenIdConnect("sso", "Single Sign On", options =>
services.AddSwaggerGen(c =>
{
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";
c.SwaggerDoc("v1", new OpenApiInfo { Title = "Bitwarden Identity", Version = "v1" });
});
options.SignInScheme = AuthenticationSchemes.BitwardenExternalCookieAuthenticationScheme;
options.ResponseType = "code";
options.SaveTokens = false;
options.GetClaimsFromUserInfoEndpoint = true;
if (!globalSettings.SelfHosted)
{
services.AddIpRateLimiting(globalSettings);
}
options.Events = new Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectEvents
// Cookies
if (Environment.IsDevelopment())
{
services.Configure<CookiePolicyOptions>(options =>
{
OnRedirectToIdentityProvider = context =>
options.MinimumSameSitePolicy = Microsoft.AspNetCore.Http.SameSiteMode.Unspecified;
options.OnAppendCookie = ctx =>
{
// 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.ContainsKey("user_identifier"))
ctx.CookieOptions.SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Unspecified;
};
});
}
JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
// Authentication
services
.AddDistributedIdentityServices(globalSettings)
.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;
options.Events = new Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectEvents
{
OnRedirectToIdentityProvider = context =>
{
context.ProtocolMessage.SessionState = context.Properties.Items["user_identifier"];
// 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.ContainsKey("user_identifier"))
{
context.ProtocolMessage.SessionState = context.Properties.Items["user_identifier"];
}
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);
}
};
});
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);
// IdentityServer
services.AddCustomIdentityServerServices(Environment, globalSettings);
// Identity
services.AddCustomIdentityServices(globalSettings);
// Identity
services.AddCustomIdentityServices(globalSettings);
// Services
services.AddBaseServices(globalSettings);
services.AddDefaultServices(globalSettings);
services.AddCoreLocalizationServices();
// Services
services.AddBaseServices(globalSettings);
services.AddDefaultServices(globalSettings);
services.AddCoreLocalizationServices();
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,
IHostApplicationLifetime appLifetime,
GlobalSettings globalSettings,
ILogger<Startup> logger)
{
IdentityModelEventSource.ShowPII = true;
app.UseSerilog(env, appLifetime, globalSettings);
// Add general security headers
app.UseMiddleware<SecurityHeadersMiddleware>();
if (!env.IsDevelopment())
{
var uri = new Uri(globalSettings.BaseServiceUri.Identity);
app.Use(async (ctx, next) =>
if (CoreHelpers.SettingHasValue(globalSettings.ServiceBus.ConnectionString) &&
CoreHelpers.SettingHasValue(globalSettings.ServiceBus.ApplicationCacheTopicName))
{
ctx.SetIdentityServerOrigin($"{uri.Scheme}://{uri.Host}");
await next();
services.AddHostedService<Core.HostedServices.ApplicationCacheHostedService>();
}
// HttpClients
services.AddHttpClient("InternalSso", client =>
{
client.BaseAddress = new Uri(globalSettings.BaseServiceUri.InternalSso);
});
}
if (globalSettings.SelfHosted)
public void Configure(
IApplicationBuilder app,
IWebHostEnvironment env,
IHostApplicationLifetime appLifetime,
GlobalSettings globalSettings,
ILogger<Startup> logger)
{
app.UsePathBase("/identity");
app.UseForwardedHeaders(globalSettings);
IdentityModelEventSource.ShowPII = true;
app.UseSerilog(env, appLifetime, globalSettings);
// Add general security headers
app.UseMiddleware<SecurityHeadersMiddleware>();
if (!env.IsDevelopment())
{
var uri = new Uri(globalSettings.BaseServiceUri.Identity);
app.Use(async (ctx, next) =>
{
ctx.SetIdentityServerOrigin($"{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))
.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, globalSettings.ProjectName + " started.");
}
// 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))
.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, globalSettings.ProjectName + " started.");
}
}

View File

@ -5,31 +5,32 @@ using IdentityServer4.Services;
using IdentityServer4.Stores;
using IdentityServer4.Validation;
namespace Bit.Identity.Utilities;
public class DiscoveryResponseGenerator : IdentityServer4.ResponseHandling.DiscoveryResponseGenerator
namespace Bit.Identity.Utilities
{
private readonly GlobalSettings _globalSettings;
public DiscoveryResponseGenerator(
IdentityServerOptions options,
IResourceStore resourceStore,
IKeyMaterialService keys,
ExtensionGrantValidator extensionGrants,
ISecretsListParser secretParsers,
IResourceOwnerPasswordValidator resourceOwnerValidator,
ILogger<DiscoveryResponseGenerator> logger,
GlobalSettings globalSettings)
: base(options, resourceStore, keys, extensionGrants, secretParsers, resourceOwnerValidator, logger)
public class DiscoveryResponseGenerator : IdentityServer4.ResponseHandling.DiscoveryResponseGenerator
{
_globalSettings = globalSettings;
}
private readonly GlobalSettings _globalSettings;
public override async Task<Dictionary<string, object>> CreateDiscoveryDocumentAsync(
string baseUrl, string issuerUri)
{
var dict = await base.CreateDiscoveryDocumentAsync(baseUrl, issuerUri);
return CoreHelpers.AdjustIdentityServerConfig(dict, _globalSettings.BaseServiceUri.Identity,
_globalSettings.BaseServiceUri.InternalIdentity);
public DiscoveryResponseGenerator(
IdentityServerOptions options,
IResourceStore resourceStore,
IKeyMaterialService keys,
ExtensionGrantValidator extensionGrants,
ISecretsListParser secretParsers,
IResourceOwnerPasswordValidator resourceOwnerValidator,
ILogger<DiscoveryResponseGenerator> logger,
GlobalSettings globalSettings)
: base(options, resourceStore, keys, extensionGrants, secretParsers, resourceOwnerValidator, logger)
{
_globalSettings = globalSettings;
}
public override async Task<Dictionary<string, object>> CreateDiscoveryDocumentAsync(
string baseUrl, string issuerUri)
{
var dict = await base.CreateDiscoveryDocumentAsync(baseUrl, issuerUri);
return CoreHelpers.AdjustIdentityServerConfig(dict, _globalSettings.BaseServiceUri.Identity,
_globalSettings.BaseServiceUri.InternalIdentity);
}
}
}

View File

@ -5,47 +5,48 @@ using IdentityServer4.ResponseHandling;
using IdentityServer4.Services;
using IdentityServer4.Stores;
namespace Bit.Identity.Utilities;
public static class ServiceCollectionExtensions
namespace Bit.Identity.Utilities
{
public static IIdentityServerBuilder AddCustomIdentityServerServices(this IServiceCollection services,
IWebHostEnvironment env, GlobalSettings globalSettings)
public static class ServiceCollectionExtensions
{
services.AddTransient<IDiscoveryResponseGenerator, DiscoveryResponseGenerator>();
public static IIdentityServerBuilder AddCustomIdentityServerServices(this IServiceCollection services,
IWebHostEnvironment env, GlobalSettings globalSettings)
{
services.AddTransient<IDiscoveryResponseGenerator, DiscoveryResponseGenerator>();
services.AddSingleton<StaticClientStore>();
services.AddTransient<IAuthorizationCodeStore, AuthorizationCodeStore>();
services.AddSingleton<StaticClientStore>();
services.AddTransient<IAuthorizationCodeStore, AuthorizationCodeStore>();
var issuerUri = new Uri(globalSettings.BaseServiceUri.InternalIdentity);
var identityServerBuilder = services
.AddIdentityServer(options =>
{
options.Endpoints.EnableIntrospectionEndpoint = false;
options.Endpoints.EnableEndSessionEndpoint = false;
options.Endpoints.EnableUserInfoEndpoint = false;
options.Endpoints.EnableCheckSessionEndpoint = false;
options.Endpoints.EnableTokenRevocationEndpoint = false;
options.IssuerUri = $"{issuerUri.Scheme}://{issuerUri.Host}";
options.Caching.ClientStoreExpiration = new TimeSpan(0, 5, 0);
if (env.IsDevelopment())
var issuerUri = new Uri(globalSettings.BaseServiceUri.InternalIdentity);
var identityServerBuilder = services
.AddIdentityServer(options =>
{
options.Authentication.CookieSameSiteMode = Microsoft.AspNetCore.Http.SameSiteMode.Unspecified;
}
options.InputLengthRestrictions.UserName = 256;
})
.AddInMemoryCaching()
.AddInMemoryApiResources(ApiResources.GetApiResources())
.AddInMemoryApiScopes(ApiScopes.GetApiScopes())
.AddClientStoreCache<ClientStore>()
.AddCustomTokenRequestValidator<CustomTokenRequestValidator>()
.AddProfileService<ProfileService>()
.AddResourceOwnerValidator<ResourceOwnerPasswordValidator>()
.AddPersistedGrantStore<PersistedGrantStore>()
.AddClientStore<ClientStore>()
.AddIdentityServerCertificate(env, globalSettings);
options.Endpoints.EnableIntrospectionEndpoint = false;
options.Endpoints.EnableEndSessionEndpoint = false;
options.Endpoints.EnableUserInfoEndpoint = false;
options.Endpoints.EnableCheckSessionEndpoint = false;
options.Endpoints.EnableTokenRevocationEndpoint = false;
options.IssuerUri = $"{issuerUri.Scheme}://{issuerUri.Host}";
options.Caching.ClientStoreExpiration = new TimeSpan(0, 5, 0);
if (env.IsDevelopment())
{
options.Authentication.CookieSameSiteMode = Microsoft.AspNetCore.Http.SameSiteMode.Unspecified;
}
options.InputLengthRestrictions.UserName = 256;
})
.AddInMemoryCaching()
.AddInMemoryApiResources(ApiResources.GetApiResources())
.AddInMemoryApiScopes(ApiScopes.GetApiScopes())
.AddClientStoreCache<ClientStore>()
.AddCustomTokenRequestValidator<CustomTokenRequestValidator>()
.AddProfileService<ProfileService>()
.AddResourceOwnerValidator<ResourceOwnerPasswordValidator>()
.AddPersistedGrantStore<PersistedGrantStore>()
.AddClientStore<ClientStore>()
.AddIdentityServerCertificate(env, globalSettings);
services.AddTransient<ICorsPolicyService, CustomCorsPolicyService>();
return identityServerBuilder;
services.AddTransient<ICorsPolicyService, CustomCorsPolicyService>();
return identityServerBuilder;
}
}
}