public async Task PopulateModelAsync(OidcUpParty party)
        {
            (var oidcDiscovery, var jsonWebKeySet) = await GetOidcDiscoveryAndValidateAsync(party.Authority);

            party.LastUpdated = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
            if (party.EditIssuersInAutomatic != true || string.IsNullOrWhiteSpace(party.Issuers?.FirstOrDefault()))
            {
                party.Issuers = new List <string> {
                    oidcDiscovery.Issuer
                };
            }
            party.Client.AuthorizeUrl = oidcDiscovery.AuthorizationEndpoint;
            if (!oidcDiscovery.TokenEndpoint.IsNullOrEmpty())
            {
                party.Client.TokenUrl = oidcDiscovery.TokenEndpoint;
            }
            else if (party.Client.ResponseType?.Contains(IdentityConstants.ResponseTypes.Code) == true)
            {
                party.Client.ResponseType = $"{IdentityConstants.ResponseTypes.Token} {IdentityConstants.ResponseTypes.IdToken}";
                party.Client.EnablePkce   = false;
            }
            if (!oidcDiscovery.EndSessionEndpoint.IsNullOrEmpty())
            {
                party.Client.EndSessionUrl = oidcDiscovery.EndSessionEndpoint;
            }
            party.Keys = jsonWebKeySet.Keys?.ToList();
        }
Example #2
0
        private List <Claim> ValidateAccessToken(OidcUpParty party, OidcUpSequenceData sequenceData, string accessToken)
        {
            try
            {
                var jwtToken = JwtHandler.ReadToken(accessToken);
                var issuer   = party.Issuers.Where(i => i == jwtToken.Issuer).FirstOrDefault();
                if (string.IsNullOrEmpty(issuer))
                {
                    throw new OAuthRequestException($"{party.Name}|Access token issuer '{jwtToken.Issuer}' is unknown.")
                          {
                              RouteBinding = RouteBinding, Error = IdentityConstants.ResponseErrors.InvalidToken
                          };
                }


                (var claimsPrincipal, _) = JwtHandler.ValidateToken(accessToken, issuer, party.Keys, sequenceData.ClientId, validateAudience: false);
                return(claimsPrincipal.Claims.ToList());
            }
            catch (OAuthRequestException)
            {
                throw;
            }
            catch (Exception ex)
            {
                throw new OAuthRequestException($"{party.Name}|Access token not valid.", ex)
                      {
                          RouteBinding = RouteBinding, Error = IdentityConstants.ResponseErrors.InvalidToken
                      };
            }
        }
        public async Task <bool> PopulateModelAsync(ModelStateDictionary modelState, OidcUpParty mp)
        {
            var isValid = true;

            try
            {
                if (mp.UpdateState != PartyUpdateStates.Manual)
                {
                    await oidcDiscoveryReadLogic.PopulateModelAsync(mp);

                    if (mp.UpdateState == PartyUpdateStates.AutomaticStopped)
                    {
                        mp.UpdateState = PartyUpdateStates.Automatic;
                    }

                    if (mp.EditIssuersInAutomatic == false)
                    {
                        mp.EditIssuersInAutomatic = null;
                    }
                }
                else
                {
                    mp.EditIssuersInAutomatic = null;
                }
            }
            catch (Exception ex)
            {
                isValid = false;
                logger.Warning(ex);
                modelState.TryAddModelError(nameof(mp.Authority).ToCamelCase(), ex.GetAllMessagesJoined());
            }
            return(isValid);
        }
 private void ValidatePartyLogoutSupport(OidcUpParty party)
 {
     if (party.Client.EndSessionUrl.IsNullOrEmpty())
     {
         throw new EndpointException("End session not configured.")
               {
                   RouteBinding = RouteBinding
               };
     }
 }
