public Logout(IInternalLogger logger, SAML2.Config.Saml2Configuration config) { if (logger == null) throw new ArgumentNullException("logger"); if (config == null) throw new ArgumentNullException("config"); this.logger = logger; this.config = config; }
/// <summary> /// Initializes a new instance of the <see cref="Saml20Assertion"/> class. /// </summary> /// <param name="assertion">The assertion.</param> /// <param name="trustedSigners">If <code>null</code>, the signature of the given assertion is not verified.</param> /// <param name="quirksMode">if set to <c>true</c> quirks mode is enabled.</param> public Saml20Assertion(XmlElement assertion, IEnumerable<AsymmetricAlgorithm> trustedSigners, bool quirksMode, Saml2Configuration config) { _quirksMode = quirksMode; _profile = null; _config = config; LoadXml(assertion, trustedSigners, config); }
public MetadataUtils(Config.Saml2Configuration configuration, Logging.IInternalLogger logger) { if (configuration == null) throw new ArgumentNullException("configuration"); if (logger == null) throw new ArgumentNullException("logger"); this.configuration = configuration; this.logger = logger; }
/// <summary> /// Constructor for LoginHandler /// </summary> /// <param name="configuration">SamlConfiguration</param> /// <param name="getFromCache">May be null unless doing artifact binding, this function will be called for artifact resolution</param> public SamlLoginHandler(SamlAuthenticationOptions options) { if (options == null) throw new ArgumentNullException("options"); this.options = options; configuration = options.Configuration; getFromCache = options.GetFromCache; setInCache = options.SetInCache; session = options.Session; }
/// <summary> /// Initializes a new instance of the <see cref="HttpArtifactBindingBuilder"/> class. /// </summary> /// <param name="context">The current http context.</param> /// <param name="redirect">Action to perform when redirecting. Parameter will be destination URL</param> /// <param name="sendResponseMessage">Action to send messages to response stream</param> public HttpArtifactBindingBuilder(Saml2Configuration config, Action<string> redirect, Action<string> sendResponseMessage) { if (config == null) throw new ArgumentNullException("config"); if (redirect == null) throw new ArgumentNullException("redirect"); if (sendResponseMessage == null) throw new ArgumentNullException("sendResponseMessage"); this.redirect = redirect; this.config = config; this.sendResponseMessage = sendResponseMessage; }
public Saml20SignonHandler(Saml2Configuration config) { _certificate = config.ServiceProvider.SigningCertificate; // Read the proper redirect url from config try { RedirectUrl = config.ServiceProvider.Endpoints.DefaultSignOnEndpoint.RedirectUrl; } catch (Exception e) { Logger.Error(e.Message, e); } }
public Logout(IInternalLogger logger, SAML2.Config.Saml2Configuration config) { if (logger == null) { throw new ArgumentNullException(nameof(logger)); } if (config == null) { throw new ArgumentNullException(nameof(config)); } this.logger = logger; this.config = config; }
/// <summary> /// Handles a request. /// </summary> /// <param name="context">The context.</param> public void Handle(HttpContext context, Saml2Configuration config) { Logger.Debug(TraceMessages.SignOnHandlerCalled); var getFromCache = new Func<string, object>(context.Cache.Get); var setInCache = new Action<string, object, DateTime>((s, o, d) => context.Cache.Insert(s, o, null, d, Cache.NoSlidingExpiration)); var loginAction = new Action<Saml20Assertion>(a => DoSignOn(context, a, config)); var session = SessionToDictionary(context.Session); // Some IdP's are known to fail to set an actual value in the SOAPAction header // so we just check for the existence of the header field. if (Array.Exists(context.Request.Headers.AllKeys, s => s == SoapConstants.SoapAction)) { Utility.HandleSoap( GetBuilder(context), context.Request.InputStream, config, loginAction, getFromCache, setInCache, session); return; } if (!string.IsNullOrEmpty(context.Request.Params["SAMLart"])) { HandleArtifact(context, config, (c, s, conf) => Utility.HandleSoap( GetBuilder(context), s, conf, loginAction, getFromCache, setInCache, session)); } var samlResponse = context.Request.Params["SamlResponse"]; if (!string.IsNullOrEmpty(samlResponse)) { var assertion = Utility.HandleResponse(config, samlResponse, session, getFromCache, setInCache); loginAction(assertion); } else { if (config.CommonDomainCookie.Enabled && context.Request.QueryString["r"] == null && context.Request.Params["cidp"] == null) { Logger.Debug(TraceMessages.CommonDomainCookieRedirectForDiscovery); context.Response.Redirect(config.CommonDomainCookie.LocalReaderEndpoint); } else { Logger.WarnFormat(ErrorMessages.UnauthenticatedAccess, context.Request.RawUrl); SendRequest(context, config); } } }
/// <summary> /// Handles a request. /// </summary> /// <param name="context">The context.</param> public void Handle(HttpContext context, Saml2Configuration config) { Logger.Debug(TraceMessages.LogoutHandlerCalled); // Some IDP's are known to fail to set an actual value in the SOAPAction header // so we just check for the existence of the header field. if (Array.Exists(context.Request.Headers.AllKeys, s => s == SoapConstants.SoapAction)) { HandleSoap(context, context.Request.InputStream, config); return; } if (!string.IsNullOrEmpty(context.Request.Params["SAMLart"])) { HandleArtifact(context, ConfigurationFactory.Instance.Configuration, HandleSoap); return; } if (!string.IsNullOrEmpty(context.Request.Params["SAMLResponse"])) { HandleResponse(context, config); } else if (!string.IsNullOrEmpty(context.Request.Params["SAMLRequest"])) { HandleRequest(context); } else { IdentityProvider idpEndpoint = null; // context.Session[IDPLoginSessionKey] may be null if IIS has been restarted if (context.Session[IdpSessionIdKey] != null) { idpEndpoint = IdpSelectionUtil.RetrieveIDPConfiguration((string)context.Session[IdpLoginSessionKey], config); } if (idpEndpoint == null) { // TODO: Reconsider how to accomplish this. context.User = null; FormsAuthentication.SignOut(); Logger.ErrorFormat(ErrorMessages.UnknownIdentityProvider, string.Empty); throw new Saml20Exception(string.Format(ErrorMessages.UnknownIdentityProvider, string.Empty)); } TransferClient(idpEndpoint, context, config); } }
/// <summary> /// Handles the selection of an IDP. If only one IDP is found, the user is automatically redirected to it. /// If several are found, and nothing indicates to which one the user should be sent, this method returns null. /// </summary> /// <param name="context">The context.</param> /// <param name="config">Configuration. If null, configuration will be populated from application config</param> /// <returns>The <see cref="IdentityProvider"/>.</returns> public IdentityProvider RetrieveIDP(NameValueCollection allparams, NameValueCollection queryString, Saml2Configuration config, Action<string> redirectToSelection) { // If idpChoice is set, use it value if (!string.IsNullOrEmpty(allparams[IdpChoiceParameterName])) { logger.DebugFormat(TraceMessages.IdentityProviderRetreivedFromQueryString, allparams[IdpChoiceParameterName]); var endPoint = config.IdentityProviders.FirstOrDefault(x => x.Id == allparams[IdpChoiceParameterName]); if (endPoint != null) { return endPoint; } } // If we have a common domain cookie, use it's value // It must have been returned from the local common domain cookie reader endpoint. if (!string.IsNullOrEmpty(queryString["_saml_idp"])) { var cdc = new Protocol.CommonDomainCookie(queryString["_saml_idp"]); if (cdc.IsSet) { var endPoint = config.IdentityProviders.FirstOrDefault(x => x.Id == cdc.PreferredIDP); if (endPoint != null) { logger.DebugFormat(TraceMessages.IdentityProviderRetreivedFromCommonDomainCookie, cdc.PreferredIDP); return endPoint; } logger.WarnFormat(ErrorMessages.CommonDomainCookieIdentityProviderInvalid, cdc.PreferredIDP); } } // If there is only one configured IdentityProviderEndpointElement lets just use that if (config.IdentityProviders.Count == 1 && config.IdentityProviders[0].Metadata != null) { logger.DebugFormat(TraceMessages.IdentityProviderRetreivedFromDefault, config.IdentityProviders[0].Name); return config.IdentityProviders[0]; } // If one of the endpoints are marked with default, use that one var defaultIDP = config.IdentityProviders.FirstOrDefault(idp => idp.Default); if (defaultIDP != null) { logger.DebugFormat(TraceMessages.IdentityProviderRetreivedFromDefault, defaultIDP.Id); return defaultIDP; } // In case an IDP selection url has been configured, redirect to that one. if (!string.IsNullOrEmpty(config.IdentityProviders.SelectionUrl)) { logger.DebugFormat(TraceMessages.IdentityProviderRetreivedFromSelection, config.IdentityProviders.SelectionUrl); redirectToSelection(config.IdentityProviders.SelectionUrl); return null; } // If an IDPSelectionEvent handler is present, request the handler for an IDP endpoint to use. return IdpSelectionUtil.InvokeIDPSelectionEventHandler(config.IdentityProviders); }
public Saml2Configuration TestEnvironmentConfiguration() { var myconfig = new Saml2Configuration { ServiceProvider = new ServiceProvider { SigningCertificate = new System.Security.Cryptography.X509Certificates.X509Certificate2("../../Metadata-Test/certificate.pfx", "", System.Security.Cryptography.X509Certificates.X509KeyStorageFlags.MachineKeySet), Server = "http://localhost:7777/identity", Id = "http://localhost:7777/identity" } }; myconfig.ServiceProvider.Endpoints.AddRange(new[] { new ServiceProviderEndpoint(EndpointType.SignOn, "/identity/login", "/identity", BindingType.Redirect), new ServiceProviderEndpoint(EndpointType.Logout, "/identity/logout", "/identity", BindingType.Redirect), new ServiceProviderEndpoint(EndpointType.Metadata, "/identity/metadata") }); myconfig.IdentityProviders.AddByMetadata("..\\..\\Metadata-Test\\uat.xml"); SAML2.Logging.LoggerProvider.Configuration = myconfig; return myconfig; }
private Saml2Configuration GetSamlConfiguration() { var myconfig = new Saml2Configuration { ServiceProvider = new ServiceProvider { SigningCertificate = new System.Security.Cryptography.X509Certificates.X509Certificate2(FileEmbeddedResource("SelfHostOwinSPExample.sts_dev_certificate.pfx"), "test1234", System.Security.Cryptography.X509Certificates.X509KeyStorageFlags.MachineKeySet), Server = "https://localhost:44333/core", Id = "https://localhost:44333/core" }, AllowedAudienceUris = new System.Collections.Generic.List<Uri>(new[] { new Uri("https://localhost:44333/core") }) }; myconfig.ServiceProvider.Endpoints.AddRange(new[] { new ServiceProviderEndpoint(EndpointType.SignOn, "/core/saml2/login", "/core"), new ServiceProviderEndpoint(EndpointType.Logout, "/core/saml2/logout", "/core"), new ServiceProviderEndpoint(EndpointType.Metadata, "/core/saml2/metadata") }); myconfig.IdentityProviders.AddByMetadataDirectory("..\\..\\Metadata"); //myconfig.IdentityProviders.AddByMetadataUrl(new Uri("https://tas.fhict.nl/identity/saml2/metadata")); myconfig.IdentityProviders.First().OmitAssertionSignatureCheck = true; myconfig.LoggingFactoryType = "SAML2.Logging.DebugLoggerFactory"; return myconfig; }
/// <summary> /// Validates the SAML20Federation configuration. /// </summary> /// <returns>True if validation passes, false otherwise</returns> public static bool ValidateConfiguration(Saml2Configuration config) { if (config == null) { throw new ArgumentNullException("config", ErrorMessages.ConfigMissingSaml2Element); } if (config.ServiceProvider == null) { throw new ArgumentOutOfRangeException("config", ErrorMessages.ConfigMissingServiceProviderElement); } if (string.IsNullOrEmpty(config.ServiceProvider.Id)) { throw new ArgumentOutOfRangeException("config", ErrorMessages.ConfigMissingServiceProviderIdAttribute); } if (config.ServiceProvider.SigningCertificate == null) { throw new ArgumentOutOfRangeException("config", ErrorMessages.ConfigMissingSigningCertificateElement); } // This will throw if no certificate or multiple certificates are found var certificate = config.ServiceProvider.SigningCertificate; if (!certificate.HasPrivateKey) { throw new ArgumentOutOfRangeException("config", ErrorMessages.ConfigSigningCertificateMissingPrivateKey); } if (config.IdentityProviders == null) { throw new ArgumentOutOfRangeException("config", ErrorMessages.ConfigMissingIdentityProvidersElement); } return true; }
/// <summary> /// Transfers the client. /// </summary> /// <param name="identityProvider">The identity provider.</param> /// <param name="request">The request.</param> /// <param name="context">The context.</param> private void TransferClient(IdentityProvider identityProvider, Saml20AuthnRequest request, HttpContext context, Saml2Configuration config) { IdentityProviderEndpoint destination = ConfigureRequest(identityProvider, request, context); switch (destination.Binding) { case BindingType.Redirect: Logger.DebugFormat(TraceMessages.AuthnRequestPrepared, identityProvider.Id, Saml20Constants.ProtocolBindings.HttpRedirect); var redirectBuilder = new HttpRedirectBindingBuilder { SigningKey = _certificate.PrivateKey, Request = request.GetXml().OuterXml }; Logger.DebugFormat(TraceMessages.AuthnRequestSent, redirectBuilder.Request); var redirectLocation = request.Destination + "?" + redirectBuilder.ToQuery(); context.Response.Redirect(redirectLocation, true); break; case BindingType.Post: Logger.DebugFormat(TraceMessages.AuthnRequestPrepared, identityProvider.Id, Saml20Constants.ProtocolBindings.HttpPost); var postBuilder = new HttpPostBindingBuilder(destination); // Honor the ForceProtocolBinding and only set this if it's not already set if (string.IsNullOrEmpty(request.ProtocolBinding)) { request.ProtocolBinding = Saml20Constants.ProtocolBindings.HttpPost; } var requestXml = request.GetXml(); XmlSignatureUtils.SignDocument(requestXml, request.Id, config.ServiceProvider.SigningCertificate); postBuilder.Request = requestXml.OuterXml; Logger.DebugFormat(TraceMessages.AuthnRequestSent, postBuilder.Request); context.Response.Write(postBuilder.GetPage()); break; case BindingType.Artifact: Logger.DebugFormat(TraceMessages.AuthnRequestPrepared, identityProvider.Id, Saml20Constants.ProtocolBindings.HttpArtifact); var artifactBuilder = GetBuilder(context); // Honor the ForceProtocolBinding and only set this if it's not already set if (string.IsNullOrEmpty(request.ProtocolBinding)) { request.ProtocolBinding = Saml20Constants.ProtocolBindings.HttpArtifact; } Logger.DebugFormat(TraceMessages.AuthnRequestSent, request.GetXml().OuterXml); artifactBuilder.RedirectFromLogin(destination, request, context.Request.Params["relayState"], (s, o) => context.Cache.Insert(s, o, null, DateTime.Now.AddMinutes(1), Cache.NoSlidingExpiration)); break; default: Logger.Error(ErrorMessages.EndpointBindingInvalid); throw new Saml20Exception(ErrorMessages.EndpointBindingInvalid); } }
/// <summary> /// Handles executing the login. /// </summary> /// <param name="context">The context.</param> /// <param name="assertion">The assertion.</param> private void DoSignOn(HttpContext context, Saml20Assertion assertion, Saml2Configuration config) { // User is now logged in at IDP specified in tmp context.Items[IdpLoginSessionKey] = context.Session != null ? context.Session[IdpTempSessionKey] : context.Items[IdpTempSessionKey]; context.Items[IdpSessionIdKey] = assertion.SessionIndex; context.Items[IdpNameIdFormat] = assertion.Subject.Format; context.Items[IdpNameId] = assertion.Subject.Value; Logger.DebugFormat(TraceMessages.SignOnProcessed, assertion.SessionIndex, assertion.Subject.Value, assertion.Subject.Format); Logger.Debug(TraceMessages.SignOnActionsExecuting); // TODO: Signon event //foreach (var action in Actions.Actions.GetActions(config)) //{ // Logger.DebugFormat("{0}.{1} called", action.GetType(), "LoginAction()"); // action.SignOnAction(this, context, assertion, config); // Logger.DebugFormat("{0}.{1} finished", action.GetType(), "LoginAction()"); //} }
/// <summary> /// Send an authentication request to the IDP. /// </summary> /// <param name="context">The context.</param> private void SendRequest(HttpContext context, Saml2Configuration config) { // See if the "ReturnUrl" - parameter is set. var returnUrl = context.Request.QueryString["ReturnUrl"]; if (!string.IsNullOrEmpty(returnUrl) && context.Session != null) { context.Session["RedirectUrl"] = returnUrl; } var isRedirected = false; var selectionUtil = new IdpSelectionUtil(Logger); var idp = selectionUtil.RetrieveIDP(context.Request.Params, context.Request.QueryString, config, s => { context.Response.Redirect(s); isRedirected = true; }); if (isRedirected) return; if (idp == null) { // Display a page to the user where she can pick the IDP Logger.DebugFormat(TraceMessages.IdentityProviderRedirect); var page = new SelectSaml20IDP(); page.ProcessRequest(context); return; } var authnRequest = Saml20AuthnRequest.GetDefault(config); TransferClient(idp, authnRequest, context, config); }
/// <summary> /// Gets a default instance of this class with meaningful default values set. /// </summary> /// <returns>The default <see cref="Saml20AttributeQuery"/>.</returns> public static Saml20AttributeQuery GetDefault(Saml2Configuration config) { return new Saml20AttributeQuery { Issuer = config.ServiceProvider.Id }; }
/// <summary> /// Resolves an artifact. /// </summary> /// <returns>A stream containing the artifact response from the IdP</returns> /// <param name="artifact">artifact from request ("SAMLart")</param> public Stream ResolveArtifact(string artifact, string relayState, Saml2Configuration config) { var idpEndPoint = DetermineIdp(artifact); if (idpEndPoint == null) { throw new InvalidOperationException(ErrorMessages.ArtifactResolveIdentityProviderUnknown); } var endpointIndex = ArtifactUtil.GetEndpointIndex(artifact); var endpointUrl = idpEndPoint.Metadata.GetIDPARSEndpoint(endpointIndex); Logger.DebugFormat(TraceMessages.ArtifactResolveForKnownIdentityProvider, artifact, idpEndPoint.Id, endpointUrl); var resolve = Saml20ArtifactResolve.GetDefault(config.ServiceProvider.Id); resolve.Artifact = artifact; var doc = resolve.GetXml(); if (doc.FirstChild is XmlDeclaration) { doc.RemoveChild(doc.FirstChild); } XmlSignatureUtils.SignDocument(doc, resolve.Id, config.ServiceProvider.SigningCertificate); var artifactResolveString = doc.OuterXml; Logger.DebugFormat(TraceMessages.ArtifactResolved, artifactResolveString); return GetResponse(endpointUrl, artifactResolveString, idpEndPoint.ArtifactResolution, relayState); }
/// <summary> /// Handles the artifact. /// </summary> /// <param name="context">The context.</param> protected void HandleArtifact(HttpContext context, Saml2Configuration config, Action<HttpContext, Stream, Saml2Configuration> handleSoap) { var builder = GetBuilder(context); var inputStream = builder.ResolveArtifact(context.Request.Params["SAMLart"], context.Request.Params["relayState"], ConfigurationFactory.Instance.Configuration); handleSoap(context, inputStream, config); }
/// <summary> /// Transfers the client. /// </summary> /// <param name="idp">The identity provider.</param> /// <param name="context">The context.</param> private void TransferClient(IdentityProvider idp, HttpContext context, Saml2Configuration config) { var request = Saml20LogoutRequest.GetDefault(config); // Determine which endpoint to use from the configuration file or the endpoint metadata. var destination = IdpSelectionUtil.DetermineEndpointConfiguration(BindingType.Redirect, idp.Endpoints.DefaultLogoutEndpoint, idp.Metadata.IDPSLOEndpoints); request.Destination = destination.Url; var nameIdFormat = (string)context.Session[IdpNameIdFormat]; request.SubjectToLogOut.Format = nameIdFormat; // Handle POST binding if (destination.Binding == BindingType.Post) { var builder = new HttpPostBindingBuilder(destination); request.Destination = destination.Url; request.Reason = Saml20Constants.Reasons.User; request.SubjectToLogOut.Value = (string)context.Session[IdpNameId]; request.SessionIndex = (string)context.Session[IdpSessionIdKey]; var requestDocument = request.GetXml(); XmlSignatureUtils.SignDocument(requestDocument, request.Id, config.ServiceProvider.SigningCertificate); builder.Request = requestDocument.OuterXml; Logger.DebugFormat(TraceMessages.LogoutRequestSent, idp.Id, "POST", builder.Request); context.Response.Write(builder.GetPage()); context.Response.End(); return; } // Handle Redirect binding if (destination.Binding == BindingType.Redirect) { request.Destination = destination.Url; request.Reason = Saml20Constants.Reasons.User; request.SubjectToLogOut.Value = (string)context.Session[IdpNameId]; request.SessionIndex = (string)context.Session[IdpSessionIdKey]; var builder = new HttpRedirectBindingBuilder { Request = request.GetXml().OuterXml, SigningKey = config.ServiceProvider.SigningCertificate.PrivateKey }; var redirectUrl = destination.Url + (destination.Url.Contains("?") ? "&" : "?") + builder.ToQuery(); Logger.DebugFormat(TraceMessages.LogoutRequestSent, idp.Id, "REDIRECT", redirectUrl); context.Response.Redirect(redirectUrl, true); return; } // Handle Artifact binding if (destination.Binding == BindingType.Artifact) { request.Destination = destination.Url; request.Reason = Saml20Constants.Reasons.User; request.SubjectToLogOut.Value = (string)context.Session[IdpNameId]; request.SessionIndex = (string)context.Session[IdpSessionIdKey]; Logger.DebugFormat(TraceMessages.LogoutRequestSent, idp.Id, "ARTIFACT", request.GetXml().OuterXml); var builder = GetBuilder(context); builder.RedirectFromLogout(destination, request, Guid.NewGuid().ToString("N"), (s, o) => context.Cache.Insert(s, o, null, DateTime.Now.AddMinutes(1), Cache.NoSlidingExpiration)); } Logger.Error(ErrorMessages.EndpointBindingInvalid); throw new Saml20Exception(ErrorMessages.EndpointBindingInvalid); }
/// <summary> /// Initializes a new instance of the <see cref="Saml20MetadataDocument"/> class. /// </summary> /// <param name="config">The config.</param> /// <param name="keyinfo">key information for the service provider certificate.</param> /// <param name="sign">if set to <c>true</c> the metadata document will be signed.</param> public Saml20MetadataDocument(Saml2Configuration config, KeyInfo keyinfo, bool sign) : this(sign) { ConvertToMetadata(config, keyinfo); }
public SamlMetadataWriter(Saml2Configuration configuration) { this.configuration = configuration; logger = LoggerProvider.LoggerFor(typeof(Saml2Configuration)); }
/// <summary> /// Takes the configuration class and converts it to a SAML2.0 metadata document. /// </summary> /// <param name="config">The config.</param> /// <param name="keyInfo">The keyInfo.</param> private void ConvertToMetadata(Saml2Configuration config, KeyInfo keyInfo) { var entity = CreateDefaultEntity(); entity.EntityID = config.ServiceProvider.Id; entity.ValidUntil = DateTime.Now.AddDays(7); var serviceProviderDescriptor = new SpSsoDescriptor { ProtocolSupportEnumeration = new[] { Saml20Constants.Protocol }, AuthnRequestsSigned = XmlConvert.ToString(true), WantAssertionsSigned = XmlConvert.ToString(true) }; if (config.ServiceProvider.NameIdFormats.Count > 0) { serviceProviderDescriptor.NameIdFormat = new string[config.ServiceProvider.NameIdFormats.Count]; var count = 0; foreach (var elem in config.ServiceProvider.NameIdFormats) { serviceProviderDescriptor.NameIdFormat[count++] = elem.Format; } } var baseUrl = new Uri(config.ServiceProvider.Server); var logoutServiceEndpoints = new List<Endpoint>(); var signonServiceEndpoints = new List<IndexedEndpoint>(); var artifactResolutionEndpoints = new List<IndexedEndpoint>(2); // Include endpoints. foreach (var endpoint in config.ServiceProvider.Endpoints) { if (endpoint.Type == EndpointType.SignOn) { var loginEndpoint = new IndexedEndpoint { Index = endpoint.Index, IsDefault = true, Location = new Uri(baseUrl, endpoint.LocalPath).ToString(), Binding = GetBinding(endpoint.Binding, Saml20Constants.ProtocolBindings.HttpPost) }; signonServiceEndpoints.Add(loginEndpoint); var artifactSignonEndpoint = new IndexedEndpoint { Binding = Saml20Constants.ProtocolBindings.HttpSoap, Index = loginEndpoint.Index, Location = loginEndpoint.Location }; artifactResolutionEndpoints.Add(artifactSignonEndpoint); continue; } if (endpoint.Type == EndpointType.Logout) { var logoutEndpoint = new Endpoint { Location = new Uri(baseUrl, endpoint.LocalPath).ToString() }; logoutEndpoint.ResponseLocation = logoutEndpoint.Location; logoutEndpoint.Binding = GetBinding(endpoint.Binding, Saml20Constants.ProtocolBindings.HttpPost); logoutServiceEndpoints.Add(logoutEndpoint); // TODO: Look at this... logoutEndpoint = new Endpoint { Location = new Uri(baseUrl, endpoint.LocalPath).ToString() }; logoutEndpoint.ResponseLocation = logoutEndpoint.Location; logoutEndpoint.Binding = GetBinding(endpoint.Binding, Saml20Constants.ProtocolBindings.HttpRedirect); logoutServiceEndpoints.Add(logoutEndpoint); var artifactLogoutEndpoint = new IndexedEndpoint { Binding = Saml20Constants.ProtocolBindings.HttpSoap, Index = endpoint.Index, Location = logoutEndpoint.Location }; artifactResolutionEndpoints.Add(artifactLogoutEndpoint); continue; } } serviceProviderDescriptor.SingleLogoutService = logoutServiceEndpoints.ToArray(); serviceProviderDescriptor.AssertionConsumerService = signonServiceEndpoints.ToArray(); // Attribute consuming service. if (config.Metadata.RequestedAttributes.Count > 0) { var attConsumingService = new AttributeConsumingService(); serviceProviderDescriptor.AttributeConsumingService = new[] { attConsumingService }; attConsumingService.Index = signonServiceEndpoints[0].Index; attConsumingService.IsDefault = true; attConsumingService.ServiceName = new[] { new LocalizedName("SP", "en") }; attConsumingService.RequestedAttribute = new RequestedAttribute[config.Metadata.RequestedAttributes.Count]; for (var i = 0; i < config.Metadata.RequestedAttributes.Count; i++) { attConsumingService.RequestedAttribute[i] = new RequestedAttribute { Name = config.Metadata.RequestedAttributes[i].Name }; if (config.Metadata.RequestedAttributes[i].IsRequired) { attConsumingService.RequestedAttribute[i].IsRequired = true; } attConsumingService.RequestedAttribute[i].NameFormat = SamlAttribute.NameformatBasic; } } else { serviceProviderDescriptor.AttributeConsumingService = new AttributeConsumingService[0]; } if (config.Metadata == null || !config.Metadata.ExcludeArtifactEndpoints) { serviceProviderDescriptor.ArtifactResolutionService = artifactResolutionEndpoints.ToArray(); } entity.Items = new object[] { serviceProviderDescriptor }; // Keyinfo var keySigning = new KeyDescriptor(); var keyEncryption = new KeyDescriptor(); serviceProviderDescriptor.KeyDescriptor = new[] { keySigning, keyEncryption }; keySigning.Use = KeyTypes.Signing; keySigning.UseSpecified = true; keyEncryption.Use = KeyTypes.Encryption; keyEncryption.UseSpecified = true; // Ugly conversion between the .Net framework classes and our classes ... avert your eyes!! keySigning.KeyInfo = Serialization.DeserializeFromXmlString<Schema.XmlDSig.KeyInfo>(keyInfo.GetXml().OuterXml); keyEncryption.KeyInfo = keySigning.KeyInfo; // apply the <Organization> element if (config.Metadata.Organization != null) { entity.Organization = new Schema.Metadata.Organization { OrganizationName = new[] { new LocalizedName { Value = config.Metadata.Organization.Name } }, OrganizationDisplayName = new[] { new LocalizedName { Value = config.Metadata.Organization.DisplayName } }, OrganizationURL = new[] { new LocalizedURI { Value = config.Metadata.Organization.Url } } }; } if (config.Metadata.Contacts != null && config.Metadata.Contacts.Any()) { entity.ContactPerson = config.Metadata.Contacts.Select(x => new Schema.Metadata.Contact { ContactType = (Schema.Metadata.ContactType) ((int)x.Type), Company = x.Company, GivenName = x.GivenName, SurName = x.SurName, EmailAddress = new[] { x.Email }, TelephoneNumber = new[] { x.Phone } }).ToArray(); } }
public Logout(IInternalLogger logger, SAML2.Config.Saml2Configuration config) { this.logger = logger ?? throw new ArgumentNullException("logger"); this.config = config ?? throw new ArgumentNullException("config"); }
private string AuthnRequestForIdp(IdentityProvider identityProvider, Saml20AuthnRequest request, IOwinContext context, Saml2Configuration config) { var logger = SAML2.Logging.LoggerProvider.LoggerFor(typeof(SamlMessage)); context.Set(IdpTempSessionKey, identityProvider.Id); // Determine which endpoint to use from the configuration file or the endpoint metadata. var destination = IdpSelectionUtil.DetermineEndpointConfiguration(BindingType.Redirect, identityProvider.Endpoints.DefaultSignOnEndpoint, identityProvider.Metadata.SSOEndpoints); request.Destination = destination.Url; if (identityProvider.ForceAuth) { request.ForceAuthn = true; } // Check isPassive status if (context.Get<bool>(IdpIsPassive)) { request.IsPassive = true; } if (identityProvider.IsPassive) { request.IsPassive = true; } // Check if request should forceAuthn if (context.Get<bool>(IdpForceAuthn)) { request.ForceAuthn = true; } // Check if protocol binding should be forced if (identityProvider.Endpoints.DefaultSignOnEndpoint != null) { if (!string.IsNullOrEmpty(identityProvider.Endpoints.DefaultSignOnEndpoint.ForceProtocolBinding)) { request.ProtocolBinding = identityProvider.Endpoints.DefaultSignOnEndpoint.ForceProtocolBinding; } } // Save request message id to session Utility.AddExpectedResponseId(request.Id); switch (destination.Binding) { case BindingType.Redirect: logger.DebugFormat(TraceMessages.AuthnRequestPrepared, identityProvider.Id, Saml20Constants.ProtocolBindings.HttpRedirect); var redirectBuilder = new HttpRedirectBindingBuilder { SigningKey = config.ServiceProvider.SigningCertificate.PrivateKey, Request = request.GetXml().OuterXml }; if (context.Authentication != null && context.Authentication.AuthenticationResponseChallenge != null && context.Authentication.AuthenticationResponseChallenge.Properties != null && context.Authentication.AuthenticationResponseChallenge.Properties.Dictionary != null && context.Authentication.AuthenticationResponseChallenge.Properties.Dictionary.Count > 0) redirectBuilder.RelayState = context.Authentication.AuthenticationResponseChallenge.Properties.Dictionary.ToDelimitedString(); logger.DebugFormat(TraceMessages.AuthnRequestSent, redirectBuilder.Request); var redirectLocation = request.Destination + "?" + redirectBuilder.ToQuery(); return redirectLocation; case BindingType.Post: throw new NotImplementedException(); //logger.DebugFormat(TraceMessages.AuthnRequestPrepared, identityProvider.Id, Saml20Constants.ProtocolBindings.HttpPost); //var postBuilder = new HttpPostBindingBuilder(destination); //// Honor the ForceProtocolBinding and only set this if it's not already set //if (string.IsNullOrEmpty(request.ProtocolBinding)) { // request.ProtocolBinding = Saml20Constants.ProtocolBindings.HttpPost; //} //var requestXml = request.GetXml(); //XmlSignatureUtils.SignDocument(requestXml, request.Id, config.ServiceProvider.SigningCertificate); //postBuilder.Request = requestXml.OuterXml; //logger.DebugFormat(TraceMessages.AuthnRequestSent, postBuilder.Request); //context.Response.Write(postBuilder.GetPage()); //break; case BindingType.Artifact: throw new NotImplementedException(); //logger.DebugFormat(TraceMessages.AuthnRequestPrepared, identityProvider.Id, Saml20Constants.ProtocolBindings.HttpArtifact); //var artifactBuilder = new HttpArtifactBindingBuilder(context, config); //// Honor the ForceProtocolBinding and only set this if it's not already set //if (string.IsNullOrEmpty(request.ProtocolBinding)) { // request.ProtocolBinding = Saml20Constants.ProtocolBindings.HttpArtifact; //} //logger.DebugFormat(TraceMessages.AuthnRequestSent, request.GetXml().OuterXml); //artifactBuilder.RedirectFromLogin(destination, request); //break; default: logger.Error(SAML2.ErrorMessages.EndpointBindingInvalid); throw new Saml20Exception(SAML2.ErrorMessages.EndpointBindingInvalid); } throw new NotImplementedException(); }
/// <summary> /// Handles executing the logout. /// </summary> /// <param name="context">The context.</param> /// <param name="idpInitiated">if set to <c>true</c> identity provider is initiated.</param> private void DoLogout(HttpContext context, bool idpInitiated = false, Saml2Configuration config = null) { Logger.Debug(TraceMessages.LogoutActionsExecuting); // TODO: Event for logout actions //foreach (var action in Actions.Actions.GetActions(config)) //{ // Logger.DebugFormat("{0}.{1} called", action.GetType(), "LogoutAction()"); // action.LogoutAction(this, context, idpInitiated); // Logger.DebugFormat("{0}.{1} finished", action.GetType(), "LogoutAction()"); //} }
/// <summary> /// Handles the SOAP message. /// </summary> /// <param name="context">The context.</param> /// <param name="inputStream">The input stream.</param> private void HandleSoap(HttpContext context, Stream inputStream, Saml2Configuration config) { var parser = new HttpArtifactBindingParser(inputStream); Logger.DebugFormat(TraceMessages.SOAPMessageParse, parser.SamlMessage.OuterXml); var builder = GetBuilder(context); var idp = IdpSelectionUtil.RetrieveIDPConfiguration(parser.Issuer, config); if (parser.IsArtifactResolve) { Logger.DebugFormat(TraceMessages.ArtifactResolveReceived, parser.SamlMessage); if (!parser.CheckSamlMessageSignature(idp.Metadata.Keys)) { Logger.ErrorFormat(ErrorMessages.ArtifactResolveSignatureInvalid); throw new Saml20Exception(ErrorMessages.ArtifactResolveSignatureInvalid); } builder.RespondToArtifactResolve(parser.ArtifactResolve, parser.SamlMessage); } else if (parser.IsArtifactResponse) { Logger.DebugFormat(TraceMessages.ArtifactResponseReceived, parser.SamlMessage); if (!parser.CheckSamlMessageSignature(idp.Metadata.Keys)) { Logger.Error(ErrorMessages.ArtifactResponseSignatureInvalid); throw new Saml20Exception(ErrorMessages.ArtifactResponseSignatureInvalid); } var status = parser.ArtifactResponse.Status; if (status.StatusCode.Value != Saml20Constants.StatusCodes.Success) { Logger.ErrorFormat(ErrorMessages.ArtifactResponseStatusCodeInvalid, status.StatusCode.Value); throw new Saml20Exception(string.Format(ErrorMessages.ArtifactResponseStatusCodeInvalid, status.StatusCode.Value)); } if (parser.ArtifactResponse.Any.LocalName == LogoutRequest.ElementName) { Logger.DebugFormat(TraceMessages.LogoutRequestReceived, parser.ArtifactResponse.Any.OuterXml); var req = Serialization.DeserializeFromXmlString<LogoutRequest>(parser.ArtifactResponse.Any.OuterXml); // Send logoutresponse via artifact var response = new Saml20LogoutResponse { Issuer = config.ServiceProvider.Id, StatusCode = Saml20Constants.StatusCodes.Success, InResponseTo = req.Id }; var endpoint = IdpSelectionUtil.RetrieveIDPConfiguration((string)context.Session[IdpLoginSessionKey], config); var destination = IdpSelectionUtil.DetermineEndpointConfiguration(BindingType.Redirect, endpoint.Endpoints.DefaultLogoutEndpoint, endpoint.Metadata.IDPSLOEndpoints); builder.RedirectFromLogout(destination, response, context.Request.Params["relayState"], (s, o) => context.Cache.Insert(s, o, null, DateTime.Now.AddMinutes(1), Cache.NoSlidingExpiration)); } else if (parser.ArtifactResponse.Any.LocalName == LogoutResponse.ElementName) { DoLogout(context, false, config); } else { Logger.ErrorFormat(ErrorMessages.ArtifactResponseMissingResponse); throw new Saml20Exception(ErrorMessages.ArtifactResponseMissingResponse); } } else if (parser.IsLogoutReqest) { Logger.DebugFormat(TraceMessages.LogoutRequestReceived, parser.SamlMessage.OuterXml); var req = parser.LogoutRequest; // Build the response object var response = new Saml20LogoutResponse { Issuer = config.ServiceProvider.Id, StatusCode = Saml20Constants.StatusCodes.Success, InResponseTo = req.Id }; // response.Destination = destination.Url; var doc = response.GetXml(); XmlSignatureUtils.SignDocument(doc, response.Id, config.ServiceProvider.SigningCertificate); if (doc.FirstChild is XmlDeclaration) { doc.RemoveChild(doc.FirstChild); } SendResponseMessage(doc.OuterXml, context); } else { Logger.ErrorFormat(ErrorMessages.SOAPMessageUnsupportedSamlMessage); throw new Saml20Exception(ErrorMessages.SOAPMessageUnsupportedSamlMessage); } }
public SamlMessage(IOwinContext context, Saml2Configuration config, Saml20Assertion assertion) : this(null, context, config) { Assertion = assertion; }
/// <summary> /// Signs an XmlDocument with an xml signature using the signing certificate specified in the /// configuration file. /// </summary> /// <param name="doc">The XmlDocument to be signed</param> /// <param name="id">The id of the topmost element in the XmlDocument</param> public static void SignDocument(XmlDocument doc, string id, Saml2Configuration config) { SignDocument(doc, id, config.ServiceProvider.SigningCertificate); }
public SamlMessage(IFormCollection form, IOwinContext context, SAML2.Config.Saml2Configuration config) : this(form) { this.context = context; this.config = config; }
public void CanDecryptFOBSAssertion() { // Arrange var doc = AssertionUtil.LoadBase64EncodedXmlDocument(@"Assertions\fobs-assertion2"); var encryptedList = doc.GetElementsByTagName(EncryptedAssertion.ElementName, Saml20Constants.Assertion); // Do some mock configuration. var config = new Saml2Configuration { AllowedAudienceUris = new System.Collections.Generic.List<Uri>(), IdentityProviders = new IdentityProviders() }; config.AllowedAudienceUris.Add(new Uri("https://saml.safewhere.net")); config.IdentityProviders.AddByMetadataDirectory(@"Protocol\MetadataDocs\FOBS"); // Set it manually. var cert = new X509Certificate2(@"Certificates\SafewhereTest_SFS.pfx", "test1234"); var encryptedAssertion = new Saml20EncryptedAssertion((RSA)cert.PrivateKey); encryptedAssertion.LoadXml((XmlElement)encryptedList[0]); // Act encryptedAssertion.Decrypt(); // Retrieve metadata var assertion = new Saml20Assertion(encryptedAssertion.Assertion.DocumentElement, null, false, TestConfiguration.Configuration); var endp = config.IdentityProviders.FirstOrDefault(x => x.Id == assertion.Issuer); // Assert Assert.That(encryptedList.Count == 1); Assert.IsNotNull(endp, "Endpoint not found"); Assert.IsNotNull(endp.Metadata, "Metadata not found"); try { assertion.CheckValid(AssertionUtil.GetTrustedSigners(assertion.Issuer)); Assert.Fail("Verification should fail. Token does not include its signing key."); } catch (InvalidOperationException) { } Assert.IsNull(assertion.SigningKey, "Signing key is already present on assertion. Modify test."); //Assert.IsTrue("We have tested this next test" == ""); //Assert.That(assertion.CheckSignature(Saml20SignonHandler.GetTrustedSigners(endp.Metadata.GetKeys(KeyTypes.Signing), endp))); //Assert.IsNotNull(assertion.SigningKey, "Signing key was not set on assertion instance."); }
/// <summary> /// Handles the response. /// </summary> /// <param name="context">The context.</param> private void HandleResponse(HttpContext context, Saml2Configuration config) { var requestType = context.Request.RequestType; var requestParams = context.Request.Params; var requestUrl = context.Request.Url; new Logout(Logger, config).ValidateLogoutRequest(requestType, requestParams, requestUrl); // Log the user out locally DoLogout(context, false, config); }