Example #1
0
        private async Task <IActionResult> SingleLogoutRequestAsync <T>(SamlDownParty party, Saml2Binding <T> binding, IEnumerable <Claim> claims)
        {
            var samlConfig = saml2ConfigurationLogic.GetSamlDownConfig(party, true);

            claims = await claimTransformationsLogic.Transform(party.ClaimTransforms?.ConvertAll(t => (ClaimTransform)t), claims);

            var saml2LogoutRequest = new Saml2LogoutRequest(samlConfig)
            {
                NameId       = samlClaimsDownLogic.GetNameId(claims),
                Destination  = new Uri(party.SingleLogoutUrl),
                SessionIndex = samlClaimsDownLogic.GetSessionIndex(claims)
            };

            binding.RelayState = SequenceString;
            binding.Bind(saml2LogoutRequest);
            logger.ScopeTrace($"SAML Single Logout request '{saml2LogoutRequest.XmlDocument.OuterXml}'.");
            logger.ScopeTrace($"Single logged out URL '{party.SingleLogoutUrl}'.");
            logger.ScopeTrace("Down, SAML Single Logout request.", triggerEvent: true);

            securityHeaderLogic.AddFormAction(party.SingleLogoutUrl);
            if (binding is Saml2Binding <Saml2RedirectBinding> )
            {
                return(await(binding as Saml2RedirectBinding).ToActionFormResultAsync());
            }
            if (binding is Saml2Binding <Saml2PostBinding> )
            {
                return(await(binding as Saml2PostBinding).ToActionFormResultAsync());
            }
            else
            {
                throw new NotSupportedException();
            }
        }
Example #2
0
        private async Task <IActionResult> AuthnResponseAsync <T>(Saml2Configuration samlConfig, string inResponseTo, string relayState, string acsUrl, Saml2Binding <T> binding, Saml2StatusCodes status, SamlDownParty party = null, IEnumerable <Claim> claims = null)
        {
            binding.RelayState = relayState;

            var saml2AuthnResponse = new FoxIdsSaml2AuthnResponse(settings, samlConfig)
            {
                InResponseTo = new Saml2Id(inResponseTo),
                Status       = status,
                Destination  = new Uri(acsUrl),
            };

            if (status == Saml2StatusCodes.Success && party != null && claims != null)
            {
                claims = await claimTransformationsLogic.Transform(party.ClaimTransformations?.ConvertAll(t => (ClaimTransformation)t), claims);

                saml2AuthnResponse.SessionIndex = claims.FindFirstValue(c => c.Type == Saml2ClaimTypes.SessionIndex);

                saml2AuthnResponse.NameId = GetNameId(claims);

                var tokenIssueTime  = DateTimeOffset.UtcNow;
                var tokenDescriptor = saml2AuthnResponse.CreateTokenDescriptor(GetSubjectClaims(party, claims), party.Issuer, tokenIssueTime, party.IssuedTokenLifetime);

                var authnContext            = claims.FindFirstValue(c => c.Type == ClaimTypes.AuthenticationMethod);
                var authenticationInstant   = claims.FindFirstValue(c => c.Type == ClaimTypes.AuthenticationInstant);
                var authenticationStatement = saml2AuthnResponse.CreateAuthenticationStatement(authnContext, DateTime.Parse(authenticationInstant));

                var subjectConfirmation = saml2AuthnResponse.CreateSubjectConfirmation(tokenIssueTime, party.SubjectConfirmationLifetime);

                await saml2AuthnResponse.CreateSecurityTokenAsync(tokenDescriptor, authenticationStatement, subjectConfirmation);
            }

            binding.Bind(saml2AuthnResponse);
            logger.ScopeTrace($"SAML Authn response '{saml2AuthnResponse.XmlDocument.OuterXml}'.");
            logger.ScopeTrace($"Acs url '{acsUrl}'.");
            logger.ScopeTrace("Down, SAML Authn response.", triggerEvent: true);

            await sequenceLogic.RemoveSequenceDataAsync <SamlDownSequenceData>();

            await formActionLogic.RemoveFormActionSequenceDataAsync();

            if (binding is Saml2Binding <Saml2RedirectBinding> )
            {
                return(await Task.FromResult((binding as Saml2RedirectBinding).ToActionResult()));
            }
            else if (binding is Saml2Binding <Saml2PostBinding> )
            {
                return(await Task.FromResult((binding as Saml2PostBinding).ToActionResult()));
            }
            else
            {
                throw new NotSupportedException();
            }
        }