Example #5
0
        private async Task <List <Claim> > ValidateIdTokenAsync(OidcUpParty party, OidcUpSequenceData sequenceData, string idToken, string accessToken, bool authorizationEndpoint)
        {
            try
            {
                var jwtToken = JwtHandler.ReadToken(idToken);
                var issuer   = party.Issuers.Where(i => i == jwtToken.Issuer).FirstOrDefault();
                if (string.IsNullOrEmpty(issuer))
                {
                    throw new OAuthRequestException($"{party.Name}|Id token issuer '{jwtToken.Issuer}' is unknown.")
                          {
                              RouteBinding = RouteBinding, Error = IdentityConstants.ResponseErrors.InvalidToken
                          };
                }

                (var claimsPrincipal, _) = JwtHandler.ValidateToken(idToken, issuer, party.Keys, sequenceData.ClientId);

                var nonce = claimsPrincipal.Claims.FindFirstValue(c => c.Type == JwtClaimTypes.Nonce);
                if (!sequenceData.Nonce.Equals(nonce, StringComparison.Ordinal))
                {
                    throw new OAuthRequestException($"{party.Name}|Id token nonce do not match.")
                          {
                              RouteBinding = RouteBinding, Error = IdentityConstants.ResponseErrors.InvalidToken
                          };
                }

                if (authorizationEndpoint && !accessToken.IsNullOrEmpty())
                {
                    var    atHash    = claimsPrincipal.Claims.FindFirstValue(c => c.Type == JwtClaimTypes.AtHash);
                    string algorithm = IdentityConstants.Algorithms.Asymmetric.RS256;
                    if (atHash != await accessToken.LeftMostBase64urlEncodedHashAsync(algorithm))
                    {
                        throw new OAuthRequestException($"{party.Name}|Access Token hash claim in ID token do not match the access token.")
                              {
                                  RouteBinding = RouteBinding, Error = IdentityConstants.ResponseErrors.InvalidToken
                              };
                    }
                }

                return(claimsPrincipal.Claims.ToList());
            }
            catch (OAuthRequestException)
            {
                throw;
            }
            catch (Exception ex)
            {
                throw new OAuthRequestException($"{party.Name}|Id token not valid.", ex)
                      {
                          RouteBinding = RouteBinding, Error = IdentityConstants.ResponseErrors.InvalidToken
                      };
            }
        }
