mirror of
https://github.com/bitwarden/server.git
synced 2025-07-02 16:42:50 -05:00
Turn on file scoped namespaces (#2225)
This commit is contained in:
@ -9,61 +9,60 @@ using Bit.Core.Utilities;
|
||||
using Bit.SharedWeb.Utilities;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Bit.Identity.Controllers
|
||||
namespace Bit.Identity.Controllers;
|
||||
|
||||
[Route("accounts")]
|
||||
[ExceptionHandlerFilter]
|
||||
public class AccountsController : Controller
|
||||
{
|
||||
[Route("accounts")]
|
||||
[ExceptionHandlerFilter]
|
||||
public class AccountsController : Controller
|
||||
private readonly ILogger<AccountsController> _logger;
|
||||
private readonly IUserRepository _userRepository;
|
||||
private readonly IUserService _userService;
|
||||
|
||||
public AccountsController(
|
||||
ILogger<AccountsController> logger,
|
||||
IUserRepository userRepository,
|
||||
IUserService userService)
|
||||
{
|
||||
private readonly ILogger<AccountsController> _logger;
|
||||
private readonly IUserRepository _userRepository;
|
||||
private readonly IUserService _userService;
|
||||
_logger = logger;
|
||||
_userRepository = userRepository;
|
||||
_userService = userService;
|
||||
}
|
||||
|
||||
public AccountsController(
|
||||
ILogger<AccountsController> logger,
|
||||
IUserRepository userRepository,
|
||||
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)
|
||||
{
|
||||
_logger = logger;
|
||||
_userRepository = userRepository;
|
||||
_userService = userService;
|
||||
return;
|
||||
}
|
||||
|
||||
// Moved from API, If you modify this endpoint, please update API as well.
|
||||
[HttpPost("register")]
|
||||
[CaptchaProtected]
|
||||
public async Task PostRegister([FromBody] RegisterRequestModel model)
|
||||
foreach (var error in result.Errors.Where(e => e.Code != "DuplicateUserName"))
|
||||
{
|
||||
var result = await _userService.RegisterUserAsync(model.ToUser(), model.MasterPasswordHash,
|
||||
model.Token, model.OrganizationUserId);
|
||||
if (result.Succeeded)
|
||||
{
|
||||
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);
|
||||
ModelState.AddModelError(string.Empty, error.Description);
|
||||
}
|
||||
|
||||
// Moved from API, If you modify this endpoint, please update API as well.
|
||||
[HttpPost("prelogin")]
|
||||
public async Task<PreloginResponseModel> PostPrelogin([FromBody] PreloginRequestModel model)
|
||||
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)
|
||||
{
|
||||
var kdfInformation = await _userRepository.GetKdfInformationByEmailAsync(model.Email);
|
||||
if (kdfInformation == null)
|
||||
kdfInformation = new UserKdfInformation
|
||||
{
|
||||
kdfInformation = new UserKdfInformation
|
||||
{
|
||||
Kdf = KdfType.PBKDF2_SHA256,
|
||||
KdfIterations = 100000,
|
||||
};
|
||||
}
|
||||
return new PreloginResponseModel(kdfInformation);
|
||||
Kdf = KdfType.PBKDF2_SHA256,
|
||||
KdfIterations = 100000,
|
||||
};
|
||||
}
|
||||
return new PreloginResponseModel(kdfInformation);
|
||||
}
|
||||
}
|
||||
|
@ -1,21 +1,20 @@
|
||||
using Bit.Core.Utilities;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Bit.Identity.Controllers
|
||||
{
|
||||
public class InfoController : Controller
|
||||
{
|
||||
[HttpGet("~/alive")]
|
||||
[HttpGet("~/now")]
|
||||
public DateTime GetAlive()
|
||||
{
|
||||
return DateTime.UtcNow;
|
||||
}
|
||||
namespace Bit.Identity.Controllers;
|
||||
|
||||
[HttpGet("~/version")]
|
||||
public JsonResult GetVersion()
|
||||
{
|
||||
return Json(CoreHelpers.GetVersion());
|
||||
}
|
||||
public class InfoController : Controller
|
||||
{
|
||||
[HttpGet("~/alive")]
|
||||
[HttpGet("~/now")]
|
||||
public DateTime GetAlive()
|
||||
{
|
||||
return DateTime.UtcNow;
|
||||
}
|
||||
|
||||
[HttpGet("~/version")]
|
||||
public JsonResult GetVersion()
|
||||
{
|
||||
return Json(CoreHelpers.GetVersion());
|
||||
}
|
||||
}
|
||||
|
@ -11,265 +11,264 @@ using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Localization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Bit.Identity.Controllers
|
||||
namespace Bit.Identity.Controllers;
|
||||
|
||||
// TODO: 2022-01-12, Remove account alias
|
||||
[Route("account/[action]")]
|
||||
[Route("sso/[action]")]
|
||||
public class SsoController : Controller
|
||||
{
|
||||
// TODO: 2022-01-12, Remove account alias
|
||||
[Route("account/[action]")]
|
||||
[Route("sso/[action]")]
|
||||
public class SsoController : Controller
|
||||
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)
|
||||
{
|
||||
private readonly IIdentityServerInteractionService _interaction;
|
||||
private readonly ILogger<SsoController> _logger;
|
||||
private readonly ISsoConfigRepository _ssoConfigRepository;
|
||||
private readonly IUserRepository _userRepository;
|
||||
private readonly IHttpClientFactory _clientFactory;
|
||||
_interaction = interaction;
|
||||
_logger = logger;
|
||||
_ssoConfigRepository = ssoConfigRepository;
|
||||
_userRepository = userRepository;
|
||||
_clientFactory = clientFactory;
|
||||
}
|
||||
|
||||
public SsoController(
|
||||
IIdentityServerInteractionService interaction,
|
||||
ILogger<SsoController> logger,
|
||||
ISsoConfigRepository ssoConfigRepository,
|
||||
IUserRepository userRepository,
|
||||
IHttpClientFactory clientFactory)
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> PreValidate(string domainHint)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(domainHint))
|
||||
{
|
||||
_interaction = interaction;
|
||||
_logger = logger;
|
||||
_ssoConfigRepository = ssoConfigRepository;
|
||||
_userRepository = userRepository;
|
||||
_clientFactory = clientFactory;
|
||||
Response.StatusCode = 400;
|
||||
return Json(new ErrorResponseModel("No domain hint was provided"));
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> PreValidate(string domainHint)
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(domainHint))
|
||||
{
|
||||
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");
|
||||
// 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,
|
||||
});
|
||||
}
|
||||
// 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");
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> Login(string returnUrl)
|
||||
catch (Exception ex)
|
||||
{
|
||||
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))
|
||||
_logger.LogError(ex, "Error pre-validating against SSO service");
|
||||
Response.StatusCode = 500;
|
||||
return Json(new ErrorResponseModel("Error pre-validating SSO authentication")
|
||||
{
|
||||
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,
|
||||
ExceptionMessage = ex.Message,
|
||||
ExceptionStackTrace = ex.StackTrace,
|
||||
InnerExceptionMessage = ex.InnerException?.Message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> ExternalChallenge(string domainHint, string returnUrl,
|
||||
string userIdentifier, string 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))
|
||||
{
|
||||
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 =
|
||||
{
|
||||
{ "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);
|
||||
throw new Exception("No domain_hint provided");
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<ActionResult> ExternalCallback()
|
||||
var userIdentifier = context.Parameters.AllKeys.Contains("user_identifier") ?
|
||||
context.Parameters["user_identifier"] : null;
|
||||
|
||||
return RedirectToAction(nameof(ExternalChallenge), new
|
||||
{
|
||||
// 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");
|
||||
}
|
||||
domainHint = domainHint,
|
||||
returnUrl,
|
||||
userIdentifier,
|
||||
ssoToken,
|
||||
});
|
||||
}
|
||||
|
||||
// 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");
|
||||
}
|
||||
[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.");
|
||||
}
|
||||
|
||||
private async Task<(User user, string provider, string providerUserId, IEnumerable<Claim> claims)>
|
||||
FindUserFromExternalProviderAsync(AuthenticateResult result)
|
||||
var ssoConfig = await _ssoConfigRepository.GetByIdentifierAsync(domainHint);
|
||||
if (ssoConfig == null || !ssoConfig.Enabled)
|
||||
{
|
||||
var externalUser = result.Principal;
|
||||
throw new Exception("Organization not found or SSO configuration not enabled");
|
||||
}
|
||||
var organizationId = ssoConfig.OrganizationId.ToString();
|
||||
|
||||
// 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");
|
||||
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 },
|
||||
}
|
||||
};
|
||||
|
||||
// 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);
|
||||
if (!string.IsNullOrWhiteSpace(userIdentifier))
|
||||
{
|
||||
props.Items.Add("user_identifier", userIdentifier);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
localClaims.Add(new Claim(JwtClaimTypes.SessionId, sid.Value));
|
||||
}
|
||||
return Challenge(props, scheme);
|
||||
}
|
||||
|
||||
// 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 } });
|
||||
}
|
||||
[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");
|
||||
}
|
||||
|
||||
private bool IsNativeClient(IdentityServer4.Models.AuthorizationRequest context)
|
||||
// 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)
|
||||
{
|
||||
return !context.RedirectUri.StartsWith("https", StringComparison.Ordinal)
|
||||
&& !context.RedirectUri.StartsWith("http", StringComparison.Ordinal);
|
||||
// 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");
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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 bool IsNativeClient(IdentityServer4.Models.AuthorizationRequest context)
|
||||
{
|
||||
return !context.RedirectUri.StartsWith("https", StringComparison.Ordinal)
|
||||
&& !context.RedirectUri.StartsWith("http", StringComparison.Ordinal);
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
namespace Bit.Identity.Models
|
||||
namespace Bit.Identity.Models;
|
||||
|
||||
public class RedirectViewModel
|
||||
{
|
||||
public class RedirectViewModel
|
||||
{
|
||||
public string RedirectUrl { get; set; }
|
||||
}
|
||||
public string RedirectUrl { get; set; }
|
||||
}
|
||||
|
@ -2,44 +2,43 @@
|
||||
using Bit.Core.Utilities;
|
||||
using Serilog.Events;
|
||||
|
||||
namespace Bit.Identity
|
||||
namespace Bit.Identity;
|
||||
|
||||
public class Program
|
||||
{
|
||||
public class Program
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
CreateHostBuilder(args)
|
||||
.Build()
|
||||
.Run();
|
||||
}
|
||||
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 =>
|
||||
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)
|
||||
{
|
||||
var context = e.Properties["SourceContext"].ToString();
|
||||
if (context.Contains(typeof(IpRateLimitMiddleware).FullName) &&
|
||||
e.Level == LogEventLevel.Information)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
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;
|
||||
}));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -13,214 +13,213 @@ using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.IdentityModel.Logging;
|
||||
using Microsoft.OpenApi.Models;
|
||||
|
||||
namespace Bit.Identity
|
||||
namespace Bit.Identity;
|
||||
|
||||
public class Startup
|
||||
{
|
||||
public class Startup
|
||||
public Startup(IWebHostEnvironment env, IConfiguration configuration)
|
||||
{
|
||||
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)
|
||||
{
|
||||
CultureInfo.DefaultThreadCurrentCulture = new CultureInfo("en-US");
|
||||
Configuration = configuration;
|
||||
Environment = env;
|
||||
services.Configure<IpRateLimitOptions>(Configuration.GetSection("IpRateLimitOptions"));
|
||||
services.Configure<IpRateLimitPolicies>(Configuration.GetSection("IpRateLimitPolicies"));
|
||||
}
|
||||
|
||||
public IConfiguration Configuration { get; private set; }
|
||||
public IWebHostEnvironment Environment { get; set; }
|
||||
// Data Protection
|
||||
services.AddCustomDataProtectionServices(Environment, globalSettings);
|
||||
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
// Repositories
|
||||
services.AddSqlServerRepositories(globalSettings);
|
||||
|
||||
// Context
|
||||
services.AddScoped<ICurrentContext, CurrentContext>();
|
||||
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
|
||||
|
||||
// Caching
|
||||
services.AddMemoryCache();
|
||||
services.AddDistributedCache(globalSettings);
|
||||
|
||||
// Mvc
|
||||
services.AddMvc(config =>
|
||||
{
|
||||
// Options
|
||||
services.AddOptions();
|
||||
config.Filters.Add(new ModelStateValidationFilterAttribute());
|
||||
});
|
||||
|
||||
// Settings
|
||||
var globalSettings = services.AddGlobalSettingsServices(Configuration, Environment);
|
||||
if (!globalSettings.SelfHosted)
|
||||
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 =>
|
||||
{
|
||||
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());
|
||||
});
|
||||
|
||||
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 =>
|
||||
options.MinimumSameSitePolicy = Microsoft.AspNetCore.Http.SameSiteMode.Unspecified;
|
||||
options.OnAppendCookie = ctx =>
|
||||
{
|
||||
options.MinimumSameSitePolicy = Microsoft.AspNetCore.Http.SameSiteMode.Unspecified;
|
||||
options.OnAppendCookie = ctx =>
|
||||
{
|
||||
ctx.CookieOptions.SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Unspecified;
|
||||
};
|
||||
});
|
||||
}
|
||||
ctx.CookieOptions.SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Unspecified;
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
|
||||
JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
|
||||
|
||||
// Authentication
|
||||
services
|
||||
.AddDistributedIdentityServices(globalSettings)
|
||||
.AddAuthentication()
|
||||
.AddCookie(AuthenticationSchemes.BitwardenExternalCookieAuthenticationScheme)
|
||||
.AddOpenIdConnect("sso", "Single Sign On", options =>
|
||||
// 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
|
||||
{
|
||||
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 =>
|
||||
{
|
||||
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.ContainsKey("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);
|
||||
context.ProtocolMessage.SessionState = context.Properties.Items["user_identifier"];
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
// IdentityServer
|
||||
services.AddCustomIdentityServerServices(Environment, globalSettings);
|
||||
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);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
// Identity
|
||||
services.AddCustomIdentityServices(globalSettings);
|
||||
// IdentityServer
|
||||
services.AddCustomIdentityServerServices(Environment, globalSettings);
|
||||
|
||||
// Services
|
||||
services.AddBaseServices(globalSettings);
|
||||
services.AddDefaultServices(globalSettings);
|
||||
services.AddCoreLocalizationServices();
|
||||
// Identity
|
||||
services.AddCustomIdentityServices(globalSettings);
|
||||
|
||||
if (CoreHelpers.SettingHasValue(globalSettings.ServiceBus.ConnectionString) &&
|
||||
CoreHelpers.SettingHasValue(globalSettings.ServiceBus.ApplicationCacheTopicName))
|
||||
// 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) =>
|
||||
{
|
||||
services.AddHostedService<Core.HostedServices.ApplicationCacheHostedService>();
|
||||
}
|
||||
|
||||
// HttpClients
|
||||
services.AddHttpClient("InternalSso", client =>
|
||||
{
|
||||
client.BaseAddress = new Uri(globalSettings.BaseServiceUri.InternalSso);
|
||||
ctx.SetIdentityServerOrigin($"{uri.Scheme}://{uri.Host}");
|
||||
await next();
|
||||
});
|
||||
}
|
||||
|
||||
public void Configure(
|
||||
IApplicationBuilder app,
|
||||
IWebHostEnvironment env,
|
||||
IHostApplicationLifetime appLifetime,
|
||||
GlobalSettings globalSettings,
|
||||
ILogger<Startup> logger)
|
||||
if (globalSettings.SelfHosted)
|
||||
{
|
||||
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.");
|
||||
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.");
|
||||
}
|
||||
}
|
||||
|
@ -5,32 +5,31 @@ using IdentityServer4.Services;
|
||||
using IdentityServer4.Stores;
|
||||
using IdentityServer4.Validation;
|
||||
|
||||
namespace Bit.Identity.Utilities
|
||||
namespace Bit.Identity.Utilities;
|
||||
|
||||
public class DiscoveryResponseGenerator : IdentityServer4.ResponseHandling.DiscoveryResponseGenerator
|
||||
{
|
||||
public class DiscoveryResponseGenerator : IdentityServer4.ResponseHandling.DiscoveryResponseGenerator
|
||||
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)
|
||||
{
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
_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)
|
||||
{
|
||||
_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 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);
|
||||
}
|
||||
}
|
||||
|
@ -5,48 +5,47 @@ using IdentityServer4.ResponseHandling;
|
||||
using IdentityServer4.Services;
|
||||
using IdentityServer4.Stores;
|
||||
|
||||
namespace Bit.Identity.Utilities
|
||||
namespace Bit.Identity.Utilities;
|
||||
|
||||
public static class ServiceCollectionExtensions
|
||||
{
|
||||
public static class ServiceCollectionExtensions
|
||||
public static IIdentityServerBuilder AddCustomIdentityServerServices(this IServiceCollection services,
|
||||
IWebHostEnvironment env, GlobalSettings globalSettings)
|
||||
{
|
||||
public static IIdentityServerBuilder AddCustomIdentityServerServices(this IServiceCollection services,
|
||||
IWebHostEnvironment env, GlobalSettings globalSettings)
|
||||
{
|
||||
services.AddTransient<IDiscoveryResponseGenerator, DiscoveryResponseGenerator>();
|
||||
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 =>
|
||||
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())
|
||||
{
|
||||
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);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user