Example #3
0
        public async Task <IActionResult> LoginResponseAsync(LoginUpParty party, User user, long authTime, IEnumerable <string> authMethods, string sessionId)
        {
            logger.ScopeTrace("Up, Login response.");

            var sequenceData = await sequenceLogic.GetSequenceDataAsync <LoginUpSequenceData>();

            logger.SetScopeProperty("upPartyId", sequenceData.UpPartyId);

            var claims = new List <Claim>();

            claims.AddClaim(JwtClaimTypes.Subject, user.UserId);
            claims.AddClaim(JwtClaimTypes.AuthTime, authTime.ToString());
            claims.AddRange(authMethods.Select(am => new Claim(JwtClaimTypes.Amr, am)));
            claims.AddClaim(JwtClaimTypes.SessionId, sessionId);
            claims.AddClaim(JwtClaimTypes.PreferredUsername, user.Email);
            claims.AddClaim(JwtClaimTypes.Email, user.Email);
            if (user.Claims?.Count() > 0)
            {
                claims.AddRange(user.Claims.ToClaimList());
            }

            claims = await claimTransformationsLogic.Transform(party.ClaimTransformations?.ConvertAll(t => (ClaimTransformation)t), claims);

            logger.ScopeTrace($"Response, Down type {sequenceData.DownPartyType}.");
            switch (sequenceData.DownPartyType)
            {
            case PartyTypes.OAuth2:
                throw new NotImplementedException();

            case PartyTypes.Oidc:
                return(await serviceProvider.GetService <OidcAuthDownLogic <OidcDownParty, OidcDownClient, OidcDownScope, OidcDownClaim> >().AuthenticationResponseAsync(sequenceData.DownPartyId, claims));

            case PartyTypes.Saml2:
                var claimsLogic = serviceProvider.GetService <ClaimsLogic <OAuthDownClient, OAuthDownScope, OAuthDownClaim> >();
                var samlClaims  = await claimsLogic.FromJwtToSamlClaims(claims);

                samlClaims.AddClaim(Saml2ClaimTypes.NameIdFormat, NameIdentifierFormats.Email.OriginalString);
                return(await serviceProvider.GetService <SamlAuthnDownLogic>().AuthnResponseAsync(sequenceData.DownPartyId, claims: samlClaims));

            default:
                throw new NotSupportedException();
            }
        }