Example #6
0
        private async Task CreateIdentityserverOidcOpUpPartyAsync()
        {
            Func <string, Task> getAction = async(name) =>
            {
                _ = await foxIDsApiClient.GetOidcUpPartyAsync(name);
            };

            Func <string, Task> postAction = async(name) =>
            {
                var baseUrl = "https://localhost:44346";

                var key = File.ReadAllText("identityserver-tempkey.jwk").ToObject <JsonWebKey>();

                var oidcUpParty = new OidcUpParty
                {
                    Name        = name,
                    Authority   = baseUrl,
                    UpdateState = PartyUpdateStates.Manual,
                    Issuers     = new string[] { baseUrl },
                    Keys        = new JsonWebKey[] { key },

                    Client = new OidcUpClient
                    {
                        AuthorizeUrl  = $"{baseUrl}/connect/authorize",
                        TokenUrl      = $"{baseUrl}/connect/token",
                        EndSessionUrl = $"{baseUrl}/connect/endsession",

                        // A less secure configuration to enable local testing
                        ResponseType = IdentityConstants.ResponseTypes.IdToken,

                        // A more secure configuration
                        //ResponseType = IdentityConstants.ResponseTypes.Code,
                        //EnablePkce = true,
                        //ClientSecret = "2tqjW-KwiGaR4KRt0IJ8KAJYw3pyPTK8S_dr_YE5nbw",

                        Scopes = new string[] { "profile", "email" },
                        Claims = new string[] { "access_token", JwtClaimTypes.Email, JwtClaimTypes.EmailVerified, JwtClaimTypes.FamilyName, JwtClaimTypes.GivenName, JwtClaimTypes.Name, JwtClaimTypes.Role },

                        UseIdTokenClaims = true,

                        ResponseMode = IdentityConstants.ResponseModes.FormPost
                    }
                };

                await foxIDsApiClient.PostOidcUpPartyAsync(oidcUpParty);
            };

            await CreateIfNotExistsAsync(identityserverOidcOpUpPartyName, getAction, postAction);
        }
        public async Task <bool> PopulateModelAsync(ModelStateDictionary modelState, OidcUpParty mp)
        {
            var isValid = true;

            try
            {
                if (mp.UpdateState != PartyUpdateStates.Manual)
                {
                    (var oidcDiscovery, var jsonWebKeySet) = await oidcDiscoveryReadLogic.GetOidcDiscoveryAndValidateAsync(mp.Authority);

                    mp.LastUpdated = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
                    if (mp.EditIssuersInAutomatic != true || string.IsNullOrWhiteSpace(mp.Issuers?.FirstOrDefault()))
                    {
                        mp.Issuers = new List <string> {
                            oidcDiscovery.Issuer
                        };
                    }
                    mp.Client.AuthorizeUrl = oidcDiscovery.AuthorizationEndpoint;
                    mp.Client.TokenUrl     = oidcDiscovery.TokenEndpoint;
                    if (!oidcDiscovery.EndSessionEndpoint.IsNullOrEmpty())
                    {
                        mp.Client.EndSessionUrl = oidcDiscovery.EndSessionEndpoint;
                    }
                    mp.Keys = jsonWebKeySet.Keys?.ToList();

                    if (mp.UpdateState == PartyUpdateStates.AutomaticStopped)
                    {
                        mp.UpdateState = PartyUpdateStates.Automatic;
                    }

                    if (mp.EditIssuersInAutomatic == false)
                    {
                        mp.EditIssuersInAutomatic = null;
                    }
                }
                else
                {
                    mp.EditIssuersInAutomatic = null;
                }
            }
            catch (Exception ex)
            {
                isValid = false;
                logger.Warning(ex);
                modelState.TryAddModelError(nameof(mp.Authority).ToCamelCase(), ex.GetAllMessagesJoined());
            }
            return(isValid);
        }
Example #8
0
        private void ValidateAuthenticationResponse(OidcUpParty party, AuthenticationResponse authenticationResponse, SessionResponse sessionResponse, bool isImplicitFlow)
        {
            authenticationResponse.Validate(isImplicitFlow);

            if (party.Client.EnablePkce && isImplicitFlow)
            {
                throw new OAuthRequestException($"Require '{IdentityConstants.ResponseTypes.Code}' flow with PKCE.")
                      {
                          RouteBinding = RouteBinding, Error = IdentityConstants.ResponseErrors.InvalidRequest
                      };
            }

            if (sessionResponse?.SessionState.IsNullOrEmpty() == false)
            {
                sessionResponse.Validate();
            }
        }
        private async Task <IActionResult> EndSessionResponseAsync(OidcUpParty party)
        {
            var queryDictionary           = HttpContext.Request.Query.ToDictionary();
            var rpInitiatedLogoutResponse = queryDictionary.ToObject <RpInitiatedLogoutResponse>();

            logger.ScopeTrace(() => $"Up, End session response '{rpInitiatedLogoutResponse.ToJsonIndented()}'.", traceType: TraceTypes.Message);
            rpInitiatedLogoutResponse.Validate();
            if (rpInitiatedLogoutResponse.State.IsNullOrEmpty())
            {
                throw new ArgumentNullException(nameof(rpInitiatedLogoutResponse.State), rpInitiatedLogoutResponse.GetTypeName());
            }

            await sequenceLogic.ValidateExternalSequenceIdAsync(rpInitiatedLogoutResponse.State);

            var sequenceData = await sequenceLogic.GetSequenceDataAsync <OidcUpSequenceData>(remove : party.DisableSingleLogout);

            logger.ScopeTrace(() => "Up, Successful OIDC End session response.", triggerEvent: true);

            if (party.DisableSingleLogout)
            {
                return(await LogoutResponseDownAsync(sequenceData));
            }
            else
            {
                (var doSingleLogout, var singleLogoutSequenceData) = await singleLogoutDownLogic.InitializeSingleLogoutAsync(new UpPartyLink { Name = party.Name, Type = party.Type }, sequenceData.DownPartyLink, sequenceData.SessionDownPartyLinks, sequenceData.SessionClaims);

                if (doSingleLogout)
                {
                    return(await singleLogoutDownLogic.StartSingleLogoutAsync(singleLogoutSequenceData));
                }
                else
                {
                    await sequenceLogic.RemoveSequenceDataAsync <OidcUpSequenceData>();

                    return(await LogoutResponseDownAsync(sequenceData));
                }
            }
        }
