public OutgoingSamlMessageHandlerTests() { _factory = new FakeSamlMessageFactory(); _binding = new FakeBinding(); _target = new FakeOutgoingSamlMessageHandler(_factory, new [] { _binding }); _options = new SamlOptions(); }
public void ShouldCreateFailureMessage() { var options = new SamlOptions { SignOutgoingMessages = false }; var samlLogoutResponseMessage = new SamlLogoutResponseMessage { Success = false }; var xmlDocument = _logoutResponseFactory.CreateMessage(options, samlLogoutResponseMessage); var mgr = new XmlNamespaceManager(xmlDocument.NameTable); mgr.AddNamespace("samlp", "urn:oasis:names:tc:SAML:2.0:protocol"); mgr.AddNamespace("saml", "urn:oasis:names:tc:SAML:2.0:assertion"); var logoutResponseNode = xmlDocument.SelectSingleNode("/samlp:LogoutResponse", mgr); var issuerNode = xmlDocument.SelectSingleNode("/samlp:LogoutResponse/saml:Issuer", mgr); var statusNode = xmlDocument.SelectSingleNode("/samlp:LogoutResponse/samlp:Status", mgr); var statusCodeNode = xmlDocument.SelectSingleNode("/samlp:LogoutResponse/samlp:Status/samlp:StatusCode", mgr); Assert.NotNull(logoutResponseNode); Assert.NotNull(issuerNode); Assert.NotNull(statusNode); Assert.NotNull(statusCodeNode); Assert.Equal("", logoutResponseNode.Attributes["Destination"].Value); Assert.Equal("2.0", logoutResponseNode.Attributes["Version"].Value); Assert.Equal("urn:oasis:names:tc:SAML:2.0:status:Responder", statusCodeNode.Attributes["Value"].Value); }
public void ShouldCreateSignedMessage() { var privateCert = new X509Certificate2(File.ReadAllBytes("PrivateTestCert.pfx"), "test"); var options = new SamlOptions { SignOutgoingMessages = true, ServiceProviderCertificate = privateCert }; var authnRequestMessage = new SamlLogoutRequestMessage { NameId = "test" }; var xmlDocument = _logoutRequestFactory.CreateMessage(options, authnRequestMessage); var mgr = new XmlNamespaceManager(xmlDocument.NameTable); mgr.AddNamespace("samlp", "urn:oasis:names:tc:SAML:2.0:protocol"); mgr.AddNamespace("saml", "urn:oasis:names:tc:SAML:2.0:assertion"); var logoutResponseNode = xmlDocument.SelectSingleNode("/samlp:LogoutRequest", mgr); var issuerNode = xmlDocument.SelectSingleNode("/samlp:LogoutRequest/saml:Issuer", mgr); var nameIDNode = xmlDocument.SelectSingleNode("/samlp:LogoutRequest/saml:NameID", mgr); var sessionIndexNode = xmlDocument.SelectSingleNode("/samlp:LogoutRequest/samlp:SessionIndex", mgr); Assert.NotNull(logoutResponseNode); Assert.NotNull(issuerNode); Assert.NotNull(nameIDNode); Assert.NotNull(sessionIndexNode); Assert.Equal("", logoutResponseNode.Attributes["Destination"].Value); Assert.Equal("2.0", logoutResponseNode.Attributes["Version"].Value); Assert.Equal("test", nameIDNode.InnerText); }
public TMessageContext Parse(XmlDocument message, SamlOptions options) { var msg = new TMessageContext(); var encryptedXml = new X509EncryptedXmlAdapter(message, options.ServiceProviderCertificate); encryptedXml.DecryptDocument(); var rootNode = message.SelectSingleNode($"{SamlAuthenticationDefaults.SamlProtocolNsPrefix}:{RootElementName}", SamlXmlExtensions.NamespaceManager); if (rootNode == null) { throw new ParsingException($"Element '{RootElementName}' missing."); } SamlXmlExtensions.ParseStandardElements((XmlElement)rootNode, msg); var signatureNode = message.DocumentElement.SelectSingleNode("ds:Signature", SamlXmlExtensions.NamespaceManager); if (signatureNode != null && options.IdentityProviderCertificate != null) { var signedXml = new SignedXml(message); signedXml.LoadXml((XmlElement)signatureNode); msg.HasValidSignature = signedXml.CheckSignature(options.IdentityProviderCertificate, false); } return(ParseInternal(rootNode, msg, options)); }
protected internal override HandleRequestResult HandleInternal(SamlOptions options, HttpContext httpContext, SamlAuthnResponseMessage messageContext, string relayState = null) { var principal = new ClaimsPrincipal(); foreach (var assertion in messageContext.Assertions) { var claims = new List <Claim>(); if (assertion.SessionIndex != null) { claims.Add(new Claim(SamlAuthenticationDefaults.SessionIndexClaimType, assertion.SessionIndex, ClaimValueTypes.String)); } if (assertion.SubjectNameId != null) { claims.Add(new Claim(ClaimTypes.NameIdentifier, assertion.SubjectNameId)); } claims.AddRange(options.ClaimsSelector(assertion.Attributes.ToList())); principal.AddIdentity(new ClaimsIdentity(claims, assertion.Issuer)); } var props = new AuthenticationProperties { RedirectUri = relayState, IssuedUtc = DateTime.Now }; var authTicket = new AuthenticationTicket(principal, props, SamlAuthenticationDefaults.AuthenticationScheme); return(HandleRequestResult.Success(authTicket)); }
public void CreatingOptionsShouldReturnMostSecureOptions() { var options = new SamlOptions(); Assert.True(options.AcceptSignedMessagesOnly); Assert.True(options.SignOutgoingMessages); }
public void ShouldCreateMessage() { var options = new SamlOptions(); options.SignOutgoingMessages = false; var authnRequestMessage = new SamlAuthnRequestMessage(); authnRequestMessage.AssertionConsumerServiceUrl = "test"; var xmlDocument = _authnRequestFactory.CreateMessage(options, authnRequestMessage); XmlNamespaceManager mgr = new XmlNamespaceManager(xmlDocument.NameTable); mgr.AddNamespace("samlp", "urn:oasis:names:tc:SAML:2.0:protocol"); mgr.AddNamespace("saml", "urn:oasis:names:tc:SAML:2.0:assertion"); var authnRequestNode = xmlDocument.SelectSingleNode("/samlp:AuthnRequest", mgr); var issuerNode = xmlDocument.SelectSingleNode("/samlp:AuthnRequest/saml:Issuer", mgr); Assert.NotNull(authnRequestNode); Assert.NotNull(issuerNode); Assert.Equal("test", authnRequestNode.Attributes["AssertionConsumerServiceURL"].Value); Assert.Equal("", authnRequestNode.Attributes["Destination"].Value); Assert.Equal("2.0", authnRequestNode.Attributes["Version"].Value); }
public void ShouldCreateSignedMessage() { var privateCert = new X509Certificate2(File.ReadAllBytes("PrivateTestCert.pfx"), "test"); var options = new SamlOptions { SignOutgoingMessages = true, ServiceProviderCertificate = privateCert }; var authnRequestMessage = new SamlAuthnRequestMessage(); authnRequestMessage.AssertionConsumerServiceUrl = "test"; var xmlDocument = _authnRequestFactory.CreateMessage(options, authnRequestMessage); XmlNamespaceManager mgr = new XmlNamespaceManager(xmlDocument.NameTable); mgr.AddNamespace("samlp", "urn:oasis:names:tc:SAML:2.0:protocol"); mgr.AddNamespace("saml", "urn:oasis:names:tc:SAML:2.0:assertion"); var authnRequestNode = xmlDocument.SelectSingleNode("/samlp:AuthnRequest", mgr); var issuerNode = xmlDocument.SelectSingleNode("/samlp:AuthnRequest/saml:Issuer", mgr); Assert.NotNull(authnRequestNode); Assert.NotNull(issuerNode); Assert.Equal("test", authnRequestNode.Attributes["AssertionConsumerServiceURL"].Value); Assert.Equal("", authnRequestNode.Attributes["Destination"].Value); Assert.Equal("2.0", authnRequestNode.Attributes["Version"].Value); }
public XmlDocument CreateMessage(SamlOptions options, SamlLogoutRequestMessage message) { var doc = new XmlDocument(); var logoutRequestElement = doc.CreateElement(SamlAuthenticationDefaults.SamlProtocolNsPrefix, "LogoutRequest", SamlAuthenticationDefaults.SamlProtocolNamespace); SamlXmlExtensions.PropagateStandardElements(logoutRequestElement, message); var nameIdElement = doc.CreateElement(SamlAuthenticationDefaults.SamlAssertionNsPrefix, "NameID", SamlAuthenticationDefaults.SamlAssertionNamespace); nameIdElement.InnerText = message.NameId; var sessionIndexElement = doc.CreateElement(SamlAuthenticationDefaults.SamlProtocolNsPrefix, "SessionIndex", SamlAuthenticationDefaults.SamlProtocolNamespace); sessionIndexElement.InnerText = message.SessionIndex; logoutRequestElement.AppendChild(nameIdElement); logoutRequestElement.AppendChild(sessionIndexElement); doc.AppendChild(logoutRequestElement); if (options.SignOutgoingMessages) { SamlXmlExtensions.SignElement(logoutRequestElement, options); } return(doc); }
public XmlDocument CreateMessage(SamlOptions options, SamlLogoutResponseMessage message) { var doc = new XmlDocument(); var logoutResponseElement = doc.CreateElement(SamlAuthenticationDefaults.SamlProtocolNsPrefix, "LogoutResponse", SamlAuthenticationDefaults.SamlProtocolNamespace); SamlXmlExtensions.PropagateStandardElements(logoutResponseElement, message); if (message.InResponseTo != null) { logoutResponseElement.SetAttribute("InResponseTo", message.InResponseTo); } var statusvalue = (message.Success) ? "urn:oasis:names:tc:SAML:2.0:status:Success" : "urn:oasis:names:tc:SAML:2.0:status:Responder"; var statusElement = doc.CreateElement(SamlAuthenticationDefaults.SamlProtocolNsPrefix, "Status", SamlAuthenticationDefaults.SamlProtocolNamespace); var statusCodeElement = doc.CreateElement(SamlAuthenticationDefaults.SamlProtocolNsPrefix, "StatusCode", SamlAuthenticationDefaults.SamlProtocolNamespace); statusCodeElement.SetAttribute("Value", statusvalue); statusElement.AppendChild(statusCodeElement); logoutResponseElement.AppendChild(statusElement); doc.AppendChild(logoutResponseElement); if (options.SignOutgoingMessages) { SamlXmlExtensions.SignElement(logoutResponseElement, options); } return(doc); }
protected internal override void Validate(SamlOptions options, ExtractionResult extractionResult, SamlAuthnResponseMessage messageContext) { if ((DateTime.UtcNow - messageContext.IssueInstant) > options.IssueInstantExpiration) { throw new SamlException("Issue instant is too long ago."); } }
protected internal virtual void Validate(SamlOptions options, ExtractionResult extractionResult, TMessageContext messageContext) { if (options.AcceptSignedMessagesOnly && (!extractionResult.HasValidSignature && !messageContext.HasValidSignature)) { throw new SamlException("Messages without or with invalid signatures are not accepted."); } }
public void Handle(SamlOptions options, HttpContext context, TMessageContext messageContext, string target, SamlBindingBehavior bindingBehavior, string relayState = null) { //TODO: Extract creation and preparation of messageContext to a separate method. var document = MessageFactory.CreateMessage(options, messageContext); GetBinding(bindingBehavior).BindMessage(document, context, target, FlowKey, options, relayState); }
public void ShouldThrowOnMissingServiceProviderEntityId() { var options = new SamlOptions { ServiceProviderEntityId = null, }; Assert.Throws <ArgumentException>(() => _target.PostConfigure(string.Empty, options)); }
public EncodingHelperTests() { _options = new SamlOptions(); _message = new XmlDocument(); var el = _message.CreateElement("Message"); _message.AppendChild(el); }
internal static QueryString EncodeMessage(SamlOptions options, string encoding, XmlDocument message, string samlFlowKey, string relayState) { if (encoding == SamlAuthenticationDefaults.DeflateEncoding) { return(SamlDeflateEncoding(options, message, samlFlowKey, relayState)); } throw new NotImplementedException(); }
protected internal override SamlLogoutResponseMessage HandleInternal(SamlOptions options, HttpContext httpContext, SamlLogoutRequestMessage messageContext, string relayState = null) { httpContext.SignOutAsync(options.SignInScheme).GetAwaiter().GetResult(); return(new SamlLogoutResponseMessage { Destination = options.IdentityProviderLogOutUrl, Success = true, Issuer = options.ServiceProviderEntityId, InResponseTo = messageContext.Id }); }
public HttpRedirectBindingHandlerTests() { _target = new HttpRedirectBindingHandler(); _message = new XmlDocument(); var root = _message.CreateElement("Message"); _message.AppendChild(root); _options = new SamlOptions(); _ctx = new DefaultHttpContext(); _uri = "http://test.com:8080/saml-idp"; }
public void ShouldThrowOnMissingIdentityProviderCertificate() { var options = new SamlOptions { ServiceProviderEntityId = "MyId", AcceptSignedMessagesOnly = true, IdentityProviderCertificate = null, }; var ex = Assert.Throws <InvalidOperationException>(() => _target.PostConfigure(string.Empty, options)); Assert.Equal("Identity Provider Certificate needed for validating signatures of incoming messages. Set AcceptSignedResponsesOnly to false if signature validation is not intended.", ex.Message); }
protected internal override object HandleInternal(SamlOptions options, HttpContext httpContext, SamlLogoutResponseMessage messageContext, string relayState = null) { if (messageContext.Success) { httpContext.SignOutAsync(options.SignInScheme).Wait(); } if (relayState != null) { httpContext.Response.Redirect(relayState); } return(null); }
private static ExtractionResult ExtractMessage(HttpContext context, SamlOptions options) { if (HttpMethods.IsGet(context.Request.Method)) { return(ExtractionHelper.ExtractHttpRedirect(context.Request.Query, options.IdentityProviderCertificate)); } if (HttpMethods.IsPost(context.Request.Method)) { return(ExtractionHelper.ExtractHttpPost(context.Request.Form, options.IdentityProviderCertificate)); } //HTTP-Methods other than GET and POST are not supported throw new InvalidOperationException($"HTTP-Method {context.Request.Method} is not supported."); }
public THandlingResult Handle(SamlOptions options, HttpContext context) { if (context == null) { throw new ArgumentNullException(nameof(context)); } var result = ExtractMessage(context, options); var messageContext = MessageParser.Parse(result.Message, options); Validate(options, result, messageContext); return(HandleInternal(options, context, messageContext, result.RelayState)); }
public void ShouldThrowOnMissingServiceProviderCertificate() { var options = new SamlOptions { ServiceProviderEntityId = "MyId", ServiceProviderCertificate = null, AcceptSignedMessagesOnly = false, IdentityProviderCertificate = null, SignOutgoingMessages = true }; var ex = Assert.Throws <InvalidOperationException>(() => _target.PostConfigure(string.Empty, options)); Assert.Equal("Service Provider Certificate needed for signing outgoing messages. Set SignOutgoingMessages to false if signing is not intended.", ex.Message); }
public void ShouldThrowOnMissingIdentityProviderSignOnUrl() { var options = new SamlOptions { ServiceProviderEntityId = "MyId", ServiceProviderCertificate = null, AcceptSignedMessagesOnly = false, IdentityProviderCertificate = null, SignOutgoingMessages = false, IdentityProviderSignOnUrl = null, IdentityProviderLogOutUrl = null, }; var ex = Assert.Throws <InvalidOperationException>(() => _target.PostConfigure(string.Empty, options)); Assert.Equal("URL for SignOn needed", ex.Message); }
public void ShouldSetClaimsSelector() { var options = new SamlOptions { ServiceProviderEntityId = "MyId", ServiceProviderCertificate = null, AcceptSignedMessagesOnly = false, IdentityProviderCertificate = null, SignOutgoingMessages = false, IdentityProviderSignOnUrl = "ips-sign-on-url", IdentityProviderLogOutUrl = "ips-log-out-url", ClaimsSelector = null, }; _target.PostConfigure(string.Empty, options); Assert.NotNull(options.ClaimsSelector); }
private static QueryString SamlDeflateEncoding(SamlOptions options, XmlDocument message, string samlFlowKey, string relayState) { //See https://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf for details var dict = new Dictionary <string, string>(); var signatureElement = message.RemoveSignature(); var messageBytes = message.Deflate(); var base64Message = Convert.ToBase64String(messageBytes); dict.Add(samlFlowKey, base64Message); if (!string.IsNullOrEmpty(relayState)) { dict.Add(SamlAuthenticationDefaults.RelayStateKey, relayState); } if (signatureElement != null) { //Construct Signature byte[] signature; var stringBuilder = new StringBuilder(); stringBuilder.Append($"{samlFlowKey}={WebUtility.UrlEncode(dict[samlFlowKey])}"); if (!string.IsNullOrEmpty(relayState)) { stringBuilder.Append($"&{SamlAuthenticationDefaults.RelayStateKey}={WebUtility.UrlEncode(relayState)}"); } dict.Add(SamlAuthenticationDefaults.SignatureAlgorithmKey, "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"); stringBuilder.Append($"&{SamlAuthenticationDefaults.SignatureAlgorithmKey}={WebUtility.UrlEncode(dict[SamlAuthenticationDefaults.SignatureAlgorithmKey])}"); using (var rsa = options.ServiceProviderCertificate.GetRSAPrivateKey()) { var signatureData = Encoding.UTF8.GetBytes(stringBuilder.ToString()); signature = rsa.SignData(signatureData, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); } dict.Add(SamlAuthenticationDefaults.SignatureKey, Convert.ToBase64String(signature)); } return(QueryString.Create(dict)); }
public HttpPostBindingHandlerTests() { _target = new HttpPostBindingHandler(); _ctx = new DefaultHttpContext(); _ctx.Features.Set <IHttpResponseFeature>(new HttpResponseFeature { Body = new MemoryStream() }); _uri = "http://test.com/saml-idp"; _message = new XmlDocument(); var el = _message.CreateElement("Message"); _message.AppendChild(el); _options = new SamlOptions(); }
public XmlDocument CreateMessage(SamlOptions options, SamlAuthnRequestMessage authnRequestMessage) { var doc = new XmlDocument(); var authnRequestElement = doc.CreateElement(SamlAuthenticationDefaults.SamlProtocolNsPrefix, "AuthnRequest", SamlAuthenticationDefaults.SamlProtocolNamespace); SamlXmlExtensions.PropagateStandardElements(authnRequestElement, authnRequestMessage); authnRequestElement.SetAttribute("AssertionConsumerServiceURL", authnRequestMessage.AssertionConsumerServiceUrl); doc.AppendChild(authnRequestElement); if (options.SignOutgoingMessages) { SamlXmlExtensions.SignElement(authnRequestElement, options); } return(doc); }
public void ShouldDefaultCallpackPathAndLogoutPath() { var options = new SamlOptions { ServiceProviderEntityId = "MyId", ServiceProviderCertificate = null, AcceptSignedMessagesOnly = false, AcceptSignedAssertionsOnly = false, IdentityProviderCertificate = null, SignOutgoingMessages = false, IdentityProviderSignOnUrl = "ips-sign-on-url", IdentityProviderLogOutUrl = "ips-log-out-url", CallbackPath = null, LogoutPath = null, }; _target.PostConfigure(string.Empty, options); Assert.Equal("/saml-auth", options.CallbackPath); Assert.Equal("/saml-logout", options.LogoutPath); }
public IncomingSamlMessageHandlerTests() { _parser = new FakeSamlMessageParserBase { ParseResult = new FakeSamlMessage() }; _target = new FakeIncomingSamlMessageHandler(_parser); _options = new SamlOptions { AcceptSignedMessagesOnly = false }; _context = new DefaultHttpContext(); _context.Request.Method = "GET"; var dict = new Dictionary <string, StringValues> { { SamlAuthenticationDefaults.SamlRequestKey, Convert.ToBase64String(Encoding.UTF8.GetBytes("<Payload/>")) } }; _context.Request.Query = new QueryCollection(dict); }