Example #4
0
        public async Task <IActionResult> AuthenticationResponseAsync(string partyId, List <Claim> claims)
        {
            logger.ScopeTrace("Down, OIDC Authentication response.");
            logger.SetScopeProperty("downPartyId", partyId);
            var party = await tenantRepository.GetAsync <TParty>(partyId);

            if (party.Client == null)
            {
                throw new NotSupportedException($"Party Client not configured.");
            }

            var sequenceData = await sequenceLogic.GetSequenceDataAsync <OidcDownSequenceData>(false);

            claims = await claimTransformationsLogic.Transform(party.ClaimTransformations?.ConvertAll(t => (ClaimTransformation)t), claims);

            var authenticationResponse = new AuthenticationResponse
            {
                TokenType = IdentityConstants.TokenTypes.Bearer,
                State     = sequenceData.State,
                ExpiresIn = party.Client.AccessTokenLifetime,
            };
            var sessionResponse = new SessionResponse
            {
                SessionState = claims.FindFirstValue(c => c.Type == JwtClaimTypes.SessionId)
            };

            logger.ScopeTrace($"Response type '{sequenceData.ResponseType}'.");
            var responseTypes = sequenceData.ResponseType.ToSpaceList();

            if (responseTypes.Contains(IdentityConstants.ResponseTypes.Code))
            {
                authenticationResponse.Code = await oauthAuthCodeGrantLogic.CreateAuthCodeGrantAsync(party.Client as TClient, claims, sequenceData.RedirectUri, sequenceData.Scope, sequenceData.Nonce, sequenceData.CodeChallenge, sequenceData.CodeChallengeMethod);
            }

            string algorithm = IdentityConstants.Algorithms.Asymmetric.RS256;

            if (responseTypes.Contains(IdentityConstants.ResponseTypes.Token))
            {
                authenticationResponse.AccessToken = await jwtLogic.CreateAccessTokenAsync(party.Client as TClient, claims, sequenceData.Scope?.ToSpaceList(), algorithm);
            }
            if (responseTypes.Contains(IdentityConstants.ResponseTypes.IdToken))
            {
                authenticationResponse.IdToken = await jwtLogic.CreateIdTokenAsync(party.Client as TClient, claims, sequenceData.Scope?.ToSpaceList(), sequenceData.Nonce, responseTypes, authenticationResponse.Code, authenticationResponse.AccessToken, algorithm);
            }

            logger.ScopeTrace($"Authentication response '{authenticationResponse.ToJsonIndented()}'.");
            var nameValueCollection = authenticationResponse.ToDictionary();

            if (!sessionResponse.SessionState.IsNullOrWhiteSpace())
            {
                logger.ScopeTrace($"Session response '{sessionResponse.ToJsonIndented()}'.");
                nameValueCollection = nameValueCollection.AddToDictionary(sessionResponse);
            }

            logger.ScopeTrace($"Redirect Uri '{sequenceData.RedirectUri}'.");
            logger.ScopeTrace("Down, OIDC Authentication response.", triggerEvent: true);

            var responseMode = GetResponseMode(sequenceData.ResponseMode, sequenceData.ResponseType);
            await sequenceLogic.RemoveSequenceDataAsync <OidcDownSequenceData>();

            await formActionLogic.RemoveFormActionSequenceDataAsync();

            switch (responseMode)
            {
            case IdentityConstants.ResponseModes.FormPost:
                return(await nameValueCollection.ToHtmlPostContentResultAsync(sequenceData.RedirectUri));

            case IdentityConstants.ResponseModes.Query:
                return(await nameValueCollection.ToRedirectResultAsync(sequenceData.RedirectUri));

            case IdentityConstants.ResponseModes.Fragment:
                return(await nameValueCollection.ToFragmentResultAsync(sequenceData.RedirectUri));

            default:
                throw new NotSupportedException();
            }
        }
Example #5
0
        private async Task <IActionResult> AuthnResponseAsync <T>(SamlUpParty party, Saml2Binding <T> binding)
        {
            var request    = HttpContext.Request;
            var samlConfig = saml2ConfigurationLogic.GetSamlUpConfig(party);

            var saml2AuthnResponse = new Saml2AuthnResponse(samlConfig);

            binding.ReadSamlResponse(request.ToGenericHttpRequest(), saml2AuthnResponse);

            await sequenceLogic.ValidateSequenceAsync(binding.RelayState);

            var sequenceData = await sequenceLogic.GetSequenceDataAsync <SamlUpSequenceData>();

            try
            {
                logger.ScopeTrace($"SAML Authn response '{saml2AuthnResponse.XmlDocument.OuterXml}'.");
                logger.SetScopeProperty("status", saml2AuthnResponse.Status.ToString());
                logger.ScopeTrace("Up, SAML Authn response.", triggerEvent: true);

                if (saml2AuthnResponse.Status != Saml2StatusCodes.Success)
                {
                    throw new SamlRequestException("Unsuccessful Authn response.")
                          {
                              RouteBinding = RouteBinding, Status = saml2AuthnResponse.Status
                          };
                }

                binding.Unbind(request.ToGenericHttpRequest(), saml2AuthnResponse);
                logger.ScopeTrace("Up, Successful SAML Authn response.", triggerEvent: true);

                var claims = saml2AuthnResponse.ClaimsIdentity?.Claims;
                if (saml2AuthnResponse.ClaimsIdentity?.Claims?.Count() <= 0)
                {
                    throw new SamlRequestException("Empty claims collection.")
                          {
                              RouteBinding = RouteBinding, Status = Saml2StatusCodes.Responder
                          };
                }

                if (!claims.Any(c => c.Type == ClaimTypes.NameIdentifier))
                {
                    claims = AddNameIdClaim(claims);
                }

                claims = await claimTransformationsLogic.Transform(party.ClaimTransformations?.ConvertAll(t => (ClaimTransformation)t), claims);

                claims = ValidateClaims(party, claims);

                return(await AuthnResponseDownAsync(sequenceData, saml2AuthnResponse.Status, claims));
            }
            catch (SamlRequestException ex)
            {
                logger.Error(ex);
                return(await AuthnResponseDownAsync(sequenceData, ex.Status));
            }
            catch (Exception ex)
            {
                logger.Error(ex);
                return(await AuthnResponseDownAsync(sequenceData, Saml2StatusCodes.Responder));
            }
        }
