protected virtual void SetHeaders(HttpResponse response) { logger.ScopeTrace($"Adding http security headers. Is {(isHtmlContent ? string.Empty : "not")} view."); response.SetHeader("X-Content-Type-Options", "nosniff"); response.SetHeader("Referrer-Policy", "no-referrer"); response.SetHeader("X-XSS-Protection", "1; mode=block"); if (isHtmlContent) { HeaderXFrameOptions(response); } var csp = CreateCsp().ToSpaceList(); if (!csp.IsNullOrEmpty()) { response.SetHeader("Content-Security-Policy", csp); response.SetHeader("X-Content-Security-Policy", csp); } logger.ScopeTrace($"Http security headers added."); }
public async Task <IActionResult> LoginRedirectAsync(UpPartyLink partyLink, LoginRequest loginRequest) { logger.ScopeTrace("Up, Login redirect."); var partyId = await UpParty.IdFormat(RouteBinding, partyLink.Name); logger.SetScopeProperty("upPartyId", partyId); await loginRequest.ValidateObjectAsync(); await sequenceLogic.SaveSequenceDataAsync(new LoginUpSequenceData { DownPartyId = loginRequest.DownParty.Id, DownPartyType = loginRequest.DownParty.Type, UpPartyId = partyId, LoginAction = loginRequest.LoginAction, UserId = loginRequest.UserId, MaxAge = loginRequest.MaxAge, EmailHint = loginRequest.EmailHint, Culture = loginRequest.Culture }); return(new RedirectResult($"~/{RouteBinding.TenantName}/{RouteBinding.TrackName}/({partyLink.Name})/login/_{SequenceString}")); }
public async Task <IActionResult> SpMetadata() { try { logger.ScopeTrace(() => $"SAML SP Metadata request, Up type '{RouteBinding.UpParty?.Type}'"); switch (RouteBinding.UpParty?.Type) { case PartyTypes.Saml2: case null: return(await serviceProvider.GetService <SamlMetadataExposeLogic>().SpMetadataAsync(RouteBinding.UpParty?.Id)); default: throw new NotSupportedException($"Party type '{RouteBinding.UpParty?.Type}' not supported."); } } catch (Exception ex) { throw new EndpointException($"SAML SP Metadata request failed, Name '{RouteBinding.UpParty?.Name}'.", ex) { RouteBinding = RouteBinding }; } }
public async Task <IActionResult> LoginRedirectAsync(UpPartyLink partyLink, LoginRequest loginRequest) { logger.ScopeTrace("Up, Login redirect."); var partyId = await UpParty.IdFormatAsync(RouteBinding, partyLink.Name); logger.SetScopeProperty("upPartyId", partyId); await loginRequest.ValidateObjectAsync(); await sequenceLogic.SetUiUpPartyIdAsync(partyId); await sequenceLogic.SaveSequenceDataAsync(new LoginUpSequenceData { DownPartyLink = loginRequest.DownPartyLink, UpPartyId = partyId, LoginAction = loginRequest.LoginAction, UserId = loginRequest.UserId, MaxAge = loginRequest.MaxAge, Email = loginRequest.EmailHint, }); return(HttpContext.GetUpPartyUrl(partyLink.Name, Constants.Routes.LoginController, includeSequence: true).ToRedirectResult()); }
public async Task <User> CreateUser(string email, string password, bool changePassword = false, List <Claim> claims = null, string tenantName = null, string trackName = null, bool checkUserAndPasswordPolicy = true, bool confirmAccount = true, bool emailVerified = false, bool disableAccount = false) { logger.ScopeTrace($"Creating user '{email}', Route '{RouteBinding?.Route}'."); email = email?.ToLower(); ValidateEmail(email); var user = new User { UserId = Guid.NewGuid().ToString(), ConfirmAccount = confirmAccount, EmailVerified = emailVerified, DisableAccount = disableAccount }; var userIdKey = new User.IdKey { TenantName = tenantName ?? RouteBinding.TenantName, TrackName = trackName ?? RouteBinding.TrackName, Email = email?.ToLower() }; await user.SetIdAsync(userIdKey); await secretHashLogic.AddSecretHashAsync(user, password); if (claims?.Count() > 0) { user.Claims = claims.ToClaimAndValues(); } if (checkUserAndPasswordPolicy) { if (await tenantRepository.ExistsAsync <User>(await User.IdFormat(userIdKey))) { throw new UserExistsException($"User '{email}' already exists."); } await ValidatePasswordPolicy(email, password); } user.ChangePassword = changePassword; await tenantRepository.CreateAsync(user); logger.ScopeTrace($"User '{email}' created, with user id '{user.UserId}'."); return(user); }
public async Task <IActionResult> RegTwoFactor() { try { logger.ScopeTrace(() => "Start two factor registration."); var sequenceData = await sequenceLogic.GetSequenceDataAsync <LoginUpSequenceData>(remove : false); loginPageLogic.CheckUpParty(sequenceData); if (sequenceData.TwoFactorAppState != TwoFactorAppSequenceStates.DoRegistration) { throw new InvalidOperationException($"Invalid {nameof(TwoFactorAppSequenceStates)} is '{sequenceData.TwoFactorAppState}'. Required to be '{TwoFactorAppSequenceStates.DoRegistration}'."); } if (!sequenceData.EmailVerified) { await accountActionLogic.SendConfirmationEmailAsync(sequenceData.Email); return(GetEmailNotConfirmedView()); } var loginUpParty = await tenantRepository.GetAsync <LoginUpParty>(sequenceData.UpPartyId); securityHeaderLogic.AddImgSrc(loginUpParty.IconUrl); securityHeaderLogic.AddImgSrcFromCss(loginUpParty.Css); var twoFactorSetupInfo = await accountTwoFactorLogic.GenerateSetupCodeAsync(loginUpParty.TwoFactorAppName, sequenceData.Email); sequenceData.TwoFactorAppNewSecret = twoFactorSetupInfo.Secret; await sequenceLogic.SaveSequenceDataAsync(sequenceData); return(View(new RegisterTwoFactorViewModel { Title = loginUpParty.Title, IconUrl = loginUpParty.IconUrl, Css = loginUpParty.Css, QrCodeSetupImageUrl = twoFactorSetupInfo.QrCodeSetupImageUrl, ManualSetupKey = twoFactorSetupInfo.ManualSetupKey })); } catch (Exception ex) { throw new EndpointException($"Start two factor registration failed, Name '{RouteBinding.UpParty.Name}'.", ex) { RouteBinding = RouteBinding }; } }
public async Task <(string sequenceString, Sequence sequence)> StartSeparateSequenceAsync(bool?accountAction = null, Sequence currentSequence = null, bool requireeUiUpPartyId = false) { try { var sequence = new Sequence { Id = RandomGenerator.Generate(12), CreateTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds(), AccountAction = accountAction }; if (currentSequence?.Culture?.IsNullOrEmpty() == false) { sequence.Culture = currentSequence.Culture; } if (currentSequence?.UiUpPartyId.IsNullOrEmpty() == false) { sequence.UiUpPartyId = currentSequence.UiUpPartyId; } else if (requireeUiUpPartyId) { throw new Exception("Required UiUpPartyId is null or empty."); } var sequenceString = await CreateSequenceStringAsync(sequence); logger.ScopeTrace($"Sequence started, id '{sequence.Id}'.", new Dictionary <string, string> { { "sequenceId", sequence.Id }, { "accountAction", accountAction == true ? "true" : "false" } }); return(sequenceString : sequenceString, sequence : sequence); } catch (Exception ex) { throw new SequenceException("Unable to start sequence.", ex); } }
public async Task <IActionResult> EndSessionRequestRedirectAsync(UpPartyLink partyLink, LogoutRequest logoutRequest) { logger.ScopeTrace("Up, OIDC End session request redirect."); var partyId = await UpParty.IdFormatAsync(RouteBinding, partyLink.Name); logger.SetScopeProperty("upPartyId", partyId); await logoutRequest.ValidateObjectAsync(); await sequenceLogic.SaveSequenceDataAsync(new OidcUpSequenceData { DownPartyLink = logoutRequest.DownPartyLink, UpPartyId = partyId, SessionId = logoutRequest.SessionId, RequireLogoutConsent = logoutRequest.RequireLogoutConsent, PostLogoutRedirect = logoutRequest.PostLogoutRedirect, }); return(HttpContext.GetUpPartyUrl(partyLink.Name, Constants.Routes.OAuthUpJumpController, Constants.Endpoints.UpJump.EndSessionRequest, includeSequence: true).ToRedirectResult()); }
public async Task <IActionResult> Confirmation() { try { logger.ScopeTrace("Start confirmation verification."); var verified = await accountActionLogic.VerifyConfirmationAsync(); var uiLoginUpParty = await tenantRepository.GetAsync <UiLoginUpPartyData>(Sequence.UiUpPartyId); return(View(new ConfirmationViewModel { CssStyle = uiLoginUpParty.CssStyle, Verified = verified })); } catch (Exception ex) { throw new EndpointException($"Confirmation failed, Name '{RouteBinding.UpParty.Name}'.", ex) { RouteBinding = RouteBinding }; } }
public async Task <long> VerifyFailingLoginCountAsync(string email) { var key = FailingLoginCountRadisKey(email); var db = redisConnectionMultiplexer.GetDatabase(); if (await db.KeyExistsAsync(FailingLoginLockedRadisKey(email))) { logger.ScopeTrace(() => $"User '{email}' locked by observation period.", triggerEvent: true); throw new UserObservationPeriodException($"User '{email}' locked by observation period."); } var failingLoginCount = (long?)await db.StringGetAsync(key); if (failingLoginCount.HasValue && failingLoginCount.Value >= RouteBinding.MaxFailingLogins) { await db.StringSetAsync(FailingLoginLockedRadisKey(email), true, TimeSpan.FromSeconds(RouteBinding.FailingLoginObservationPeriod)); await db.KeyDeleteAsync(key); logger.ScopeTrace(() => $"Observation period started for user '{email}'.", scopeProperties: FailingLoginCountDictonary(failingLoginCount.Value), triggerEvent: true); throw new UserObservationPeriodException($"Observation period started for user '{email}'."); } return(failingLoginCount.HasValue ? failingLoginCount.Value : 0); }
public async Task <string> CreateAuthCodeGrantAsync(TClient client, List <Claim> claims, string redirectUri, string scope, string nonce, string codeChallenge, string codeChallengeMethod) { logger.ScopeTrace($"Create Authorization code grant, Route '{RouteBinding.Route}'."); if (!client.AuthorizationCodeLifetime.HasValue) { throw new EndpointException("Client AuthorizationCodeLifetime not configured.") { RouteBinding = RouteBinding } } ; var grantClaims = await claimsDownLogic.FilterJwtClaimsAsync(client, claims, scope?.ToSpaceList(), includeIdTokenClaims : true, includeAccessTokenClaims : true); var code = RandomGenerator.Generate(64); var grant = new AuthCodeTtlGrant { TimeToLive = client.AuthorizationCodeLifetime.Value, Claims = grantClaims.ToClaimAndValues(), ClientId = client.ClientId, RedirectUri = redirectUri, Scope = scope, Nonce = nonce, CodeChallenge = codeChallenge, CodeChallengeMethod = codeChallengeMethod }; await grant.SetIdAsync(new AuthCodeTtlGrant.IdKey { TenantName = RouteBinding.TenantName, TrackName = RouteBinding.TrackName, Code = code }); await tenantRepository.SaveAsync(grant); logger.ScopeTrace($"Authorization code grant created, Code '{code}'."); return(code); }
public async Task <IActionResult> AuthorizationResponse() { try { logger.ScopeTrace(() => $"Authorization response, Up type '{RouteBinding.UpParty.Type}'"); switch (RouteBinding.UpParty.Type) { case PartyTypes.Oidc: return(await serviceProvider.GetService <OidcAuthUpLogic <OidcUpParty, OidcUpClient> >().AuthenticationResponseAsync(RouteBinding.UpParty.Id)); default: throw new NotSupportedException($"Party type '{RouteBinding.UpParty.Type}' not supported."); } } catch (Exception ex) { throw new EndpointException($"Authorization response failed, Name '{RouteBinding.UpParty.Name}'.", ex) { RouteBinding = RouteBinding }; } }
public virtual async Task <IActionResult> TokenRequestAsync(string partyId) { logger.ScopeTrace("Down, OAuth Token request."); logger.SetScopeProperty("downPartyId", partyId); var party = await tenantRepository.GetAsync <TParty>(partyId); if (party.Client == null) { throw new NotSupportedException($"Party Client not configured."); } var formDictionary = HttpContext.Request.Form.ToDictionary(); var tokenRequest = formDictionary.ToObject <TokenRequest>(); var clientCredentials = formDictionary.ToObject <ClientCredentials>(); logger.ScopeTrace($"Token request '{tokenRequest.ToJsonIndented()}'."); logger.SetScopeProperty("clientId", tokenRequest.ClientId); try { logger.SetScopeProperty("GrantType", tokenRequest.GrantType); switch (tokenRequest.GrantType) { case IdentityConstants.GrantTypes.AuthorizationCode: throw new NotImplementedException(); case IdentityConstants.GrantTypes.RefreshToken: throw new NotImplementedException(); case IdentityConstants.GrantTypes.ClientCredentials: ValidateClientCredentialsRequest(party.Client, tokenRequest); await ValidateSecret(party.Client, tokenRequest, clientCredentials); return(await ClientCredentialsGrant(party.Client, tokenRequest)); case IdentityConstants.GrantTypes.Delegation: throw new NotImplementedException(); default: throw new OAuthRequestException($"Unsupported grant type '{tokenRequest.GrantType}'.") { RouteBinding = RouteBinding, Error = IdentityConstants.ResponseErrors.UnsupportedGrantType }; } } catch (ArgumentException ex) { throw new OAuthRequestException(ex.Message, ex) { RouteBinding = RouteBinding, Error = IdentityConstants.ResponseErrors.InvalidRequest }; } }
public Task <List <Claim> > Transform(IEnumerable <ClaimTransform> claimTransformations, IEnumerable <Claim> claims) { if (claimTransformations == null || claims == null) { return(Task.FromResult(new List <Claim>(claims))); } logger.ScopeTrace("Transform claims."); var transformedClaims = new List <Claim>(claims); var orderedTransformations = claimTransformations.OrderBy(t => t.Order); foreach (var transformation in orderedTransformations) { switch (transformation.Type) { case ClaimTransformTypes.Constant: transformedClaims.Add(ConstantTransformation(transformation)); break; case ClaimTransformTypes.Match: transformedClaims.AddRange(MatchTransformation(transformation, transformedClaims)); break; case ClaimTransformTypes.RegexMatch: transformedClaims.AddRange(RegexMatchTransformation(transformation, transformedClaims)); break; case ClaimTransformTypes.Map: transformedClaims.AddRange(MapTransformation(transformation, transformedClaims)); break; case ClaimTransformTypes.RegexMap: transformedClaims.AddRange(RegexMapTransformation(transformation, transformedClaims)); break; case ClaimTransformTypes.Concatenate: transformedClaims.AddRange(ConcatenateTransformation(transformation, transformedClaims)); break; default: throw new NotSupportedException($"Claim transformation type '{transformation.Type}' not supported."); } } return(Task.FromResult(transformedClaims)); }
public async Task ThrowIfUserExists(string email) { logger.ScopeTrace($"Check if user exists '{email}', Route '{RouteBinding.Route}'."); ValidateEmail(email); if (await tenantRepository.ExistsAsync <User>(await User.IdFormat(new User.IdKey { TenantName = RouteBinding.TenantName, TrackName = RouteBinding.TrackName, Email = email }))) { throw new UserExistsException($"User '{email}' already exists."); } }
public async Task <IActionResult> EndSessionRequestAsync(string partyId) { logger.ScopeTrace("Down, End session request."); logger.SetScopeProperty("downPartyId", partyId); var party = await tenantRepository.GetAsync <TParty>(partyId); if (party.Client == null) { throw new NotSupportedException("Party Client not configured."); } var formOrQueryDictionary = HttpContext.Request.Method switch { "POST" => party.Client.ResponseMode == IdentityConstants.ResponseModes.FormPost ? HttpContext.Request.Form.ToDictionary() : throw new NotSupportedException($"POST not supported by response mode '{party.Client.ResponseMode}'."), "GET" => party.Client.ResponseMode == IdentityConstants.ResponseModes.Query ? HttpContext.Request.Query.ToDictionary() : throw new NotSupportedException($"GET not supported by response mode '{party.Client.ResponseMode}'."), _ => throw new NotSupportedException($"Request method not supported by response mode '{party.Client.ResponseMode}'") }; var rpInitiatedLogoutRequest = formOrQueryDictionary.ToObject <RpInitiatedLogoutRequest>(); logger.ScopeTrace($"end session request '{rpInitiatedLogoutRequest.ToJsonIndented()}'."); logger.SetScopeProperty("downPartyClientId", party.Client.ClientId); ValidateEndSessionRequest(party.Client, rpInitiatedLogoutRequest); logger.ScopeTrace("Down, OIDC End session request accepted.", triggerEvent: true); (var validIdToken, var sessionId, var idTokenClaims) = await ValidateIdTokenHintAsync(party.Client, rpInitiatedLogoutRequest.IdTokenHint); if (!validIdToken) { if (party.Client.RequireLogoutIdTokenHint) { if (!rpInitiatedLogoutRequest.IdTokenHint.IsNullOrEmpty()) { throw new OAuthRequestException($"Invalid ID Token hint.") { RouteBinding = RouteBinding }; } throw new OAuthRequestException($"ID Token hint is required.") { RouteBinding = RouteBinding }; } } else { logger.ScopeTrace("Valid ID token hint."); } var postLogoutRedirectUri = !rpInitiatedLogoutRequest.PostLogoutRedirectUri.IsNullOrWhiteSpace() ? rpInitiatedLogoutRequest.PostLogoutRedirectUri : party.Client.PostLogoutRedirectUri; await sequenceLogic.SaveSequenceDataAsync(new OidcDownSequenceData { RedirectUri = postLogoutRedirectUri, State = rpInitiatedLogoutRequest.State, }); var toUpPartie = GetToUpParty(idTokenClaims); logger.ScopeTrace($"Request, Up type '{toUpPartie.Type}'."); switch (toUpPartie.Type) { case PartyTypes.Login: return(await serviceProvider.GetService <LogoutUpLogic>().LogoutRedirect(toUpPartie, GetLogoutRequest(party, sessionId, validIdToken, postLogoutRedirectUri))); case PartyTypes.OAuth2: throw new NotImplementedException(); case PartyTypes.Oidc: return(await serviceProvider.GetService <OidcRpInitiatedLogoutUpLogic <OidcUpParty, OidcUpClient> >().EndSessionRequestRedirectAsync(toUpPartie, GetLogoutRequest(party, sessionId, validIdToken, postLogoutRedirectUri))); case PartyTypes.Saml2: if (!validIdToken) { throw new OAuthRequestException($"ID Token hint is required for SAML 2.0 Up-party.") { RouteBinding = RouteBinding }; } return(await serviceProvider.GetService <SamlLogoutUpLogic>().LogoutRequestRedirectAsync(toUpPartie, GetSamlLogoutRequest(party, sessionId, idTokenClaims))); default: throw new NotSupportedException($"Party type '{toUpPartie.Type}' not supported."); } }
public override async Task <IActionResult> TokenRequestAsync(string partyId) { logger.ScopeTrace(() => "Down, OIDC Token request."); logger.SetScopeProperty(Constants.Logs.DownPartyId, partyId); var party = await tenantRepository.GetAsync <TParty>(partyId); if (party.Client == null) { throw new NotSupportedException("Party Client not configured."); } logger.SetScopeProperty(Constants.Logs.DownPartyClientId, party.Client.ClientId); var formDictionary = HttpContext.Request.Form.ToDictionary(); var tokenRequest = formDictionary.ToObject <TokenRequest>(); logger.ScopeTrace(() => $"Down, Token request '{tokenRequest.ToJsonIndented()}'.", traceType: TraceTypes.Message); var clientCredentials = formDictionary.ToObject <ClientCredentials>(); logger.ScopeTrace(() => $"Down, Client credentials '{new ClientCredentials { ClientSecret = $"{(clientCredentials.ClientSecret?.Length > 10 ? clientCredentials.ClientSecret.Substring(0, 3) : string.Empty)}..." }.ToJsonIndented()}'.", traceType: TraceTypes.Message); var codeVerifierSecret = party.Client.RequirePkce ? formDictionary.ToObject <CodeVerifierSecret>() : null; if (codeVerifierSecret != null) { logger.ScopeTrace(() => $"Down, Code verifier secret '{new CodeVerifierSecret { CodeVerifier = $"{(codeVerifierSecret.CodeVerifier?.Length > 10 ? codeVerifierSecret.CodeVerifier.Substring(0, 3) : string.Empty)}..." }.ToJsonIndented()}'.", traceType: TraceTypes.Message); } try { logger.SetScopeProperty(Constants.Logs.GrantType, tokenRequest.GrantType); switch (tokenRequest.GrantType) { case IdentityConstants.GrantTypes.AuthorizationCode: ValidateAuthCodeRequest(party.Client, tokenRequest); var validatePkce = party.Client.RequirePkce && codeVerifierSecret != null; await ValidateSecretAsync(party.Client, tokenRequest, clientCredentials, secretValidationRequired : !validatePkce); return(await AuthorizationCodeGrantAsync(party.Client, tokenRequest, validatePkce, codeVerifierSecret)); case IdentityConstants.GrantTypes.RefreshToken: ValidateRefreshTokenRequest(party.Client, tokenRequest); await ValidateSecretAsync(party.Client, tokenRequest, clientCredentials, secretValidationRequired : !party.Client.RequirePkce); return(await RefreshTokenGrantAsync(party.Client, tokenRequest)); case IdentityConstants.GrantTypes.ClientCredentials: ValidateClientCredentialsRequest(party.Client, tokenRequest); await ValidateSecretAsync(party.Client, tokenRequest, clientCredentials); return(await ClientCredentialsGrantAsync(party, tokenRequest)); case IdentityConstants.GrantTypes.Delegation: throw new NotImplementedException(); default: throw new OAuthRequestException($"Unsupported grant type '{tokenRequest.GrantType}'.") { RouteBinding = RouteBinding, Error = IdentityConstants.ResponseErrors.UnsupportedGrantType }; } } catch (ArgumentException ex) { throw new OAuthRequestException(ex.Message, ex) { RouteBinding = RouteBinding, Error = IdentityConstants.ResponseErrors.InvalidRequest }; } }
public async Task <IActionResult> AuthenticationRequestAsync(string partyId) { logger.ScopeTrace("Down, OIDC Authentication request."); logger.SetScopeProperty("downPartyId", partyId); var party = await tenantRepository.GetAsync <TParty>(partyId); if (party.Client == null) { throw new NotSupportedException($"Party Client not configured."); } var queryDictionary = HttpContext.Request.Query.ToDictionary(); var authenticationRequest = queryDictionary.ToObject <AuthenticationRequest>(); logger.ScopeTrace($"Authentication request '{authenticationRequest.ToJsonIndented()}'."); logger.SetScopeProperty("clientId", authenticationRequest.ClientId); var codeChallengeSecret = party.Client.EnablePkce.Value ? queryDictionary.ToObject <CodeChallengeSecret>() : null; if (codeChallengeSecret != null) { codeChallengeSecret.Validate(); logger.ScopeTrace($"CodeChallengeSecret '{codeChallengeSecret.ToJsonIndented()}'."); } try { var requireCodeFlow = party.Client.EnablePkce.Value && codeChallengeSecret != null; ValidateAuthenticationRequest(party.Client, authenticationRequest, requireCodeFlow); logger.ScopeTrace("Down, OIDC Authentication request accepted.", triggerEvent: true); if (!authenticationRequest.UiLocales.IsNullOrWhiteSpace()) { await sequenceLogic.SetCultureAsync(authenticationRequest.UiLocales.ToSpaceList()); } await sequenceLogic.SaveSequenceDataAsync(new OidcDownSequenceData { ResponseType = authenticationRequest.ResponseType, RedirectUri = authenticationRequest.RedirectUri, Scope = authenticationRequest.Scope, State = authenticationRequest.State, ResponseMode = authenticationRequest.ResponseMode, Nonce = authenticationRequest.Nonce, CodeChallenge = codeChallengeSecret?.CodeChallenge, CodeChallengeMethod = codeChallengeSecret?.CodeChallengeMethod, }); await formActionLogic.CreateFormActionByUrlAsync(authenticationRequest.RedirectUri); var type = RouteBinding.ToUpParties.First().Type; logger.ScopeTrace($"Request, Up type '{type}'."); switch (type) { case PartyTypes.Login: return(await serviceProvider.GetService <LoginUpLogic>().LoginRedirectAsync(RouteBinding.ToUpParties.First(), await GetLoginRequestAsync(party, authenticationRequest))); case PartyTypes.OAuth2: throw new NotImplementedException(); case PartyTypes.Oidc: return(await serviceProvider.GetService <OidcAuthUpLogic <OidcDownParty, OidcDownClient, OidcDownScope, OidcDownClaim> >().AuthenticationRequestAsync(RouteBinding.ToUpParties.First())); case PartyTypes.Saml2: return(await serviceProvider.GetService <SamlAuthnUpLogic>().AuthnRequestAsync(RouteBinding.ToUpParties.First(), await GetLoginRequestAsync(party, authenticationRequest))); default: throw new NotSupportedException($"Party type '{type}' not supported."); } } catch (OAuthRequestException ex) { logger.Error(ex); return(await AuthenticationResponseErrorAsync(partyId, authenticationRequest, ex)); } }
public async Task <string> CreateOrUpdateSessionAsync <T>(T upParty, DownPartySessionLink newDownPartyLink, List <Claim> claims, string externalSessionId, string idToken = null) where T : UpParty { logger.ScopeTrace($"Create or update session up-party, Route '{RouteBinding.Route}'."); var sessionClaims = FilterClaims(claims); Action <SessionUpPartyCookie> updateAction = (session) => { sessionClaims.AddClaim(JwtClaimTypes.SessionId, NewSessionId()); session.Claims = sessionClaims.ToClaimAndValues(); session.ExternalSessionId = externalSessionId; session.IdToken = idToken; AddDownPartyLink(session, newDownPartyLink); }; var sessionEnabled = SessionEnabled(upParty); var session = await sessionCookieRepository.GetAsync(); if (session != null) { var sessionValid = SessionValid(upParty, session); logger.ScopeTrace($"User id '{session.UserId}' session up-party exists, Enabled '{sessionEnabled}', Valid '{sessionValid}', Session id '{session.SessionId}', Route '{RouteBinding.Route}'."); if (sessionEnabled && sessionValid) { var userId = sessionClaims.FindFirstValue(c => c.Type == JwtClaimTypes.Subject); if (!session.UserId.IsNullOrEmpty() && session.UserId != userId) { logger.ScopeTrace("Authenticated user and requested user do not match."); // TODO invalid user login throw new NotImplementedException("Authenticated user and requested user do not match."); } if (session.ExternalSessionId != externalSessionId) { try { throw new Exception("External session ID has changed, causing an session update including new session ID."); } catch (Exception ex) { logger.Warning(ex); } updateAction(session); } else { AddDownPartyLink(session, newDownPartyLink); } session.LastUpdated = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); await sessionCookieRepository.SaveAsync(session, null); logger.ScopeTrace($"Session updated up-party, Session id '{session.SessionId}'.", GetSessionScopeProperties(session)); return(session.SessionId); } if (!sessionEnabled) { await sessionCookieRepository.DeleteAsync(); logger.ScopeTrace($"Session deleted, Session id '{session.SessionId}'."); } } if (sessionEnabled) { logger.ScopeTrace($"Create session up-party, External Session id '{externalSessionId}', Route '{RouteBinding.Route}'."); session = new SessionUpPartyCookie(); updateAction(session); session.LastUpdated = session.CreateTime; await sessionCookieRepository.SaveAsync(session, null); logger.ScopeTrace($"Session up-party created, User id '{session.UserId}', Session id '{session.SessionId}', External Session id '{externalSessionId}'.", GetSessionScopeProperties(session)); return(session.SessionId); } return(null); }
public async Task <IActionResult> EndSessionRequestAsync(string partyId) { logger.ScopeTrace("Down, End session request."); logger.SetScopeProperty("downPartyId", partyId); var party = await tenantRepository.GetAsync <TParty>(partyId); if (party.Client == null) { throw new NotSupportedException($"Party Client not configured."); } var endSessionRequest = HttpContext.Request.Query.ToObject <EndSessionRequest>(); logger.ScopeTrace($"end session request '{endSessionRequest.ToJsonIndented()}'."); logger.SetScopeProperty("clientId", party.Client.ClientId); ValidateEndSessionRequest(party.Client, endSessionRequest); logger.ScopeTrace("Down, OIDC End session request accepted.", triggerEvent: true); (var validIdToken, var sessionId, var idTokenClaims) = await ValidateIdTokenHintAsync(party.Client, endSessionRequest.IdTokenHint); if (!validIdToken) { if (!endSessionRequest.IdTokenHint.IsNullOrEmpty()) { throw new OAuthRequestException($"Invalid ID Token hint.") { RouteBinding = RouteBinding }; } else if (party.Client.RequireLogoutIdTokenHint) { throw new OAuthRequestException($"ID Token hint is required.") { RouteBinding = RouteBinding }; } } else { logger.ScopeTrace("Valid ID token hint."); } await oauthRefreshTokenGrantLogic.DeleteRefreshTokenGrantAsync(party.Client, sessionId); await sequenceLogic.SaveSequenceDataAsync(new OidcDownSequenceData { RedirectUri = endSessionRequest.PostLogoutRedirectUri, State = endSessionRequest.State, }); await formActionLogic.CreateFormActionByUrlAsync(endSessionRequest.PostLogoutRedirectUri); var type = RouteBinding.ToUpParties.First().Type; logger.ScopeTrace($"Request, Up type '{type}'."); switch (type) { case PartyTypes.Login: var logoutRequest = new LogoutRequest { DownParty = party, SessionId = sessionId, RequireLogoutConsent = !validIdToken, PostLogoutRedirect = !endSessionRequest.PostLogoutRedirectUri.IsNullOrWhiteSpace(), }; return(await serviceProvider.GetService <LogoutUpLogic>().LogoutRedirect(RouteBinding.ToUpParties.First(), logoutRequest)); case PartyTypes.OAuth2: throw new NotImplementedException(); case PartyTypes.Oidc: throw new NotImplementedException(); case PartyTypes.Saml2: if (!validIdToken) { throw new OAuthRequestException($"ID Token hint is required for SAML 2.0 Up Party.") { RouteBinding = RouteBinding }; } return(await serviceProvider.GetService <SamlLogoutUpLogic>().LogoutAsync(RouteBinding.ToUpParties.First(), GetSamlUpLogoutRequest(party, sessionId, idTokenClaims))); default: throw new NotSupportedException($"Party type '{type}' not supported."); } }
public async Task CheckOidcDiscoveryAndUpdatePartyAsync(TParty party) { if (party.UpdateState != PartyUpdateStates.Automatic) { return; } var lastUpdated = DateTimeOffset.FromUnixTimeSeconds(party.LastUpdated); if (lastUpdated.AddSeconds(party.OidcDiscoveryUpdateRate.Value) >= DateTimeOffset.UtcNow) { return; } var db = redisConnectionMultiplexer.GetDatabase(); var key = UpdateWaitPeriodKey(party.Id); if (await db.KeyExistsAsync(key)) { logger.ScopeTrace(() => $"Up party '{party.Id}' not updated with OIDC discovery because another update is in progress."); return; } else { await db.StringSetAsync(key, true, TimeSpan.FromSeconds(settings.UpPartyUpdateWaitPeriod)); } var failingUpdateCount = (long?)await db.StringGetAsync(FailingUpdateCountKey(party.Id)); if (failingUpdateCount.HasValue && failingUpdateCount.Value >= settings.UpPartyMaxFailingUpdate) { party.UpdateState = PartyUpdateStates.AutomaticStopped; await tenantRepository.SaveAsync(party); await db.KeyDeleteAsync(FailingUpdateCountKey(party.Id)); return; } try { try { await oidcDiscoveryReadLogic.PopulateModelAsync(party as OidcUpParty); } catch (Exception ex) { throw new EndpointException("Failed to read OIDC discovery.", ex) { RouteBinding = RouteBinding }; } await tenantRepository.SaveAsync(party); logger.ScopeTrace(() => $"Up party '{party.Id}' updated by OIDC discovery.", triggerEvent: true); await db.KeyDeleteAsync(FailingUpdateCountKey(party.Id)); } catch (Exception ex) { await db.StringIncrementAsync(FailingUpdateCountKey(party.Id)); logger.Warning(ex); } }
public async Task <IActionResult> SpMetadataAsync(string partyId) { logger.ScopeTrace(() => "Up, SP Metadata request."); logger.SetScopeProperty(Constants.Logs.UpPartyId, partyId); var party = RouteBinding.UpParty != null ? await tenantRepository.GetAsync <SamlUpParty>(partyId) : null; var signMetadata = party != null ? party.SignMetadata : false; var samlConfig = await saml2ConfigurationLogic.GetSamlUpConfigAsync(party, includeSigningAndDecryptionCertificate : signMetadata, includeSignatureValidationCertificates : false); var acsDestination = new Uri(UrlCombine.Combine(HttpContext.GetHostWithTenantAndTrack(), RouteBinding.PartyNameAndBinding, Constants.Routes.SamlController, Constants.Endpoints.SamlAcs)); var singleLogoutDestination = new Uri(UrlCombine.Combine(HttpContext.GetHostWithTenantAndTrack(), RouteBinding.PartyNameAndBinding, Constants.Routes.SamlController, Constants.Endpoints.SamlSingleLogout)); var entityDescriptor = new EntityDescriptor(samlConfig, signMetadata); if (party != null) { entityDescriptor.ValidUntil = new TimeSpan(0, 0, settings.SamlMetadataLifetime).Days; } var trackCertificates = GetTrackCertificates(); entityDescriptor.SPSsoDescriptor = new SPSsoDescriptor { //AuthnRequestsSigned = true, //WantAssertionsSigned = true, SigningCertificates = trackCertificates, AssertionConsumerServices = new AssertionConsumerService[] { new AssertionConsumerService { Binding = ToSamleBindingUri(party?.AuthnBinding?.ResponseBinding), Location = acsDestination }, }, }; entityDescriptor.SPSsoDescriptor.SingleLogoutServices = new SingleLogoutService[] { new SingleLogoutService { Binding = ToSamleBindingUri(party?.LogoutBinding?.ResponseBinding), Location = singleLogoutDestination }, }; if (party?.MetadataIncludeEncryptionCertificates == true) { entityDescriptor.SPSsoDescriptor.EncryptionCertificates = trackCertificates; entityDescriptor.SPSsoDescriptor.SetDefaultEncryptionMethods(); } if (party?.MetadataNameIdFormats?.Count > 0) { entityDescriptor.SPSsoDescriptor.NameIDFormats = party.MetadataNameIdFormats.Select(nf => new Uri(nf)); } if (party?.MetadataAttributeConsumingServices?.Count() > 0) { var attributeConsumingServices = new List <AttributeConsumingService>(); foreach (var aItem in party.MetadataAttributeConsumingServices) { var attributeConsumingService = new AttributeConsumingService { ServiceName = new ServiceName(aItem.ServiceName.Name, aItem.ServiceName.Lang) }; attributeConsumingService.RequestedAttributes = aItem.RequestedAttributes.Select(ra => string.IsNullOrEmpty(ra.NameFormat) ? new RequestedAttribute(ra.Name, ra.IsRequired) : new RequestedAttribute(ra.Name, ra.IsRequired, ra.NameFormat)); attributeConsumingServices.Add(attributeConsumingService); } entityDescriptor.SPSsoDescriptor.AttributeConsumingServices = attributeConsumingServices; } if (party?.MetadataContactPersons?.Count() > 0) { entityDescriptor.ContactPersons = GetContactPersons(party.MetadataContactPersons); } return(new Saml2Metadata(entityDescriptor).CreateMetadata().ToActionResult()); }
public async Task CreateSessionAsync(LoginUpParty loginUpParty, User user, long authTime, List <string> authMethods, string sessionId) { if (SessionEnabled(loginUpParty)) { logger.ScopeTrace($"Create session for User '{user.Email}', User id '{user.UserId}', Session id '{sessionId}', Route '{RouteBinding.Route}'."); var session = new SessionCookie { Email = user.Email, UserId = user.UserId, SessionId = sessionId }; session.CreateTime = authTime; session.LastUpdated = authTime; session.AuthMethods = authMethods; await sessionCookieRepository.SaveAsync(session, GetPersistentCookieExpires(loginUpParty, session.CreateTime)); logger.ScopeTrace($"Session created, Session id '{session.SessionId}'.", new Dictionary <string, string> { { "sessionId", session.SessionId } }); } }
public async Task <IActionResult> AuthenticationRequestRedirectAsync(UpPartyLink partyLink, LoginRequest loginRequest) { logger.ScopeTrace(() => "Up, OIDC Authentication request redirect."); var partyId = await UpParty.IdFormatAsync(RouteBinding, partyLink.Name); logger.SetScopeProperty(Constants.Logs.UpPartyId, partyId); await loginRequest.ValidateObjectAsync(); var party = await tenantRepository.GetAsync <TParty>(partyId); var oidcUpSequenceData = new OidcUpSequenceData { DownPartyLink = loginRequest.DownPartyLink, UpPartyId = partyId, LoginAction = loginRequest.LoginAction, UserId = loginRequest.UserId, MaxAge = loginRequest.MaxAge }; await sequenceLogic.SaveSequenceDataAsync(oidcUpSequenceData); return(HttpContext.GetUpPartyUrl(partyLink.Name, Constants.Routes.OAuthUpJumpController, Constants.Endpoints.UpJump.AuthenticationRequest, includeSequence: true, partyBindingPattern: party.PartyBindingPattern).ToRedirectResult()); }
public async Task SendConfirmationEmailAsync(User user) { logger.ScopeTrace($"Send confirmation email to '{user.Email}' for user id '{user.UserId}'."); if (user == null || user.DisableAccount) { throw new ConfirmationException($"User with email '{user.Email}' do not exists or is disabled."); } if (user.EmailVerified) { logger.ScopeTrace($"User is confirmed, email '{user.Email}' and id '{user.UserId}'."); return; } var db = redisConnectionMultiplexer.GetDatabase(); var key = ConfirmationEmailWaitPeriodRadisKey(user.Email); if (await db.KeyExistsAsync(key)) { logger.ScopeTrace($"User confirmation wait period, email '{user.Email}' and id '{user.UserId}'."); return; } else { await db.StringSetAsync(key, true, TimeSpan.FromSeconds(settings.ConfirmationEmailWaitPeriod)); } (var separateSequenceString, var separateSequence) = await sequenceLogic.StartSeparateSequenceAsync(accountAction : true, currentSequence : Sequence, requireeUiUpPartyId : true); await sequenceLogic.SaveSequenceDataAsync(new ConfirmationSequenceData { Email = user.Email }, separateSequence); var confirmationUrl = UrlCombine.Combine(HttpContext.GetHost(), $"{RouteBinding.TenantName}/{RouteBinding.TrackName}/({RouteBinding.UpParty.Name})/action/confirmation/_{separateSequenceString}"); await sendEmailLogic.SendEmailAsync(new MailAddress(user.Email, GetDisplayName(user)), localizer["Please confirm your email address"], localizer["<h2 style='margin-bottom:30px;font-weight:300;line-height:1.5;font-size:24px'>Please confirm your email address</h2><p style='margin-bottom:30px'>By clicking on this <a href='{0}'>link</a>, you are confirming your email address.</p>", confirmationUrl]); logger.ScopeTrace($"Confirmation send to '{user.Email}' for user id '{user.UserId}'.", triggerEvent: true); }
public async Task <IActionResult> AuthnRequestAsync(string partyId) { logger.ScopeTrace("Down, SAML Authn request."); logger.SetScopeProperty("downPartyId", partyId); var party = await tenantRepository.GetAsync <SamlDownParty>(partyId); switch (party.AuthnBinding.RequestBinding) { case SamlBindingType.Redirect: return(await AuthnRequestAsync(party, new Saml2RedirectBinding())); case SamlBindingType.Post: return(await AuthnRequestAsync(party, new Saml2PostBinding())); default: throw new NotSupportedException($"Binding '{party.AuthnBinding.RequestBinding}' not supported."); } }
public async Task CreateSessionAsync(LoginUpParty loginUpParty, DownPartySessionLink newDownPartyLink, long authTime, IEnumerable <Claim> claims) { if (SessionEnabled(loginUpParty)) { logger.ScopeTrace(() => $"Create session, Route '{RouteBinding.Route}'."); var session = new SessionLoginUpPartyCookie { Claims = claims.ToClaimAndValues(), }; AddDownPartyLink(session, newDownPartyLink); session.CreateTime = authTime; session.LastUpdated = authTime; await sessionCookieRepository.SaveAsync(loginUpParty, session, GetPersistentCookieExpires(loginUpParty, session.CreateTime)); logger.ScopeTrace(() => $"Session created, User id '{session.UserId}', Session id '{session.SessionId}'.", GetSessionScopeProperties(session)); } }
public async Task <IActionResult> LogoutRequestRedirectAsync(UpPartyLink partyLink, LogoutRequest logoutRequest) { logger.ScopeTrace(() => "Up, SAML Logout request."); var partyId = await UpParty.IdFormatAsync(RouteBinding, partyLink.Name); logger.SetScopeProperty(Constants.Logs.UpPartyId, partyId); await logoutRequest.ValidateObjectAsync(); var party = await tenantRepository.GetAsync <SamlUpParty>(partyId); await sequenceLogic.SaveSequenceDataAsync(new SamlUpSequenceData { DownPartyLink = logoutRequest.DownPartyLink, UpPartyId = partyId, SessionId = logoutRequest.SessionId, RequireLogoutConsent = logoutRequest.RequireLogoutConsent, PostLogoutRedirect = logoutRequest.PostLogoutRedirect, Claims = logoutRequest.Claims.ToClaimAndValues() }); return(HttpContext.GetUpPartyUrl(partyLink.Name, Constants.Routes.SamlUpJumpController, Constants.Endpoints.UpJump.LogoutRequest, includeSequence: true, partyBindingPattern: party.PartyBindingPattern).ToRedirectResult()); }
public async Task <IActionResult> Login() { try { logger.ScopeTrace("Start login."); var sequenceData = await sequenceLogic.GetSequenceDataAsync <LoginUpSequenceData>(remove : false); CheckUpParty(sequenceData); var loginUpParty = await tenantRepository.GetAsync <LoginUpParty>(sequenceData.UpPartyId); var session = await sessionLogic.GetAndUpdateSessionCheckUserAsync(loginUpParty, GetDownPartyLink(loginUpParty, sequenceData)); var validSession = ValidSession(sequenceData, session); if (validSession && sequenceData.LoginAction != LoginAction.RequireLogin) { return(await loginUpLogic.LoginResponseAsync(session.Claims.ToClaimList())); } if (sequenceData.LoginAction == LoginAction.ReadSession) { return(await loginUpLogic.LoginResponseErrorAsync(sequenceData, LoginSequenceError.LoginRequired)); } else { logger.ScopeTrace("Show login dialog."); return(View(nameof(Login), new LoginViewModel { SequenceString = SequenceString, CssStyle = loginUpParty.CssStyle, EnableCancelLogin = loginUpParty.EnableCancelLogin, EnableResetPassword = !loginUpParty.DisableResetPassword, EnableCreateUser = !validSession && loginUpParty.EnableCreateUser, Email = sequenceData.Email.IsNullOrWhiteSpace() ? string.Empty : sequenceData.Email, })); } } catch (Exception ex) { throw new EndpointException($"Login failed, Name '{RouteBinding.UpParty.Name}'.", ex) { RouteBinding = RouteBinding }; } }
public async Task <IActionResult> FrontChannelLogoutAsync(string partyId) { logger.ScopeTrace(() => "Up, OIDC Front channel logout."); logger.SetScopeProperty(Constants.Logs.UpPartyId, partyId); var party = await tenantRepository.GetAsync <OidcUpParty>(partyId); logger.SetScopeProperty(Constants.Logs.UpPartyClientId, party.Client.ClientId); if (party.Client.DisableFrontChannelLogout) { return(new BadRequestResult()); } var queryDictionary = HttpContext.Request.Query.ToDictionary(); var frontChannelLogoutRequest = queryDictionary.ToObject <FrontChannelLogoutRequest>(); logger.ScopeTrace(() => $"Up, Front channel logout request '{frontChannelLogoutRequest.ToJsonIndented()}'.", traceType: TraceTypes.Message); frontChannelLogoutRequest.Validate(); if (party.Client.FrontChannelLogoutSessionRequired) { if (frontChannelLogoutRequest.SessionId.IsNullOrEmpty()) { throw new ArgumentNullException(nameof(frontChannelLogoutRequest.SessionId), frontChannelLogoutRequest.GetTypeName()); } } var session = await sessionUpPartyLogic.GetSessionAsync(party); logger.ScopeTrace(() => "Up, Successful OIDC Front channel logout request.", triggerEvent: true); if (session != null) { if (party.Client.FrontChannelLogoutSessionRequired) { if (!party.Issuers.Where(i => i == frontChannelLogoutRequest.Issuer).Any()) { throw new Exception("Incorrect issuer."); } if (session.ExternalSessionId != frontChannelLogoutRequest.SessionId) { throw new Exception("Incorrect session id."); } } var _ = await sessionUpPartyLogic.DeleteSessionAsync(party, session); await oauthRefreshTokenGrantLogic.DeleteRefreshTokenGrantsAsync(session.SessionId); if (!party.DisableSingleLogout) { var allowIframeOnDomains = new List <string>().ConcatOnce(party.Client.AuthorizeUrl?.UrlToDomain()).ConcatOnce(party.Client.EndSessionUrl?.UrlToDomain()).ConcatOnce(party.Client.TokenUrl?.UrlToDomain()); (var doSingleLogout, var singleLogoutSequenceData) = await singleLogoutDownLogic.InitializeSingleLogoutAsync(new UpPartyLink { Name = party.Name, Type = party.Type }, null, session.DownPartyLinks, session.Claims, allowIframeOnDomains, hostedInIframe : true); if (doSingleLogout) { return(await singleLogoutDownLogic.StartSingleLogoutAsync(singleLogoutSequenceData)); } } } return(new OkResult()); }