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(); } }
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(); } }
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(); } }
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(); } }
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)); } }
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)); } }
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)); } }