Esempio n. 1
0
            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.");
            }
Esempio n. 2
0
        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}"));
        }
Esempio n. 3
0
        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
                      };
            }
        }
Esempio n. 4
0
        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());
        }
Esempio n. 5
0
        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);
        }
Esempio n. 6
0
        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
                      };
            }
        }
Esempio n. 7
0
        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());
        }
Esempio n. 9
0
        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
                      };
            }
        }
Esempio n. 10
0
        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);
        }
Esempio n. 11
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);
        }
Esempio n. 12
0
        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
                      };
            }
        }
Esempio n. 13
0
        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
                      };
            }
        }
Esempio n. 14
0
        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));
        }
Esempio n. 15
0
        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.");
            }
        }
Esempio n. 17
0
        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
                      };
            }
        }
Esempio n. 18
0
        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));
            }
        }
Esempio n. 19
0
        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);
        }
Esempio n. 20
0
        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.");
            }
        }
Esempio n. 21
0
        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);
            }
        }
Esempio n. 22
0
        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());
        }
Esempio n. 23
0
        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 }
                });
            }
        }
Esempio n. 24
0
        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());
        }
Esempio n. 25
0
        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);
        }
Esempio n. 26
0
        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.");
            }
        }
Esempio n. 27
0
        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));
            }
        }
Esempio n. 28
0
        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());
        }
Esempio n. 29
0
        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());
        }