Example #6
0
        private async Task <IActionResult> AuthnResponseAsync <T>(SamlUpParty party, Saml2Binding <T> binding)
        {
            var request    = HttpContext.Request;
            var samlConfig = saml2ConfigurationLogic.GetSamlUpConfig(party);

            var saml2AuthnResponse = new Saml2AuthnResponse(samlConfig);

            binding.ReadSamlResponse(request.ToGenericHttpRequest(), saml2AuthnResponse);
            if (binding.RelayState.IsNullOrEmpty())
            {
                throw new ArgumentNullException(nameof(binding.RelayState), binding.GetTypeName());
            }

            await sequenceLogic.ValidateSequenceAsync(binding.RelayState);

            var sequenceData = await sequenceLogic.GetSequenceDataAsync <SamlUpSequenceData>();

            try
            {
                logger.ScopeTrace($"SAML Authn response '{saml2AuthnResponse.XmlDocument.OuterXml}'.");
                logger.SetScopeProperty("upPartyStatus", saml2AuthnResponse.Status.ToString());
                logger.ScopeTrace("Up, SAML Authn response.", triggerEvent: true);

                if (saml2AuthnResponse.Status != Saml2StatusCodes.Success)
                {
                    throw new SamlRequestException("Unsuccessful Authn response.")
                          {
                              RouteBinding = RouteBinding, Status = saml2AuthnResponse.Status
                          };
                }

                binding.Unbind(request.ToGenericHttpRequest(), saml2AuthnResponse);
                logger.ScopeTrace("Up, Successful SAML Authn response.", triggerEvent: true);

                if (saml2AuthnResponse.ClaimsIdentity?.Claims?.Count() <= 0)
                {
                    throw new SamlRequestException("Empty claims collection.")
                          {
                              RouteBinding = RouteBinding, Status = Saml2StatusCodes.Responder
                          };
                }

                var claims      = new List <Claim>(saml2AuthnResponse.ClaimsIdentity.Claims.Where(c => c.Type != ClaimTypes.NameIdentifier));
                var nameIdClaim = GetNameIdClaim(party.Name, saml2AuthnResponse.ClaimsIdentity.Claims);
                if (nameIdClaim != null)
                {
                    claims.Add(nameIdClaim);
                }

                var externalSessionId = claims.FindFirstValue(c => c.Type == Saml2ClaimTypes.SessionIndex);
                externalSessionId.ValidateMaxLength(IdentityConstants.MessageLength.SessionIdMax, nameof(externalSessionId), "Session index claim");
                claims = claims.Where(c => c.Type != Saml2ClaimTypes.SessionIndex && c.Type != Constants.SamlClaimTypes.UpPary && c.Type != Constants.SamlClaimTypes.UpParyType).ToList();
                claims.AddClaim(Constants.SamlClaimTypes.UpPary, party.Name);
                claims.AddClaim(Constants.SamlClaimTypes.UpParyType, party.Type.ToString().ToLower());

                var transformedClaims = await claimTransformationsLogic.Transform(party.ClaimTransforms?.ConvertAll(t => (ClaimTransform)t), claims);

                var validClaims = ValidateClaims(party, transformedClaims);

                var jwtValidClaims = await claimsDownLogic.FromSamlToJwtClaimsAsync(validClaims);

                var sessionId = await sessionUpPartyLogic.CreateOrUpdateSessionAsync(party, party.DisableSingleLogout?null : sequenceData.DownPartyLink, jwtValidClaims, externalSessionId);

                if (!sessionId.IsNullOrEmpty())
                {
                    jwtValidClaims.AddClaim(JwtClaimTypes.SessionId, sessionId);
                }

                return(await AuthnResponseDownAsync(sequenceData, saml2AuthnResponse.Status, jwtValidClaims));
            }
            catch (StopSequenceException)
            {
                throw;
            }
            catch (SamlRequestException ex)
            {
                logger.Error(ex);
                return(await AuthnResponseDownAsync(sequenceData, ex.Status));
            }
            catch (Exception ex)
            {
                logger.Error(ex);
                return(await AuthnResponseDownAsync(sequenceData, Saml2StatusCodes.Responder));
            }
        }