Example #10
0
        private List <Claim> ValidateClaims(OidcUpParty party, List <Claim> claims)
        {
            var acceptedClaims = Constants.DefaultClaims.JwtTokenUpParty.ConcatOnce(party.Client.Claims).Where(c => !Constants.DefaultClaims.ExcludeJwtTokenUpParty.Contains(c));

            claims = claims.Where(c => acceptedClaims.Any(ic => ic == c.Type)).ToList();
            foreach (var claim in claims)
            {
                if (claim.Type?.Length > Constants.Models.Claim.JwtTypeLength)
                {
                    throw new OAuthRequestException($"Claim '{claim.Type.Substring(0, Constants.Models.Claim.JwtTypeLength)}' is too long, maximum length of '{Constants.Models.Claim.JwtTypeLength}'.")
                          {
                              RouteBinding = RouteBinding, Error = IdentityConstants.ResponseErrors.InvalidToken
                          };
                }

                if (Constants.EmbeddedJwtToken.JwtTokenClaims.Contains(claim.Type))
                {
                    if (claim.Value?.Length > Constants.EmbeddedJwtToken.ValueLength)
                    {
                        throw new OAuthRequestException($"Claim '{claim.Type}' value is too long, maximum length of '{Constants.EmbeddedJwtToken.ValueLength}'.")
                              {
                                  RouteBinding = RouteBinding, Error = IdentityConstants.ResponseErrors.InvalidToken
                              };
                    }
                }
                else
                {
                    if (claim.Value?.Length > Constants.Models.Claim.ValueLength)
                    {
                        throw new OAuthRequestException($"Claim '{claim.Type}' value is too long, maximum length of '{Constants.Models.Claim.ValueLength}'.")
                              {
                                  RouteBinding = RouteBinding, Error = IdentityConstants.ResponseErrors.InvalidToken
                              };
                    }
                }
            }
            return(claims);
        }
Example #11
0
        private async Task <(List <Claim>, string)> ValidateTokensAsync(OidcUpParty party, OidcUpSequenceData sequenceData, string idToken, string accessToken, bool authorizationEndpoint)
        {
            var claims = await ValidateIdTokenAsync(party, sequenceData, idToken, accessToken, authorizationEndpoint);

            if (!accessToken.IsNullOrWhiteSpace())
            {
                if (!party.Client.UseIdTokenClaims)
                {
                    // If access token exists, use access token claims instead of ID token claims.
                    claims = ValidateAccessToken(party, sequenceData, accessToken);
                }
                claims.AddClaim(Constants.JwtClaimTypes.AccessToken, $"{party.Name}|{accessToken}");
            }

            var subject = claims.FindFirstValue(c => c.Type == JwtClaimTypes.Subject);

            if (!subject.IsNullOrEmpty())
            {
                claims = claims.Where(c => c.Type != JwtClaimTypes.Subject).ToList();
                claims.Add(new Claim(JwtClaimTypes.Subject, $"{party.Name}|{subject}"));
            }

            return(await Task.FromResult((claims, idToken)));
        }
