private async Task <IActionResult> SingleLogoutRequestAsync(SamlUpParty party, SamlUpSequenceData sequenceData) { var session = await sessionUpPartyLogic.DeleteSessionAsync(party); await oauthRefreshTokenGrantLogic.DeleteRefreshTokenGrantsAsync(session.SessionId); if (party.DisableSingleLogout) { var samlConfig = await saml2ConfigurationLogic.GetSamlUpConfigAsync(party, includeSigningAndDecryptionCertificate : true); return(await SingleLogoutResponseAsync(party, samlConfig, sequenceData.Id, sequenceData.RelayState)); } else { (var doSingleLogout, var singleLogoutSequenceData) = await singleLogoutDownLogic.InitializeSingleLogoutAsync(new UpPartyLink { Name = party.Name, Type = party.Type }, null, session.DownPartyLinks, session.Claims); if (doSingleLogout) { return(await singleLogoutDownLogic.StartSingleLogoutAsync(singleLogoutSequenceData)); } else { var samlConfig = await saml2ConfigurationLogic.GetSamlUpConfigAsync(party, includeSigningAndDecryptionCertificate : true); return(await SingleLogoutResponseAsync(party, samlConfig, sequenceData.Id, sequenceData.RelayState)); } } }
public async Task <ActionResult <Api.SamlUpParty> > PostSamlUpPartyReadMetadata([FromBody] Api.SamlReadMetadataRequest samlReadMetadataRequest) { if (!await ModelState.TryValidateObjectAsync(samlReadMetadataRequest)) { return(BadRequest(ModelState)); } try { var samlUpParty = new SamlUpParty { AuthnBinding = new SamlBinding() }; switch (samlReadMetadataRequest.Type) { case Api.SamlReadMetadataType.Url: samlUpParty.MetadataUrl = samlReadMetadataRequest.Metadata; await samlMetadataReadLogic.PopulateModelAsync(samlUpParty); break; case Api.SamlReadMetadataType.Xml: await samlMetadataReadLogic.PopulateModelAsync(samlUpParty, samlReadMetadataRequest.Metadata); break; default: throw new NotSupportedException(); } return(Ok(mapper.Map <Api.SamlUpParty>(samlUpParty))); } catch (Exception ex) { throw new ValidationException("Unable to read SAML 2.0 metadata.", ex); } }
private IEnumerable <Claim> ValidateClaims(SamlUpParty party, IEnumerable <Claim> claims) { var acceptAllClaims = party.Claims?.Where(c => c == "*")?.Count() > 0; if (!acceptAllClaims) { var acceptedClaims = Constants.DefaultClaims.SamlClaims.ConcatOnce(party.Claims); claims = claims.Where(c => acceptedClaims.Any(ic => ic == c.Type)); } foreach (var claim in claims) { if (claim.Type?.Length > Constants.Models.Claim.SamlTypeLength) { throw new SamlRequestException($"Claim '{claim.Type.Substring(0, Constants.Models.Claim.SamlTypeLength)}' is too long, maximum length of '{Constants.Models.Claim.SamlTypeLength}'.") { RouteBinding = RouteBinding, Status = Saml2StatusCodes.Responder }; } if (claim.Value?.Length > Constants.Models.Claim.ValueLength) { throw new SamlRequestException($"Claim '{claim.Type}' value is too long, maximum length of '{Constants.Models.Claim.ValueLength}'.") { RouteBinding = RouteBinding, Status = Saml2StatusCodes.Responder }; } } return(claims); }
private async Task CreateAspNetCoreSamlIdPSampleUpPartyAsync() { Func <string, Task> getAction = async(name) => { _ = await foxIDsApiClient.GetSamlUpPartyAsync(name); }; Func <string, Task> postAction = async(name) => { var baseUrl = "https://localhost:44342"; var samlUpParty = new SamlUpParty { Name = name, Issuer = "urn:itfoxtec:idservice:samples:aspnetcoresamlidpsample", Keys = new[] { GetSamlCertificateKey("AspNetCoreSamlIdPSample-test-sign-cert.crt") }, //SignatureAlgorithm = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256", //CertificateValidationMode = X509CertificateValidationMode.None, //RevocationMode = X509RevocationMode.NoCheck, AuthnRequestBinding = SamlBindingTypes.Redirect, AuthnResponseBinding = SamlBindingTypes.Post, AuthnUrl = UrlCombine.Combine(baseUrl, "saml/login"), LogoutRequestBinding = SamlBindingTypes.Post, LogoutResponseBinding = SamlBindingTypes.Post, LogoutUrl = UrlCombine.Combine(baseUrl, "saml/logout"), Claims = new string[] { ClaimTypes.Email, ClaimTypes.Name, ClaimTypes.GivenName, ClaimTypes.Surname, ClaimTypes.Role } }; await foxIDsApiClient.PostSamlUpPartyAsync(samlUpParty); }; await CreateIfNotExistsAsync(aspNetCoreSamlIdPSampleUpPartyName, getAction, postAction); }
public Saml2Configuration GetSamlUpConfig(SamlUpParty party, bool includeSigningCertificate = false) { var samlConfig = new Saml2Configuration(); samlConfig.AllowedIssuer = party.Issuer; samlConfig.Issuer = !party.SpIssuer.IsNullOrEmpty() ? party.SpIssuer : trackIssuerLogic.GetIssuer(); samlConfig.AllowedAudienceUris.Add(samlConfig.Issuer); samlConfig.SingleSignOnDestination = new Uri(party.AuthnUrl); if (!party.LogoutUrl.IsNullOrEmpty()) { samlConfig.SingleLogoutDestination = new Uri(party.LogoutUrl); } foreach (var key in party.Keys) { samlConfig.SignatureValidationCertificates.Add(key.ToSaml2X509Certificate()); } if (includeSigningCertificate) { samlConfig.SigningCertificate = trackKeyLogic.GetPrimarySaml2X509Certificate(RouteBinding.Key); } samlConfig.SignatureAlgorithm = party.SignatureAlgorithm; samlConfig.CertificateValidationMode = party.CertificateValidationMode; samlConfig.RevocationMode = party.RevocationMode; return(samlConfig); }
private void ValidatePartyLogoutSupport(SamlUpParty party) { if (party.LogoutBinding == null || party.LogoutUrl.IsNullOrEmpty()) { throw new EndpointException("Logout not configured.") { RouteBinding = RouteBinding }; } }
public async Task <FoxIDsSaml2Configuration> GetSamlUpConfigAsync(SamlUpParty party, bool includeSigningAndDecryptionCertificate = false, bool includeSignatureValidationCertificates = true) { var samlConfig = new FoxIDsSaml2Configuration(); if (party != null) { samlConfig.AllowedIssuer = party.Issuer; } samlConfig.Issuer = !string.IsNullOrEmpty(party?.SpIssuer) ? party.SpIssuer : trackIssuerLogic.GetIssuer(); samlConfig.AllowedAudienceUris.Add(samlConfig.Issuer); if (party != null) { samlConfig.SingleSignOnDestination = new Uri(party.AuthnUrl); if (!string.IsNullOrEmpty(party?.LogoutUrl)) { samlConfig.SingleLogoutDestination = new Uri(party.LogoutUrl); } if (includeSignatureValidationCertificates) { var partyCertificates = party.Keys.ToSaml2X509Certificates(); foreach (var partyCertificate in partyCertificates) { if (partyCertificate.IsValidLocalTime()) { samlConfig.SignatureValidationCertificates.Add(partyCertificate); } else { samlConfig.InvalidSignatureValidationCertificates.Add(partyCertificate); } } } } if (includeSigningAndDecryptionCertificate) { samlConfig.SigningCertificate = samlConfig.DecryptionCertificate = await trackKeyLogic.GetPrimarySaml2X509CertificateAsync(RouteBinding.Key); samlConfig.SecondaryDecryptionCertificate = trackKeyLogic.GetSecondarySaml2X509Certificate(RouteBinding.Key); } if (party != null) { samlConfig.SignatureAlgorithm = party.SignatureAlgorithm; samlConfig.SignAuthnRequest = party.SignAuthnRequest; samlConfig.CertificateValidationMode = party.CertificateValidationMode; samlConfig.RevocationMode = party.RevocationMode; } return(samlConfig); }
public async Task PopulateModelAsync(SamlUpParty party, string metadataXml) { if (metadataXml?.Length > Constants.Models.SamlParty.MetadataXmlSize) { throw new Exception($"Metadata XML must be a string with a maximum length of '{Constants.Models.SamlParty.MetadataXmlSize}'."); } var entityDescriptor = new EntityDescriptor(); entityDescriptor.ReadIdPSsoDescriptor(metadataXml); await PopulateModelInternalAsync(party, entityDescriptor); }
private async Task <IActionResult> SingleLogoutRequestAsync <T>(SamlUpParty party, Saml2Binding <T> binding) { var samlConfig = saml2ConfigurationLogic.GetSamlUpConfig(party); var saml2LogoutRequest = new Saml2LogoutRequest(samlConfig); binding.ReadSamlRequest(HttpContext.Request.ToGenericHttpRequest(), saml2LogoutRequest); try { logger.ScopeTrace($"SAML Single Logout request '{saml2LogoutRequest.XmlDocument.OuterXml}'."); logger.ScopeTrace("Up, SAML Single Logout request.", triggerEvent: true); binding.Unbind(HttpContext.Request.ToGenericHttpRequest(), saml2LogoutRequest); logger.ScopeTrace("Up, Successful SAML Single Logout request.", triggerEvent: true); var session = await sessionUpPartyLogic.DeleteSessionAsync(); await oauthRefreshTokenGrantLogic.DeleteRefreshTokenGrantsAsync(session.SessionId); await sequenceLogic.SaveSequenceDataAsync(new SamlUpSequenceData { ExternalInitiatedSingleLogout = true, Id = saml2LogoutRequest.IdAsString, UpPartyId = party.Id, RelayState = binding.RelayState, SessionId = saml2LogoutRequest.SessionIndex }); if (party.DisableSingleLogout) { return(await SingleLogoutResponseAsync(party, samlConfig, saml2LogoutRequest.Id.Value, binding.RelayState)); } else { (var doSingleLogout, var singleLogoutSequenceData) = await singleLogoutDownLogic.InitializeSingleLogoutAsync(new UpPartyLink { Name = party.Name, Type = party.Type }, null, session.DownPartyLinks, session.Claims); if (doSingleLogout) { return(await singleLogoutDownLogic.StartSingleLogoutAsync(singleLogoutSequenceData)); } else { return(await SingleLogoutResponseAsync(party, samlConfig, saml2LogoutRequest.Id.Value, binding.RelayState)); } } } catch (SamlRequestException ex) { logger.Error(ex); return(await SingleLogoutResponseAsync(party, samlConfig, saml2LogoutRequest.Id.Value, binding.RelayState, ex.Status)); } }
private async Task PopulateModelInternalAsync(SamlUpParty party, EntityDescriptor entityDescriptor) { if (entityDescriptor.IdPSsoDescriptor != null) { party.LastUpdated = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); party.Issuer = entityDescriptor.EntityId; var singleSignOnServices = entityDescriptor.IdPSsoDescriptor.SingleSignOnServices.FirstOrDefault(); if (singleSignOnServices == null) { throw new Exception("IdPSsoDescriptor SingleSignOnServices is empty."); } party.AuthnUrl = singleSignOnServices.Location?.OriginalString; party.AuthnBinding.RequestBinding = GetSamlBindingTypes(singleSignOnServices.Binding?.OriginalString); var singleLogoutDestination = GetSingleLogoutServices(entityDescriptor.IdPSsoDescriptor.SingleLogoutServices); if (singleLogoutDestination != null) { party.LogoutUrl = singleLogoutDestination.Location?.OriginalString; var singleLogoutResponseLocation = singleLogoutDestination.ResponseLocation?.OriginalString; if (!string.IsNullOrEmpty(singleLogoutResponseLocation)) { party.SingleLogoutResponseUrl = singleLogoutResponseLocation; } if (party.LogoutBinding == null) { party.LogoutBinding = new SamlBinding { ResponseBinding = SamlBindingTypes.Post }; } party.LogoutBinding.RequestBinding = GetSamlBindingTypes(singleLogoutDestination.Binding?.OriginalString); } if (entityDescriptor.IdPSsoDescriptor.SigningCertificates?.Count() > 0) { party.Keys = await Task.FromResult(entityDescriptor.IdPSsoDescriptor.SigningCertificates.Select(c => c.ToFTJsonWebKey()).ToList()); } else { party.Keys = null; } if (entityDescriptor.IdPSsoDescriptor.WantAuthnRequestsSigned.HasValue) { party.SignAuthnRequest = entityDescriptor.IdPSsoDescriptor.WantAuthnRequestsSigned.Value; } } else { throw new Exception("IdPSsoDescriptor not loaded from metadata."); } }
private async Task <IActionResult> SingleLogoutRequestAsync <T>(SamlUpParty party, Saml2Binding <T> binding) { var samlConfig = await saml2ConfigurationLogic.GetSamlUpConfigAsync(party); var saml2LogoutRequest = new Saml2LogoutRequest(samlConfig); binding.ReadSamlRequest(HttpContext.Request.ToGenericHttpRequest(), saml2LogoutRequest); try { logger.ScopeTrace(() => $"SAML Single Logout request '{saml2LogoutRequest.XmlDocument.OuterXml}'.", traceType: TraceTypes.Message); logger.ScopeTrace(() => "Up, SAML Single Logout request.", triggerEvent: true); try { binding.Unbind(HttpContext.Request.ToGenericHttpRequest(), saml2LogoutRequest); logger.ScopeTrace(() => "Up, Successful SAML Single Logout request.", triggerEvent: true); } catch (Exception ex) { var isex = saml2ConfigurationLogic.GetInvalidSignatureValidationCertificateException(samlConfig, ex); if (isex != null) { throw isex; } throw; } var sequenceData = await sequenceLogic.SaveSequenceDataAsync(new SamlUpSequenceData { ExternalInitiatedSingleLogout = true, Id = saml2LogoutRequest.IdAsString, UpPartyId = party.Id, RelayState = binding.RelayState, SessionId = saml2LogoutRequest.SessionIndex }); if (binding is Saml2Binding <Saml2PostBinding> ) { return(HttpContext.GetUpPartyUrl(party.Name, Constants.Routes.SamlController, Constants.Endpoints.UpJump.SingleLogoutRequestJump, includeSequence: true, partyBindingPattern: party.PartyBindingPattern).ToRedirectResult()); } else { return(await SingleLogoutRequestAsync(party, sequenceData)); } } catch (SamlRequestException ex) { logger.Error(ex); return(await SingleLogoutResponseAsync(party, samlConfig, saml2LogoutRequest.Id.Value, binding.RelayState, ex.Status)); } }
private async Task <IActionResult> AuthnRequestAsync <T>(SamlUpParty party, Saml2Binding <T> binding, SamlUpSequenceData samlUpSequenceData) { var samlConfig = await saml2ConfigurationLogic.GetSamlUpConfigAsync(party, includeSigningAndDecryptionCertificate : true); binding.RelayState = await sequenceLogic.CreateExternalSequenceIdAsync(); var saml2AuthnRequest = new Saml2AuthnRequest(samlConfig); switch (samlUpSequenceData.LoginAction) { case LoginAction.ReadSession: saml2AuthnRequest.IsPassive = true; break; case LoginAction.RequireLogin: saml2AuthnRequest.ForceAuthn = true; break; default: break; } if (party.AuthnContextClassReferences?.Count() > 0) { saml2AuthnRequest.RequestedAuthnContext = new RequestedAuthnContext { Comparison = party.AuthnContextComparison.HasValue ? (AuthnContextComparisonTypes)Enum.Parse(typeof(AuthnContextComparisonTypes), party.AuthnContextComparison.Value.ToString()) : null, AuthnContextClassRef = party.AuthnContextClassReferences, }; } binding.Bind(saml2AuthnRequest); logger.ScopeTrace(() => $"SAML Authn request '{saml2AuthnRequest.XmlDocument.OuterXml}'.", traceType: TraceTypes.Message); logger.ScopeTrace(() => $"Authn URL '{samlConfig.SingleSignOnDestination?.OriginalString}'."); logger.ScopeTrace(() => "Up, Sending SAML Authn request.", triggerEvent: true); securityHeaderLogic.AddFormActionAllowAll(); if (binding is Saml2Binding <Saml2RedirectBinding> ) { return(await(binding as Saml2RedirectBinding).ToActionFormResultAsync()); } else if (binding is Saml2Binding <Saml2PostBinding> ) { return(await(binding as Saml2PostBinding).ToActionFormResultAsync()); } else { throw new NotSupportedException(); } }
private async Task CreateAspNetCoreSamlIdPSampleUpPartyAsync() { Func <string, Task> getAction = async(name) => { _ = await foxIDsApiClient.GetSamlUpPartyAsync(name); }; Func <string, Task> postAction = async(name) => { var baseUrl = "https://localhost:44342"; var samlUpParty = new SamlUpParty { Name = name, Issuer = "urn:itfoxtec:idservice:samples:aspnetcoresamlidpsample", Keys = new[] { new JsonWebKey { Kty = "RSA", Kid = "27EB823D00B02FA7A02AA754146B6CFC60B8C301", X5c = new[] { "MIICzzCCAbegAwIBAgIJANht5lyL71T0MA0GCSqGSIb3DQEBCwUAMBkxFzAVBgNVBAMTDnRlc3Qtc2lnbi1jZXJ0MB4XDTE4MTAxMTE1MDEyMVoXDTE5MTAxMjE1MDEyMVowGTEXMBUGA1UEAxMOdGVzdC1zaWduLWNlcnQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDUf6b4mtamR7DvTdtz6fdtEe+aXHpzXqvTrjf3SbN5Hol+kAvrIGVXcnJJSfO6N9yC/s8fPE4crVJKwceGkeykzt/j0UHRafmX7e7zzCPO8nd8pVSwZtflogNVdYbrIPTIHLP/eOrPt4Im2PHU0Q561frZjIDgqaoGmtpTLof/0z3GoD52hesZyeE3mW9Q0/+TngLne52rmDe9gmebtmckM7wJw9DXbaJhI24KZPn25PRYnPJMuyBh2EFjJ6qjIAQodpaMstdH6eGJyTan9J/yI6yPhYZ3jl4UngwZ7OpSiGB7m335SYIpPRGxZSdN/tGGdVPV1TIyBU6QFD5mn259AgMBAAGjGjAYMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgXgMA0GCSqGSIb3DQEBCwUAA4IBAQCCYJoiq4sVqTyJ5md4VJIvT3Ezoo6MUDxPmC+bcdT+4j0rYPJr69Fv7celEcS7NEnDK3JQXU2bJ1HAAExBz53bqZphlFnuDFQcJU2lYGaOamDUN/v2aEM/g/Zrlbs4/V4WsCETUkcq7FwmtVia58AZSOtBEqpS7OpdEq4WUmWPRqpjDn+Ne1n921qIKMDtczqOCGc/BREbFUjy49gY/+a57WUxXPhL0gWrGHBwSLIJHp/m9sG7wFA6a2wnvgycrAMYFZ50iGe6IcSzktRdQXd5lTeVtl4JgftwIplIqWyuTYoHwTX+xo2qMSMCF38w31j6BASAmXJniKWeK8aeQ9o7" }, X5t = "J-uCPQCwL6egKqdUFGts_GC4wwE", N = "1H-m-JrWpkew703bc-n3bRHvmlx6c16r064390mzeR6JfpAL6yBlV3JySUnzujfcgv7PHzxOHK1SSsHHhpHspM7f49FB0Wn5l-3u88wjzvJ3fKVUsGbX5aIDVXWG6yD0yByz_3jqz7eCJtjx1NEOetX62YyA4KmqBpraUy6H_9M9xqA-doXrGcnhN5lvUNP_k54C53udq5g3vYJnm7ZnJDO8CcPQ122iYSNuCmT59uT0WJzyTLsgYdhBYyeqoyAEKHaWjLLXR-nhick2p_Sf8iOsj4WGd45eFJ4MGezqUohge5t9-UmCKT0RsWUnTf7RhnVT1dUyMgVOkBQ-Zp9ufQ", E = "AQAB" } }, //SignatureAlgorithm = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256", //CertificateValidationMode = X509CertificateValidationMode.None, //RevocationMode = X509RevocationMode.NoCheck, AuthnBinding = new SamlBinding { RequestBinding = SamlBindingTypes.Redirect, ResponseBinding = SamlBindingTypes.Post }, AuthnUrl = UrlCombine.Combine(baseUrl, "saml/login"), LogoutBinding = new SamlBinding { RequestBinding = SamlBindingTypes.Post, ResponseBinding = SamlBindingTypes.Post }, LogoutUrl = UrlCombine.Combine(baseUrl, "saml/logout"), Claims = new string[] { ClaimTypes.Email, ClaimTypes.Name, ClaimTypes.GivenName, ClaimTypes.Surname, ClaimTypes.Role } }; await foxIDsApiClient.PostSamlUpPartyAsync(samlUpParty); }; await CreateIfNotExistsAsync(aspNetCoreSamlIdPSampleUpPartyName, getAction, postAction); }
private async Task <IActionResult> LogoutAsync <T>(SamlUpParty party, Saml2Binding <T> binding, LogoutRequest logoutRequest) { var samlConfig = saml2ConfigurationLogic.GetSamlUpConfig(party, includeSigningCertificate: true); await formActionLogic.AddFormActionByUrlAsync(samlConfig.SingleLogoutDestination.OriginalString); binding.RelayState = SequenceString; var saml2LogoutRequest = new Saml2LogoutRequest(samlConfig); saml2LogoutRequest.SessionIndex = logoutRequest.SessionId; var nameID = logoutRequest.Claims?.Where(c => c.Type == Saml2ClaimTypes.NameId).Select(c => c.Value).FirstOrDefault(); var nameIdFormat = logoutRequest.Claims?.Where(c => c.Type == Saml2ClaimTypes.NameIdFormat).Select(c => c.Value).FirstOrDefault(); if (!nameID.IsNullOrEmpty()) { if (nameIdFormat.IsNullOrEmpty()) { saml2LogoutRequest.NameId = new Saml2NameIdentifier(nameID); } else { saml2LogoutRequest.NameId = new Saml2NameIdentifier(nameID, new Uri(nameIdFormat)); } } binding.Bind(saml2LogoutRequest); logger.ScopeTrace($"SAML Logout request '{saml2LogoutRequest.XmlDocument.OuterXml}'."); logger.ScopeTrace($"Logout url '{samlConfig.SingleLogoutDestination?.OriginalString}'."); logger.ScopeTrace("Up, SAML Logout request.", triggerEvent: true); if (binding is Saml2Binding <Saml2RedirectBinding> ) { return(await Task.FromResult((binding as Saml2RedirectBinding).ToActionResult())); } if (binding is Saml2Binding <Saml2PostBinding> ) { return(await Task.FromResult((binding as Saml2PostBinding).ToActionResult())); } else { throw new NotSupportedException(); } }
private async Task <IActionResult> SingleLogoutResponseAsync(SamlUpParty party, Saml2Configuration samlConfig, string inResponseTo, string relayState, Saml2StatusCodes status = Saml2StatusCodes.Success, string sessionIndex = null) { logger.ScopeTrace(() => $"Down, SAML Single Logout response{(status != Saml2StatusCodes.Success ? " error" : string.Empty)}, Status code '{status}'."); var binding = party.LogoutBinding.ResponseBinding; logger.ScopeTrace(() => $"Binding '{binding}'"); switch (binding) { case SamlBindingTypes.Redirect: return(await LogoutResponseAsync(samlConfig, inResponseTo, relayState, GetSingleLogoutResponseUrl(party), new Saml2RedirectBinding(), status, sessionIndex)); case SamlBindingTypes.Post: return(await LogoutResponseAsync(samlConfig, inResponseTo, relayState, GetSingleLogoutResponseUrl(party), new Saml2PostBinding(), status, sessionIndex)); default: throw new NotSupportedException($"SAML binding '{binding}' not supported."); } }
private async Task <IActionResult> LogoutResponseAsync <T>(SamlUpParty party, Saml2Binding <T> binding) { var samlConfig = saml2ConfigurationLogic.GetSamlUpConfig(party); var saml2LogoutResponse = new Saml2LogoutResponse(samlConfig); binding.ReadSamlResponse(HttpContext.Request.ToGenericHttpRequest(), saml2LogoutResponse); await sequenceLogic.ValidateSequenceAsync(binding.RelayState); var sequenceData = await sequenceLogic.GetSequenceDataAsync <SamlUpSequenceData>(); try { logger.ScopeTrace($"SAML Logout response '{saml2LogoutResponse.XmlDocument.OuterXml}'."); logger.SetScopeProperty("status", saml2LogoutResponse.Status.ToString()); logger.ScopeTrace("Up, SAML Logout response.", triggerEvent: true); if (saml2LogoutResponse.Status != Saml2StatusCodes.Success) { throw new SamlRequestException("Unsuccessful Logout response.") { RouteBinding = RouteBinding, Status = saml2LogoutResponse.Status }; } binding.Unbind(HttpContext.Request.ToGenericHttpRequest(), saml2LogoutResponse); logger.ScopeTrace("Up, Successful SAML Logout response.", triggerEvent: true); return(await LogoutResponseDownAsync(sequenceData, saml2LogoutResponse.Status, saml2LogoutResponse.SessionIndex)); } catch (SamlRequestException ex) { logger.Error(ex); return(await LogoutResponseDownAsync(sequenceData, ex.Status)); } catch (Exception ex) { logger.Error(ex); return(await LogoutResponseDownAsync(sequenceData, Saml2StatusCodes.Responder)); } }
private async Task <IActionResult> AuthnRequestAsync <T>(SamlUpParty party, Saml2Binding <T> binding, SamlUpSequenceData samlUpSequenceData) { var samlConfig = saml2ConfigurationLogic.GetSamlUpConfig(party); binding.RelayState = SequenceString; var saml2AuthnRequest = new Saml2AuthnRequest(samlConfig); switch (samlUpSequenceData.LoginAction) { case LoginAction.ReadSession: saml2AuthnRequest.IsPassive = true; break; case LoginAction.RequireLogin: saml2AuthnRequest.ForceAuthn = true; break; default: break; } binding.Bind(saml2AuthnRequest); logger.ScopeTrace($"SAML Authn request '{saml2AuthnRequest.XmlDocument.OuterXml}'."); logger.ScopeTrace($"Authn URL '{samlConfig.SingleSignOnDestination?.OriginalString}'."); logger.ScopeTrace("Up, Sending SAML Authn request.", triggerEvent: true); securityHeaderLogic.AddFormActionAllowAll(); if (binding is Saml2Binding <Saml2RedirectBinding> ) { return(await(binding as Saml2RedirectBinding).ToActionFormResultAsync()); } else if (binding is Saml2Binding <Saml2PostBinding> ) { return(await(binding as Saml2PostBinding).ToActionFormResultAsync()); } else { throw new NotSupportedException(); } }
public Saml2Configuration GetSamlUpConfig(SamlUpParty party, bool includeSigningCertificate = false) { var samlConfig = new Saml2Configuration(); if (!party.IdSIssuer.IsNullOrEmpty()) { samlConfig.Issuer = party.IdSIssuer; } else { samlConfig.Issuer = UrlCombine.Combine(HttpContext.GetHost(), RouteBinding.TenantName, RouteBinding.TrackName); } samlConfig.AllowedAudienceUris.Add(samlConfig.Issuer); samlConfig.SingleSignOnDestination = new Uri(party.AuthnUrl); if (!party.LogoutUrl.IsNullOrEmpty()) { samlConfig.SingleLogoutDestination = new Uri(party.LogoutUrl); } foreach (var key in party.Keys) { samlConfig.SignatureValidationCertificates.Add(key.ToSaml2X509Certificate()); } if (includeSigningCertificate) { samlConfig.SigningCertificate = trackKeyLogic.GetSaml2X509Certificate(RouteBinding.PrimaryKey); } samlConfig.SignatureAlgorithm = party.SignatureAlgorithm; samlConfig.CertificateValidationMode = party.CertificateValidationMode; samlConfig.RevocationMode = party.RevocationMode; return(samlConfig); }
private IEnumerable <Claim> ValidateClaims(SamlUpParty party, IEnumerable <Claim> claims) { IEnumerable <string> acceptedClaims = Constants.DefaultClaims.SamlClaims.ConcatOnce(party.Claims); claims = claims.Where(c => acceptedClaims.Any(ic => ic == c.Type)); foreach (var claim in claims) { if (claim.Type?.Count() > Constants.Models.SamlParty.ClaimLength) { throw new SamlRequestException($"Claim '{claim.Type.Substring(0, Constants.Models.SamlParty.ClaimLength)}' is too long.") { RouteBinding = RouteBinding, Status = Saml2StatusCodes.Responder }; } if (claim.Value?.Count() > Constants.Models.SamlParty.ClaimValueLength) { throw new SamlRequestException($"Claim value '{claim.Value.Substring(0, Constants.Models.SamlParty.ClaimValueLength)}' is too long.") { RouteBinding = RouteBinding, Status = Saml2StatusCodes.Responder }; } } return(claims); }
public async Task <bool> PopulateModelAsync(ModelStateDictionary modelState, SamlUpParty mp) { var isValid = true; try { if (mp.UpdateState != PartyUpdateStates.Manual) { await samlMetadataReadLogic.PopulateModelAsync(mp); if (mp.UpdateState == PartyUpdateStates.AutomaticStopped) { mp.UpdateState = PartyUpdateStates.Automatic; } } } catch (Exception ex) { isValid = false; logger.Warning(ex); modelState.TryAddModelError(nameof(mp.MetadataUrl).ToCamelCase(), ex.GetAllMessagesJoined()); } return(isValid); }
private string GetSingleLogoutResponseUrl(SamlUpParty party) => party.SingleLogoutResponseUrl.IsNullOrEmpty() ? party.LogoutUrl : party.SingleLogoutResponseUrl;
public async Task CheckMetadataAndUpdateUpPartyAsync(SamlUpParty party) { if (party.UpdateState != PartyUpdateStates.Automatic) { return; } var lastUpdated = DateTimeOffset.FromUnixTimeSeconds(party.LastUpdated); if (lastUpdated.AddSeconds(party.MetadataUpdateRate.Value) >= DateTimeOffset.UtcNow) { return; } var db = redisConnectionMultiplexer.GetDatabase(); var key = UpdateUpPartyWaitPeriodKey(party.Id); if (await db.KeyExistsAsync(key)) { logger.ScopeTrace(() => $"Up party '{party.Id}' not updated with SAML 2.0 metadata because another update is in progress."); return; } else { await db.StringSetAsync(key, true, TimeSpan.FromSeconds(settings.UpPartyUpdateWaitPeriod)); } var failingUpdateCount = (long?)await db.StringGetAsync(FailingUpdateUpPartyCountKey(party.Id)); if (failingUpdateCount.HasValue && failingUpdateCount.Value >= settings.UpPartyMaxFailingUpdate) { party.UpdateState = PartyUpdateStates.AutomaticStopped; await tenantRepository.SaveAsync(party); await db.KeyDeleteAsync(FailingUpdateUpPartyCountKey(party.Id)); return; } try { try { await samlMetadataReadLogic.PopulateModelAsync(party); } catch (Exception ex) { throw new EndpointException("Failed to read SAML 2.0 metadata.", ex) { RouteBinding = RouteBinding }; } await tenantRepository.SaveAsync(party); logger.ScopeTrace(() => $"Up party '{party.Id}' updated by SAML 2.0 metadata.", triggerEvent: true); await db.KeyDeleteAsync(FailingUpdateUpPartyCountKey(party.Id)); } catch (Exception ex) { await db.StringIncrementAsync(FailingUpdateUpPartyCountKey(party.Id)); logger.Warning(ex); } }
public async Task PopulateModelAsync(SamlUpParty party) { var metadata = await ReadMetadataAsync(party.MetadataUrl); await PopulateModelAsync(party, metadata); }
public async Task UpdateSamlUpPartyAsync(SamlUpParty party) => await PutAsync(samlApiUri, party);
public async Task CreateSamlUpPartyAsync(SamlUpParty party) => await PostAsync(samlApiUri, party);
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)); } }
private async Task <IActionResult> AuthnResponseAsync <T>(SamlUpParty party, Saml2Binding <T> binding) { var request = HttpContext.Request; var samlConfig = await saml2ConfigurationLogic.GetSamlUpConfigAsync(party, includeSigningAndDecryptionCertificate : true); var saml2AuthnResponse = new Saml2AuthnResponse(samlConfig); try { binding.ReadSamlResponse(request.ToGenericHttpRequest(), saml2AuthnResponse); } catch (Exception ex) { if (samlConfig.SecondaryDecryptionCertificate != null && binding is Saml2PostBinding && ex.Source.Contains("cryptography", StringComparison.OrdinalIgnoreCase)) { samlConfig.DecryptionCertificate = samlConfig.SecondaryDecryptionCertificate; saml2AuthnResponse = new Saml2AuthnResponse(samlConfig); binding.ReadSamlResponse(request.ToGenericHttpRequest(), saml2AuthnResponse); logger.ScopeTrace(() => $"SAML Authn response decrypted with secondary certificate.", traceType: TraceTypes.Message); } else { throw; } } if (binding.RelayState.IsNullOrEmpty()) { throw new ArgumentNullException(nameof(binding.RelayState), binding.GetTypeName()); } await sequenceLogic.ValidateExternalSequenceIdAsync(binding.RelayState); var sequenceData = await sequenceLogic.GetSequenceDataAsync <SamlUpSequenceData>(); try { logger.ScopeTrace(() => $"SAML Authn response '{saml2AuthnResponse.XmlDocument.OuterXml}'.", traceType: TraceTypes.Message); logger.SetScopeProperty(Constants.Logs.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 }; } try { binding.Unbind(request.ToGenericHttpRequest(), saml2AuthnResponse); logger.ScopeTrace(() => "Up, Successful SAML Authn response.", triggerEvent: true); } catch (Exception ex) { var isex = saml2ConfigurationLogic.GetInvalidSignatureValidationCertificateException(samlConfig, ex); if (isex != null) { throw isex; } throw; } 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); } logger.ScopeTrace(() => $"Up, SAML Authn received SAML claims '{claims.ToFormattedString()}'", traceType: TraceTypes.Claim); 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.UpParty && c.Type != Constants.SamlClaimTypes.UpPartyType).ToList(); claims.AddClaim(Constants.SamlClaimTypes.UpParty, party.Name); claims.AddClaim(Constants.SamlClaimTypes.UpPartyType, party.Type.ToString().ToLower()); var transformedClaims = await claimTransformLogic.Transform(party.ClaimTransforms?.ConvertAll(t => (ClaimTransform)t), claims); var validClaims = ValidateClaims(party, transformedClaims); logger.ScopeTrace(() => $"Up, SAML Authn output SAML claims '{validClaims.ToFormattedString()}'", traceType: TraceTypes.Claim); 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); } logger.ScopeTrace(() => $"Up, SAML Authn output JWT claims '{jwtValidClaims.ToFormattedString()}'", traceType: TraceTypes.Claim); 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)); } }
private async Task <IActionResult> LogoutRequestAsync <T>(SamlUpParty party, Saml2Binding <T> binding, SamlUpSequenceData samlUpSequenceData) { var samlConfig = await saml2ConfigurationLogic.GetSamlUpConfigAsync(party, includeSigningAndDecryptionCertificate : true); binding.RelayState = await sequenceLogic.CreateExternalSequenceIdAsync(); var saml2LogoutRequest = new Saml2LogoutRequest(samlConfig); var session = await sessionUpPartyLogic.GetSessionAsync(party); if (session == null) { return(await LogoutResponseDownAsync(samlUpSequenceData)); } try { if (!samlUpSequenceData.SessionId.Equals(session.SessionId, StringComparison.Ordinal)) { throw new Exception("Requested session ID do not match up-party session ID."); } } catch (Exception ex) { logger.Warning(ex); } saml2LogoutRequest.SessionIndex = session.ExternalSessionId; samlUpSequenceData.SessionDownPartyLinks = session.DownPartyLinks; samlUpSequenceData.SessionClaims = session.Claims; await sequenceLogic.SaveSequenceDataAsync(samlUpSequenceData); var jwtClaims = samlUpSequenceData.SessionClaims.ToClaimList(); var nameID = jwtClaims?.Where(c => c.Type == JwtClaimTypes.Subject).Select(c => c.Value).FirstOrDefault(); var nameIdFormat = jwtClaims?.Where(c => c.Type == Constants.JwtClaimTypes.SubFormat).Select(c => c.Value).FirstOrDefault(); if (!nameID.IsNullOrEmpty()) { var prePartyName = $"{party.Name}|"; if (nameID.StartsWith(prePartyName, StringComparison.Ordinal)) { nameID = nameID.Remove(0, prePartyName.Length); } if (nameIdFormat.IsNullOrEmpty()) { saml2LogoutRequest.NameId = new Saml2NameIdentifier(nameID); } else { saml2LogoutRequest.NameId = new Saml2NameIdentifier(nameID, new Uri(nameIdFormat)); } } binding.Bind(saml2LogoutRequest); logger.ScopeTrace(() => $"SAML Logout request '{saml2LogoutRequest.XmlDocument.OuterXml}'.", traceType: TraceTypes.Message); logger.ScopeTrace(() => $"Logout URL '{samlConfig.SingleLogoutDestination?.OriginalString}'."); logger.ScopeTrace(() => "Up, SAML Logout request.", triggerEvent: true); _ = await sessionUpPartyLogic.DeleteSessionAsync(party, session); await oauthRefreshTokenGrantLogic.DeleteRefreshTokenGrantsAsync(samlUpSequenceData.SessionId); securityHeaderLogic.AddFormActionAllowAll(); if (binding is Saml2Binding <Saml2RedirectBinding> ) { return(await(binding as Saml2RedirectBinding).ToActionFormResultAsync()); } else if (binding is Saml2Binding <Saml2PostBinding> ) { return(await(binding as Saml2PostBinding).ToActionFormResultAsync()); } else { throw new NotSupportedException(); } }
private async Task <IActionResult> LogoutResponseAsync <T>(SamlUpParty party, Saml2Binding <T> binding) { var samlConfig = await saml2ConfigurationLogic.GetSamlUpConfigAsync(party); var saml2LogoutResponse = new Saml2LogoutResponse(samlConfig); binding.ReadSamlResponse(HttpContext.Request.ToGenericHttpRequest(), saml2LogoutResponse); await sequenceLogic.ValidateExternalSequenceIdAsync(binding.RelayState); var sequenceData = await sequenceLogic.GetSequenceDataAsync <SamlUpSequenceData>(remove : party.DisableSingleLogout); try { logger.ScopeTrace(() => $"SAML Logout response '{saml2LogoutResponse.XmlDocument.OuterXml}'.", traceType: TraceTypes.Message); logger.SetScopeProperty(Constants.Logs.Status, saml2LogoutResponse.Status.ToString()); logger.ScopeTrace(() => "Up, SAML Logout response.", triggerEvent: true); if (saml2LogoutResponse.Status != Saml2StatusCodes.Success) { throw new SamlRequestException("Unsuccessful Logout response.") { RouteBinding = RouteBinding, Status = saml2LogoutResponse.Status }; } try { binding.Unbind(HttpContext.Request.ToGenericHttpRequest(), saml2LogoutResponse); logger.ScopeTrace(() => "Up, Successful SAML Logout response.", triggerEvent: true); } catch (Exception ex) { var isex = saml2ConfigurationLogic.GetInvalidSignatureValidationCertificateException(samlConfig, ex); if (isex != null) { throw isex; } throw; } 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 <SamlUpSequenceData>(); return(await LogoutResponseDownAsync(sequenceData)); } } } catch (StopSequenceException) { throw; } catch (SamlRequestException ex) { logger.Error(ex); return(await LogoutResponseDownAsync(sequenceData, status : ex.Status)); } catch (Exception ex) { logger.Error(ex); return(await LogoutResponseDownAsync(sequenceData, status : Saml2StatusCodes.Responder)); } }