Example #7
0
        public async Task <IActionResult> AuthenticationResponseAsync(string partyId)
        {
            logger.ScopeTrace($"Up, OIDC Authentication response.");
            logger.SetScopeProperty("upPartyId", partyId);

            var party = await tenantRepository.GetAsync <OidcUpParty>(partyId);

            logger.SetScopeProperty("upPartyClientId", party.Client.ClientId);

            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 authenticationResponse = formOrQueryDictionary.ToObject <AuthenticationResponse>();

            logger.ScopeTrace($"Up, Authentication response '{authenticationResponse.ToJsonIndented()}'.");
            if (authenticationResponse.State.IsNullOrEmpty())
            {
                throw new ArgumentNullException(nameof(authenticationResponse.State), authenticationResponse.GetTypeName());
            }

            await sequenceLogic.ValidateSequenceAsync(authenticationResponse.State);

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

            var sessionResponse = formOrQueryDictionary.ToObject <SessionResponse>();

            if (sessionResponse != null)
            {
                logger.ScopeTrace($"Up, Session response '{sessionResponse.ToJsonIndented()}'.");
            }

            try
            {
                logger.ScopeTrace("Up, OIDC Authentication response.", triggerEvent: true);

                bool isImplicitFlow = !party.Client.ResponseType.Contains(IdentityConstants.ResponseTypes.Code);
                ValidateAuthenticationResponse(party, authenticationResponse, sessionResponse, isImplicitFlow);

                (var claims, var idToken) = isImplicitFlow switch
                {
                    true => await ValidateTokensAsync(party, sequenceData, authenticationResponse.IdToken, authenticationResponse.AccessToken, true),
                    false => await HandleAuthorizationCodeResponseAsync(party, sequenceData, authenticationResponse.Code)
                };
                logger.ScopeTrace("Up, Successful OIDC Authentication response.", triggerEvent: true);

                var externalSessionId = claims.FindFirstValue(c => c.Type == JwtClaimTypes.SessionId);
                externalSessionId.ValidateMaxLength(IdentityConstants.MessageLength.SessionIdMax, nameof(externalSessionId), "Session state or claim");
                claims = claims.Where(c => c.Type != JwtClaimTypes.SessionId && c.Type != Constants.JwtClaimTypes.UpPary && c.Type != Constants.JwtClaimTypes.UpParyType).ToList();
                claims.AddClaim(Constants.JwtClaimTypes.UpPary, party.Name);
                claims.AddClaim(Constants.JwtClaimTypes.UpParyType, party.Type.ToString().ToLower());

                var transformedClaims = await claimTransformationsLogic.Transform(party.ClaimTransforms?.ConvertAll(t => (ClaimTransform)t), claims);

                var validClaims = ValidateClaims(party, transformedClaims);

                var sessionId = await sessionUpPartyLogic.CreateOrUpdateSessionAsync(party, party.DisableSingleLogout?null : sequenceData.DownPartyLink, validClaims, externalSessionId, idToken);

                if (!sessionId.IsNullOrEmpty())
                {
                    validClaims.AddClaim(JwtClaimTypes.SessionId, sessionId);
                }

                return(await AuthenticationResponseDownAsync(sequenceData, claims : validClaims));
            }
            catch (StopSequenceException)
            {
                throw;
            }
            catch (OAuthRequestException orex)
            {
                logger.SetScopeProperty("upPartyStatus", orex.Error);
                logger.Error(orex);
                return(await AuthenticationResponseDownAsync(sequenceData, error : orex.Error, errorDescription : orex.ErrorDescription));
            }
            catch (ResponseErrorException rex)
            {
                logger.SetScopeProperty("upPartyStatus", rex.Error);
                logger.Error(rex);
                return(await AuthenticationResponseDownAsync(sequenceData, error : rex.Error, errorDescription : $"{party.Name}|{rex.Message}"));
            }
            catch (Exception ex)
            {
                logger.Error(ex);
                return(await AuthenticationResponseDownAsync(sequenceData, error : IdentityConstants.ResponseErrors.InvalidRequest));
            }
        }