Example #12
0
        public async Task CheckOidcDiscoveryAndUpdatePartyAsync(OidcUpParty 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 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
                {
                    (var oidcDiscovery, var jsonWebKeySet) = await oidcDiscoveryReadLogic.GetOidcDiscoveryAndValidateAsync(party.Authority);

                    party.LastUpdated = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
                    party.Issuers     = new List <string> {
                        oidcDiscovery.Issuer
                    };
                    party.Client.AuthorizeUrl = oidcDiscovery.AuthorizationEndpoint;
                    party.Client.TokenUrl     = oidcDiscovery.TokenEndpoint;
                    if (!oidcDiscovery.EndSessionEndpoint.IsNullOrEmpty())
                    {
                        party.Client.EndSessionUrl = oidcDiscovery.EndSessionEndpoint;
                    }
                    party.Keys = jsonWebKeySet.Keys?.ToList();
                }
                catch (Exception ex)
                {
                    throw new EndpointException("Failing 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);
            }
        }
Example #13
0
        private OidcUpPartyViewModel ToViewModel(GeneralOidcUpPartyViewModel generalOidcUpParty, OidcUpParty oidcUpParty)
        {
            return(oidcUpParty.Map <OidcUpPartyViewModel>(afterMap =>
            {
                if (oidcUpParty.UpdateState == PartyUpdateStates.Manual)
                {
                    afterMap.IsManual = true;
                }

                if (oidcUpParty.UpdateState == PartyUpdateStates.AutomaticStopped)
                {
                    afterMap.AutomaticStopped = true;
                }
                else
                {
                    afterMap.AutomaticStopped = false;
                }

                afterMap.EnableSingleLogout = !oidcUpParty.DisableSingleLogout;
                if (oidcUpParty.Client != null)
                {
                    afterMap.Client.EnableFrontChannelLogout = !oidcUpParty.Client.DisableFrontChannelLogout;
                }

                generalOidcUpParty.KeyInfoList.Clear();
                foreach (var key in afterMap.Keys)
                {
                    if (key.Kty == MTokens.JsonWebAlgorithmsKeyTypes.RSA && key.X5c?.Count >= 1)
                    {
                        generalOidcUpParty.KeyInfoList.Add(new KeyInfoViewModel
                        {
                            Subject = key.CertificateInfo.Subject,
                            ValidFrom = key.CertificateInfo.ValidFrom,
                            ValidTo = key.CertificateInfo.ValidTo,
                            IsValid = key.CertificateInfo.IsValid(),
                            Thumbprint = key.CertificateInfo.Thumbprint,
                            KeyId = key.Kid,
                            Key = key
                        });
                    }
                    else
                    {
                        generalOidcUpParty.KeyInfoList.Add(new KeyInfoViewModel
                        {
                            KeyId = key.Kid,
                            Key = key
                        });
                    }
                }

                if (afterMap.ClaimTransforms?.Count > 0)
                {
                    afterMap.ClaimTransforms = afterMap.ClaimTransforms.MapClaimTransforms();
                }
            }));
        }
Example #14
0
        private async Task <(List <Claim>, string)> HandleAuthorizationCodeResponseAsync(OidcUpParty party, OidcUpSequenceData sequenceData, string code)
        {
            var tokenResponse = await TokenRequestAsync(party.Client, code, sequenceData);

            return(await ValidateTokensAsync(party, sequenceData, tokenResponse.IdToken, tokenResponse.AccessToken, false));
        }
Example #15
0
 public async Task CreateOidcUpPartyAsync(OidcUpParty party) => await PostAsync(oidcApiUri, party);
Example #16
0
 public async Task UpdateOidcUpPartyAsync(OidcUpParty party) => await PutAsync(oidcApiUri, party);
Example #17
0
 public async Task <OidcUpParty> UpdateOidcUpPartyAsync(OidcUpParty party) => await PutResponseAsync <OidcUpParty, OidcUpParty>(oidcApiUri, party);