public void AcsCommand_Run_WithReturnUrl_SuccessfulResult() { var idp = Options.FromConfiguration.IdentityProviders.Default; var response = @"<saml2p:Response xmlns:saml2p=""urn:oasis:names:tc:SAML:2.0:protocol"" xmlns:saml2=""urn:oasis:names:tc:SAML:2.0:assertion"" ID = """ + MethodBase.GetCurrentMethod().Name + @""" InResponseTo = ""InResponseToId"" Version=""2.0"" IssueInstant=""2013-01-01T00:00:00Z""> <saml2:Issuer> https://idp.example.com </saml2:Issuer> <saml2p:Status> <saml2p:StatusCode Value=""urn:oasis:names:tc:SAML:2.0:status:Success"" /> </saml2p:Status> <saml2:Assertion Version=""2.0"" ID=""" + MethodBase.GetCurrentMethod().Name + @"_Assertion2"" IssueInstant=""2013-09-25T00:00:00Z""> <saml2:Issuer>https://idp.example.com</saml2:Issuer> <saml2:Subject> <saml2:NameID>SomeUser</saml2:NameID> <saml2:SubjectConfirmation Method=""urn:oasis:names:tc:SAML:2.0:cm:bearer"" /> </saml2:Subject> <saml2:Conditions NotOnOrAfter=""2100-01-01T00:00:00Z"" /> </saml2:Assertion> </saml2p:Response>"; var responseFormValue = Convert.ToBase64String (Encoding.UTF8.GetBytes(SignedXmlHelper.SignXml(response))); var relayStateFormValue = "rs1234"; var r = new HttpRequestData( "POST", new Uri("http://localhost"), "/ModulePath", new KeyValuePair <string, IEnumerable <string> >[] { new KeyValuePair <string, IEnumerable <string> >("SAMLResponse", new string[] { responseFormValue }), new KeyValuePair <string, IEnumerable <string> >("RelayState", new string[] { relayStateFormValue }) }, new StoredRequestState( new EntityId("https://idp.example.com"), new Uri("http://localhost/testUrl.aspx"), new Saml2Id("InResponseToId"), null) ); var ids = new ClaimsIdentity[] { new ClaimsIdentity("Federation"), new ClaimsIdentity("ClaimsAuthenticationManager") }; ids[0].AddClaim(new Claim(ClaimTypes.NameIdentifier, "SomeUser", null, "https://idp.example.com")); ids[1].AddClaim(new Claim(ClaimTypes.Role, "RoleFromClaimsAuthManager", null, "ClaimsAuthenticationManagerStub")); var expected = new CommandResult() { Principal = new ClaimsPrincipal(ids), HttpStatusCode = HttpStatusCode.SeeOther, Location = new Uri("http://localhost/testUrl.aspx"), ClearCookieName = "Kentor." + relayStateFormValue }; new AcsCommand().Run(r, StubFactory.CreateOptions()) .ShouldBeEquivalentTo(expected, opt => opt.IgnoringCyclicReferences()); }
public void Cleanup() { SignedXmlHelper.RemoveGlobalSha256XmlSignatureSupport(); }
public async Task Saml2Handler_Acs_Works() { var context = new Saml2HandlerTestContext(); context.HttpContext.Request.Method = "POST"; context.HttpContext.Request.Path = "/Saml2/Acs"; var authProps = new AuthenticationProperties() { IssuedUtc = new DateTimeOffset(DateTime.UtcNow) }; authProps.Items["Test"] = "TestValue"; var state = new StoredRequestState( new EntityId("https://idp.example.com"), new Uri("https://localhost/LoggedIn"), new Saml2Id("InResponseToId"), authProps.Items); var relayState = SecureKeyGenerator.CreateRelayState(); var cookieData = HttpRequestData.ConvertBinaryData( StubDataProtector.Protect(state.Serialize())); var cookieName = $"{StoredRequestState.CookieNameBase}{relayState}"; context.HttpContext.Request.Cookies = new StubCookieCollection( Enumerable.Repeat(new KeyValuePair <string, string>( cookieName, cookieData), 1)); var response = @"<saml2p:Response xmlns:saml2p=""urn:oasis:names:tc:SAML:2.0:protocol"" xmlns:saml2=""urn:oasis:names:tc:SAML:2.0:assertion"" ID = """ + MethodBase.GetCurrentMethod().Name + @""" Version=""2.0"" IssueInstant=""2013-01-01T00:00:00Z"" InResponseTo=""InResponseToId"" > <saml2:Issuer> https://idp.example.com </saml2:Issuer> <saml2p:Status> <saml2p:StatusCode Value=""urn:oasis:names:tc:SAML:2.0:status:Success"" /> </saml2p:Status> <saml2:Assertion Version=""2.0"" ID=""" + MethodBase.GetCurrentMethod().Name + @"_Assertion1"" IssueInstant=""2013-09-25T00:00:00Z""> <saml2:Issuer>https://idp.example.com</saml2:Issuer> <saml2:Subject> <saml2:NameID>SomeUser</saml2:NameID> <saml2:SubjectConfirmation Method=""urn:oasis:names:tc:SAML:2.0:cm:bearer"" /> </saml2:Subject> <saml2:Conditions NotOnOrAfter=""2100-01-01T00:00:00Z""> <saml2:AudienceRestriction> <saml2:Audience>http://sp.example.com/saml2</saml2:Audience> </saml2:AudienceRestriction> </saml2:Conditions> </saml2:Assertion> </saml2p:Response>"; var form = Substitute.For <IFormCollection>(); IEnumerator <KeyValuePair <string, StringValues> > formCollectionEnumerator = new KeyValuePair <string, StringValues>[] { new KeyValuePair <string, StringValues>( "SAMLResponse", new StringValues( Convert.ToBase64String( Encoding.UTF8.GetBytes(SignedXmlHelper.SignXml(response))))), new KeyValuePair <string, StringValues>( "RelayState", new StringValues(relayState)) }.AsEnumerable().GetEnumerator(); form.GetEnumerator().Returns(formCollectionEnumerator); context.HttpContext.Request.Form.Returns(form); var authService = Substitute.For <IAuthenticationService>(); context.HttpContext.RequestServices.GetService(typeof(IAuthenticationService)) .Returns(authService); ClaimsPrincipal principal = null; AuthenticationProperties actualAuthProps = null; await authService.SignInAsync( context.HttpContext, TestHelpers.defaultSignInScheme, Arg.Do <ClaimsPrincipal>(p => principal = p), Arg.Do <AuthenticationProperties>(ap => actualAuthProps = ap)); await context.Subject.HandleRequestAsync(); principal.HasClaim(ClaimTypes.NameIdentifier, "SomeUser").Should().BeTrue(); actualAuthProps.IssuedUtc.Should().Be(authProps.IssuedUtc); actualAuthProps.Items["Test"].Should().Be("TestValue"); context.HttpContext.Response.Headers["Location"].Single().Should().Be( state.ReturnUrl.OriginalString); context.HttpContext.Response.StatusCode.Should().Be(303); }
public async Task KentorAuthServicesAuthenticationMiddleware_UsesCommandResultLocation() { // For Owin middleware, the redirect uri is part of the // authentication properties, but we don't want to use it as it // is because it can be empty (e.g. on unsolicited responses // or until #182 is fixed). The redirect uri should be taken // from the commandresult location instead. var context = OwinTestHelpers.CreateOwinContext(); context.Request.Method = "POST"; var response = @"<saml2p:Response xmlns:saml2p=""urn:oasis:names:tc:SAML:2.0:protocol"" xmlns:saml2=""urn:oasis:names:tc:SAML:2.0:assertion"" ID = """ + MethodBase.GetCurrentMethod().Name + @""" Version=""2.0"" IssueInstant=""2013-01-01T00:00:00Z""> <saml2:Issuer> https://idp.example.com </saml2:Issuer> <saml2p:Status> <saml2p:StatusCode Value=""urn:oasis:names:tc:SAML:2.0:status:Success"" /> </saml2p:Status> <saml2:Assertion Version=""2.0"" ID=""" + MethodBase.GetCurrentMethod().Name + @"_Assertion1"" IssueInstant=""2013-09-25T00:00:00Z""> <saml2:Issuer>https://idp.example.com</saml2:Issuer> <saml2:Subject> <saml2:NameID>SomeUser</saml2:NameID> <saml2:SubjectConfirmation Method=""urn:oasis:names:tc:SAML:2.0:cm:bearer"" /> </saml2:Subject> <saml2:Conditions NotOnOrAfter=""2100-01-01T00:00:00Z"" /> </saml2:Assertion> </saml2p:Response>"; var bodyData = new KeyValuePair <string, string>[] { new KeyValuePair <string, string>("SAMLResponse", Convert.ToBase64String(Encoding.UTF8.GetBytes(SignedXmlHelper.SignXml(response)))) }; var encodedBodyData = new FormUrlEncodedContent(bodyData); context.Request.Body = encodedBodyData.ReadAsStreamAsync().Result; context.Request.ContentType = encodedBodyData.Headers.ContentType.ToString(); context.Request.Host = new HostString("localhost"); context.Request.Path = new PathString("/AuthServices/Acs"); var middleware = new KentorAuthServicesAuthenticationMiddleware(null, CreateAppBuilder(), new KentorAuthServicesAuthenticationOptions(true) { SignInAsAuthenticationType = "AuthType" }); await middleware.Invoke(context); context.Response.StatusCode.Should().Be(302); context.Response.Headers["Location"].Should().Be("http://localhost/LoggedIn"); }
public async Task KentorAuthServicesAuthenticationMiddleware_AcsWorks() { var context = OwinTestHelpers.CreateOwinContext(); context.Request.Method = "POST"; var state = new StoredRequestState(new EntityId("https://idp.example.com"), new Uri("http://localhost/LoggedIn"), new Saml2Id(MethodBase.GetCurrentMethod().Name + "RequestID"), new AuthenticationProperties()); ((AuthenticationProperties)state.RelayData).RedirectUri = state.ReturnUrl.OriginalString; ((AuthenticationProperties)state.RelayData).Dictionary["Test"] = "TestValue"; var relayState = RelayStateGenerator.CreateSecureKey(); PendingAuthnRequests.Add(relayState, state); var response = @"<saml2p:Response xmlns:saml2p=""urn:oasis:names:tc:SAML:2.0:protocol"" xmlns:saml2=""urn:oasis:names:tc:SAML:2.0:assertion"" ID = """ + MethodBase.GetCurrentMethod().Name + @""" Version=""2.0"" IssueInstant=""2013-01-01T00:00:00Z"" InResponseTo=""" + MethodBase.GetCurrentMethod().Name + @"RequestID"" > <saml2:Issuer> https://idp.example.com </saml2:Issuer> <saml2p:Status> <saml2p:StatusCode Value=""urn:oasis:names:tc:SAML:2.0:status:Success"" /> </saml2p:Status> <saml2:Assertion Version=""2.0"" ID=""" + MethodBase.GetCurrentMethod().Name + @"_Assertion1"" IssueInstant=""2013-09-25T00:00:00Z""> <saml2:Issuer>https://idp.example.com</saml2:Issuer> <saml2:Subject> <saml2:NameID>SomeUser</saml2:NameID> <saml2:SubjectConfirmation Method=""urn:oasis:names:tc:SAML:2.0:cm:bearer"" /> </saml2:Subject> <saml2:Conditions NotOnOrAfter=""2100-01-01T00:00:00Z"" /> </saml2:Assertion> </saml2p:Response>"; var bodyData = new KeyValuePair <string, string>[] { new KeyValuePair <string, string>("SAMLResponse", Convert.ToBase64String(Encoding.UTF8.GetBytes(SignedXmlHelper.SignXml(response)))), new KeyValuePair <string, string>("RelayState", relayState) }; var encodedBodyData = new FormUrlEncodedContent(bodyData); context.Request.Body = encodedBodyData.ReadAsStreamAsync().Result; context.Request.ContentType = encodedBodyData.Headers.ContentType.ToString(); context.Request.Host = new HostString("localhost"); context.Request.Path = new PathString("/AuthServices/Acs"); var signInAsAuthenticationType = "AuthType"; var ids = new ClaimsIdentity[] { new ClaimsIdentity(signInAsAuthenticationType), new ClaimsIdentity(signInAsAuthenticationType) }; ids[0].AddClaim(new Claim(ClaimTypes.NameIdentifier, "SomeUser", null, "https://idp.example.com")); ids[1].AddClaim(new Claim(ClaimTypes.Role, "RoleFromClaimsAuthManager", null, "ClaimsAuthenticationManagerStub")); var middleware = new KentorAuthServicesAuthenticationMiddleware(null, CreateAppBuilder(), StubFactory.CreateOwinOptions()); await middleware.Invoke(context); context.Response.StatusCode.Should().Be(302); context.Response.Headers["Location"].Should().Be("http://localhost/LoggedIn"); context.Authentication.AuthenticationResponseGrant.Principal.Identities .ShouldBeEquivalentTo(ids, opt => opt.IgnoringCyclicReferences()); context.Authentication.AuthenticationResponseGrant.Properties.RedirectUri .Should().Be("http://localhost/LoggedIn"); context.Authentication.AuthenticationResponseGrant.Properties.Dictionary["Test"] .Should().Be("TestValue"); }
public void AcsCommand_Run_CallsNotifications() { var messageId = MethodBase.GetCurrentMethod().Name; var response = @"<saml2p:Response xmlns:saml2p=""urn:oasis:names:tc:SAML:2.0:protocol"" xmlns:saml2=""urn:oasis:names:tc:SAML:2.0:assertion"" ID = """ + messageId + @""" Version=""2.0"" IssueInstant=""2013-01-01T00:00:00Z""> <saml2:Issuer> https://idp.example.com </saml2:Issuer> <saml2p:Status> <saml2p:StatusCode Value=""urn:oasis:names:tc:SAML:2.0:status:Success"" /> </saml2p:Status> <saml2:Assertion Version=""2.0"" ID=""" + messageId + @"_Assertion"" IssueInstant=""2013-09-25T00:00:00Z""> <saml2:Issuer>https://idp.example.com</saml2:Issuer> <saml2:Subject> <saml2:NameID>SomeUser</saml2:NameID> <saml2:SubjectConfirmation Method=""urn:oasis:names:tc:SAML:2.0:cm:bearer"" /> </saml2:Subject> <saml2:Conditions NotOnOrAfter=""2100-01-01T00:00:00Z"" /> </saml2:Assertion> </saml2p:Response>"; var formValue = Convert.ToBase64String(Encoding.UTF8.GetBytes( SignedXmlHelper.SignXml(response))); var requestData = new HttpRequestData( "POST", new Uri("http://localhost"), "/ModulePath", new KeyValuePair <string, string[]>[] { new KeyValuePair <string, string[]>("SAMLResponse", new string[] { formValue }) }, null); var options = StubFactory.CreateOptions(); var responseUnboundCalled = false; options.Notifications.MessageUnbound = ur => { ur.Should().NotBeNull(); responseUnboundCalled = true; }; CommandResult notifiedCommandResult = null; options.Notifications.AcsCommandResultCreated = (cr, r) => { notifiedCommandResult = cr; r.Id.Value.Should().Be(messageId); }; new AcsCommand().Run(requestData, options) .Should().BeSameAs(notifiedCommandResult); responseUnboundCalled.Should().BeTrue("the ResponseUnbound notification should have been called."); }
static IDictionary <string, string> GetContent() { var content = new Dictionary <string, string>(); content["/idpMetadata"] = string.Format( @"<EntityDescriptor xmlns=""urn:oasis:names:tc:SAML:2.0:metadata"" entityID=""http://*****:*****@"<EntityDescriptor xmlns=""urn:oasis:names:tc:SAML:2.0:metadata"" entityID=""http://*****:*****@"<EntityDescriptor xmlns=""urn:oasis:names:tc:SAML:2.0:metadata"" entityID=""http://other.entityid.example.com""> <IDPSSODescriptor protocolSupportEnumeration=""urn:oasis:names:tc:SAML:2.0:protocol"" WantAuthnRequestsSigned=""true""> <KeyDescriptor use=""signing""> {0} </KeyDescriptor> <SingleSignOnService Binding=""urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"" Location=""http://wrong.entityid.example.com/acs""/> </IDPSSODescriptor> </EntityDescriptor>", SignedXmlHelper.KeyInfoXml); content["/federationMetadata"] = string.Format( @"<EntitiesDescriptor xmlns=""urn:oasis:names:tc:SAML:2.0:metadata"" validUntil=""2100-01-01T14:43:15Z""> <EntityDescriptor entityID=""http://idp.federation.example.com/metadata""> <IDPSSODescriptor protocolSupportEnumeration=""urn:oasis:names:tc:SAML:2.0:protocol""> <KeyDescriptor use=""signing""> {0} </KeyDescriptor> <SingleSignOnService Binding=""urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"" Location=""http://idp.federation.example.com/ssoService"" /> </IDPSSODescriptor> </EntityDescriptor> <EntityDescriptor entityID=""http://sp.federation.example.com/metadata""> <SPSSODescriptor protocolSupportEnumeration=""urn:oasis:names:tc:SAML:2.0:protocol""> <AssertionConsumerService index=""0"" Binding=""urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"" Location=""http://sp.federation.example.com/acs"" /> </SPSSODescriptor> </EntityDescriptor> </EntitiesDescriptor> ", SignedXmlHelper.KeyInfoXml); var federationMetadataSigned = string.Format( @"<EntitiesDescriptor ID=""federationMetadataSigned"" xmlns=""urn:oasis:names:tc:SAML:2.0:metadata"" validUntil=""2100-01-01T14:43:15Z""> <EntityDescriptor entityID=""http://idp.signedfederation.example.com/metadata""> <IDPSSODescriptor protocolSupportEnumeration=""urn:oasis:names:tc:SAML:2.0:protocol""> <KeyDescriptor use=""signing""> {0} </KeyDescriptor> <SingleSignOnService Binding=""urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"" Location=""http://idp.signedfederation.example.com/ssoService"" /> </IDPSSODescriptor> </EntityDescriptor> </EntitiesDescriptor> ", SignedXmlHelper.KeyInfoXml); content["/federationMetadataSigned"] = SignedXmlHelper.SignXml(federationMetadataSigned); var federationMetadataSignedTampered = string.Format( @"<EntitiesDescriptor ID=""federationMetadataSignedTampered"" xmlns=""urn:oasis:names:tc:SAML:2.0:metadata"" validUntil=""2100-01-01T14:43:15Z""> <EntityDescriptor entityID=""http://idp.federation.example.com/metadata""> <IDPSSODescriptor protocolSupportEnumeration=""urn:oasis:names:tc:SAML:2.0:protocol""> <KeyDescriptor use=""signing""> {0} </KeyDescriptor> <SingleSignOnService Binding=""urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"" Location=""http://idp.federation.example.com/ssoService"" /> </IDPSSODescriptor> </EntityDescriptor> </EntitiesDescriptor> ", SignedXmlHelper.KeyInfoXml); federationMetadataSignedTampered = SignedXmlHelper.SignXml(federationMetadataSignedTampered); content["/federationMetadataSignedTampered"] = federationMetadataSignedTampered.Replace("ssoService", "tampered"); if (IdpAndFederationShortCacheDurationAvailable) { content["/federationMetadataVeryShortCacheDuration"] = string.Format( @"<EntitiesDescriptor xmlns=""urn:oasis:names:tc:SAML:2.0:metadata"" cacheDuration=""PT0.001S""> <EntityDescriptor entityID=""http://idp1.federation.example.com/metadata""> <IDPSSODescriptor protocolSupportEnumeration=""urn:oasis:names:tc:SAML:2.0:protocol""> <KeyDescriptor use=""signing""> {0} </KeyDescriptor> <SingleSignOnService Binding=""urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"" Location=""http://idp1.federation.example.com:{1}/ssoService"" /> </IDPSSODescriptor> </EntityDescriptor> <EntityDescriptor entityID=""http://idp2.federation.example.com/metadata""> <IDPSSODescriptor protocolSupportEnumeration=""urn:oasis:names:tc:SAML:2.0:protocol""> <KeyDescriptor use=""signing""> {0} </KeyDescriptor> <SingleSignOnService Binding=""urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"" Location=""http://idp2.federation.example.com:{1}/ssoService"" /> </IDPSSODescriptor> </EntityDescriptor> <EntityDescriptor entityID=""http://sp.federation.example.com/metadata""> <SPSSODescriptor protocolSupportEnumeration=""urn:oasis:names:tc:SAML:2.0:protocol""> <AssertionConsumerService index=""0"" Binding=""urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"" Location=""http://sp.federation.example.com/acs"" /> </SPSSODescriptor> </EntityDescriptor> </EntitiesDescriptor>", SignedXmlHelper.KeyInfoXml, IdpAndFederationVeryShortCacheDurationPort); } if (FederationVeryShortCacheDurationSecondAlternativeEnabled) { content["/federationMetadataVeryShortCacheDuration"] = string.Format( @"<EntitiesDescriptor xmlns=""urn:oasis:names:tc:SAML:2.0:metadata"" cacheDuration=""PT0.001S""> <EntityDescriptor entityID=""http://idp1.federation.example.com/metadata""> <IDPSSODescriptor protocolSupportEnumeration=""urn:oasis:names:tc:SAML:2.0:protocol""> <KeyDescriptor use=""signing""> {0} </KeyDescriptor> <SingleSignOnService Binding=""urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"" Location=""http://idp1.federation.example.com:{1}/ssoService"" /> </IDPSSODescriptor> </EntityDescriptor> <EntityDescriptor entityID=""http://idp3.federation.example.com/metadata""> <IDPSSODescriptor protocolSupportEnumeration=""urn:oasis:names:tc:SAML:2.0:protocol""> <KeyDescriptor use=""signing""> {0} </KeyDescriptor> <SingleSignOnService Binding=""urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"" Location=""http://idp3.federation.example.com:{1}/ssoService"" /> </IDPSSODescriptor> </EntityDescriptor> <EntityDescriptor entityID=""http://sp.federation.example.com/metadata""> <SPSSODescriptor protocolSupportEnumeration=""urn:oasis:names:tc:SAML:2.0:protocol""> <AssertionConsumerService index=""0"" Binding=""urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"" Location=""http://sp.federation.example.com/acs"" /> </SPSSODescriptor> </EntityDescriptor> </EntitiesDescriptor>", SignedXmlHelper.KeyInfoXml, IdpAndFederationVeryShortCacheDurationPort); } if (IdpAndFederationShortCacheDurationAvailable) { content["/federationMetadataShortCacheDuration"] = string.Format( @"<EntitiesDescriptor xmlns=""urn:oasis:names:tc:SAML:2.0:metadata"" cacheDuration=""PT0.500S""> <EntityDescriptor entityID=""http://idp1.federation.example.com/metadata""> <IDPSSODescriptor protocolSupportEnumeration=""urn:oasis:names:tc:SAML:2.0:protocol""> <KeyDescriptor use=""signing""> {0} </KeyDescriptor> <SingleSignOnService Binding=""urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"" Location=""http://idp1.federation.example.com/ssoService"" /> </IDPSSODescriptor> </EntityDescriptor> <EntityDescriptor entityID=""http://idp2.federation.example.com/metadata""> <IDPSSODescriptor protocolSupportEnumeration=""urn:oasis:names:tc:SAML:2.0:protocol""> <KeyDescriptor use=""signing""> {0} </KeyDescriptor> <SingleSignOnService Binding=""urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"" Location=""http://idp2.federation.example.com/ssoService"" /> </IDPSSODescriptor> </EntityDescriptor> <EntityDescriptor entityID=""http://sp.federation.example.com/metadata""> <SPSSODescriptor protocolSupportEnumeration=""urn:oasis:names:tc:SAML:2.0:protocol""> <AssertionConsumerService index=""0"" Binding=""urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"" Location=""http://sp.federation.example.com/acs"" /> </SPSSODescriptor> </EntityDescriptor> </EntitiesDescriptor>", SignedXmlHelper.KeyInfoXml); } content["/idpMetadataWithMultipleBindings"] = string.Format( @"<EntityDescriptor xmlns=""urn:oasis:names:tc:SAML:2.0:metadata"" entityID=""http://*****:*****@"<EntityDescriptor xmlns=""urn:oasis:names:tc:SAML:2.0:metadata"" entityID=""some-idp""> <IDPSSODescriptor protocolSupportEnumeration=""urn:oasis:names:tc:SAML:2.0:protocol""> <KeyDescriptor use=""signing""> {0} </KeyDescriptor> <SingleSignOnService Binding=""urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"" Location=""http://idp.example.com/SsoService"" /> </IDPSSODescriptor> </EntityDescriptor>", SignedXmlHelper.KeyInfoXml); content["/idpMetadataWithArtifactBinding"] = string.Format( @"<EntityDescriptor xmlns=""urn:oasis:names:tc:SAML:2.0:metadata"" entityID=""http://*****:*****@"<KeyDescriptor use=""signing"">{0}</KeyDescriptor>", IdpVeryShortCacheDurationIncludeInvalidKey ? "Gibberish" : SignedXmlHelper.KeyInfoXml2) : ""; content["/idpMetadataVeryShortCacheDuration"] = string.Format( @"<EntityDescriptor xmlns=""urn:oasis:names:tc:SAML:2.0:metadata"" entityID=""http://localhost:13428/idpMetadataVeryShortCacheDuration"" cacheDuration=""PT0.001S""> <IDPSSODescriptor protocolSupportEnumeration=""urn:oasis:names:tc:SAML:2.0:protocol""> {0} <SingleSignOnService Binding=""{1}"" Location=""http://localhost:{2}/acs""/> <ArtifactResolutionService index=""0"" Location=""http://localhost:{2}/ars"" Binding=""urn:oasis:names:tc:SAML:2.0:bindings:SOAP"" /> <SingleLogoutService Binding=""{1}"" Location=""http://localhost:{2}/logout"" ResponseLocation=""http://localhost:{2}/logoutResponse""/> </IDPSSODescriptor> </EntityDescriptor>", keyElement, IdpVeryShortCacheDurationBinding, IdpAndFederationVeryShortCacheDurationPort); } return(content); }
public void Saml2PostBinding_Bind_SignsXmlAndPreserversXmlDeclaration() { var message = new Saml2MessageImplementation { DestinationUrl = new Uri("http://www.example.com/acs"), XmlData = "<root ID=\"id\">\r\n <content>data</content>\r\n</root>", MessageName = "SAMLMessageName", RelayState = "ABC1234", SigningCertificate = SignedXmlHelper.TestCert, SigningAlgorithm = SecurityAlgorithms.RsaSha256Signature }; var signedXml = "<?xml version=\"1.0\" encoding=\"blaha\"?>\r\n" + SignedXmlHelper.SignXml(message.XmlData, true); var expectedValue = Convert.ToBase64String(Encoding.UTF8.GetBytes(signedXml)); var notificationCalled = false; var result = Saml2Binding.Get(Saml2BindingType.HttpPost).Bind(message, null, (m, xd, bt) => { xd.Declaration = new XDeclaration("1.0", "blaha", null); m.Should().BeSameAs(message); bt.Should().Be(Saml2BindingType.HttpPost); notificationCalled = true; }); var expected = new CommandResult() { ContentType = "text/html", Content = @"<?xml version=""1.0"" encoding=""UTF-8""?> <!DOCTYPE html PUBLIC ""-//W3C//DTD XHTML 1.1//EN"" ""http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd""> <html xmlns=""http://www.w3.org/1999/xhtml"" xml:lang=""en""> <head> <meta http-equiv=""Content-Security-Policy"" content=""script-src 'sha256-H3SVZBYrbqBt3ncrT/nNmOb6nwCjC12cPQzh5jnW4Y0='""> </head> <body> <noscript> <p> <strong>Note:</strong> Since your browser does not support JavaScript, you must press the Continue button once to proceed. </p> </noscript> <form action=""http://www.example.com/acs"" method=""post"" name=""sustainsysSamlPostBindingSubmit""> <div> <input type=""hidden"" name=""RelayState"" value=""ABC1234""/> <input type=""hidden"" name=""SAMLMessageName"" value=""" + expectedValue + @"""/> </div> <noscript> <div> <input type=""submit"" value=""Continue""/> </div> </noscript> </form> <script type=""text/javascript""> document.forms.sustainsysSamlPostBindingSubmit.submit(); </script> </body> </html>" }; result.Should().BeEquivalentTo(expected); notificationCalled.Should().BeTrue(); }
public void AcsCommand_Run_UsesIdpFromNotification() { var messageId = MethodBase.GetCurrentMethod().Name; var response = $@"<saml2p:Response xmlns:saml2p=""urn:oasis:names:tc:SAML:2.0:protocol"" xmlns:saml2=""urn:oasis:names:tc:SAML:2.0:assertion"" ID = ""{messageId}"" Version=""2.0"" InResponseTo=""InResponseToID"" IssueInstant=""2013-01-01T00:00:00Z""> <saml2:Issuer> https://other.idp.example.com </saml2:Issuer> <saml2p:Status> <saml2p:StatusCode Value=""urn:oasis:names:tc:SAML:2.0:status:Success"" /> </saml2p:Status> <saml2:Assertion Version=""2.0"" ID=""{messageId}_Assertion"" IssueInstant=""2013-09-25T00:00:00Z""> <saml2:Issuer>https://other.idp.example.com</saml2:Issuer> <saml2:Subject> <saml2:NameID>SomeUser</saml2:NameID> <saml2:SubjectConfirmation Method=""urn:oasis:names:tc:SAML:2.0:cm:bearer"" /> </saml2:Subject> <saml2:Conditions NotOnOrAfter=""2100-01-01T00:00:00Z"" /> <saml2:AuthnStatement AuthnInstant=""{DateTime.UtcNow.ToSaml2DateTimeString()}""> <saml2:AuthnContext> <saml2:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml2:AuthnContextClassRef> </saml2:AuthnContext> </saml2:AuthnStatement> </saml2:Assertion> </saml2p:Response>"; var formValue = Convert.ToBase64String(Encoding.UTF8.GetBytes( SignedXmlHelper.SignXml(response))); var relayData = new Dictionary <string, string> { { "key", "value" } }; var requestData = new HttpRequestData( "POST", new Uri("http://localhost"), "/ModulePath", new KeyValuePair <string, IEnumerable <string> >[] { new KeyValuePair <string, IEnumerable <string> >("SAMLResponse", new string[] { formValue }) }, new StoredRequestState( new EntityId("https://other.idp.example.com"), new Uri("http://localhost/testUrl.aspx"), new Saml2Id("InResponseToID"), relayData)); var options = StubFactory.CreateOptions(); options.Notifications.GetIdentityProvider = (idpEntityId, rd, opt) => { idpEntityId.Id.Should().Be("https://other.idp.example.com"); rd["key"].Should().Be("value"); var idp = new IdentityProvider(new EntityId("https://other.idp.example.com"), options.SPOptions); idp.SigningKeys.AddConfiguredKey(SignedXmlHelper.TestCert); return(idp); }; var subject = new AcsCommand(); var actual = subject.Run(requestData, options); actual.Principal.Claims.First().Issuer.Should().Be("https://other.idp.example.com"); }
public void Saml2Controller_Acs_Works() { var request = Substitute.For <HttpRequestBase>(); request.HttpMethod.Returns("POST"); var response = @"<saml2p:Response xmlns:saml2p=""urn:oasis:names:tc:SAML:2.0:protocol"" xmlns:saml2=""urn:oasis:names:tc:SAML:2.0:assertion"" ID = """ + MethodBase.GetCurrentMethod().Name + @""" Version=""2.0"" IssueInstant=""2013-01-01T00:00:00Z"" InResponseTo=""InResponseToId""> <saml2:Issuer> https://idp.example.com </saml2:Issuer> <saml2p:Status> <saml2p:StatusCode Value=""urn:oasis:names:tc:SAML:2.0:status:Success"" /> </saml2p:Status> <saml2:Assertion Version=""2.0"" ID=""" + MethodBase.GetCurrentMethod().Name + @"_Assertion1"" IssueInstant=""2013-09-25T00:00:00Z""> <saml2:Issuer>https://idp.example.com</saml2:Issuer> <saml2:Subject> <saml2:NameID>SomeUser</saml2:NameID> <saml2:SubjectConfirmation Method=""urn:oasis:names:tc:SAML:2.0:cm:bearer"" /> </saml2:Subject> <saml2:Conditions NotOnOrAfter=""2100-01-01T00:00:00Z"" /> </saml2:Assertion> </saml2p:Response>"; var formValue = Convert.ToBase64String(Encoding.UTF8.GetBytes( SignedXmlHelper.SignXml(response))); var relayState = "rs1234"; request.Form.Returns(new NameValueCollection() { { "SAMLResponse", formValue }, { "RelayState", relayState } }); request.Url.Returns(new Uri("http://url.example.com/url")); request.Cookies.Returns(new HttpCookieCollection()); request.Cookies.Add(new HttpCookie(StoredRequestState.CookieNameBase + relayState, HttpRequestData.ConvertBinaryData( MachineKey.Protect( new StoredRequestState(null, null, new Saml2Id("InResponseToId"), null).Serialize(), HttpRequestBaseExtensions.ProtectionPurpose)))); var httpContext = Substitute.For <HttpContextBase>(); httpContext.Request.Returns(request); var controller = new Saml2Controller(); controller.ControllerContext = new ControllerContext(httpContext, new RouteData(), controller); var expected = new { Permanent = false, Url = Saml2Controller.Options.SPOptions.ReturnUrl.OriginalString }; controller.Acs().As <RedirectResult>().ShouldBeEquivalentTo(expected); controller.Response.Received().SetCookie( Arg.Is <HttpCookie>(c => c.Expires.Year == 1970)); }
private void RelayStateAsReturnUrl(string relayState, IOptions options, [CallerMemberName] string caller = null) { if (string.IsNullOrEmpty(caller)) { throw new ArgumentNullException(nameof(caller)); } var response = @"<saml2p:Response xmlns:saml2p=""urn:oasis:names:tc:SAML:2.0:protocol"" xmlns:saml2=""urn:oasis:names:tc:SAML:2.0:assertion"" ID = """ + caller + @""" Version=""2.0"" IssueInstant=""2013-01-01T00:00:00Z""> <saml2:Issuer> https://idp5.example.com </saml2:Issuer> <saml2p:Status> <saml2p:StatusCode Value=""urn:oasis:names:tc:SAML:2.0:status:Success"" /> </saml2p:Status> <saml2:Assertion Version=""2.0"" ID=""" + caller + @"_Assertion"" IssueInstant=""2013-09-25T00:00:00Z""> <saml2:Issuer>https://idp5.example.com</saml2:Issuer> <saml2:Subject> <saml2:NameID>SomeUser</saml2:NameID> <saml2:SubjectConfirmation Method=""urn:oasis:names:tc:SAML:2.0:cm:bearer"" /> </saml2:Subject> <saml2:Conditions NotOnOrAfter=""2100-01-01T00:00:00Z"" /> </saml2:Assertion> </saml2p:Response>"; var responseFormValue = Convert.ToBase64String (Encoding.UTF8.GetBytes(SignedXmlHelper.SignXml(response))); var formData = new List <KeyValuePair <string, IEnumerable <string> > > { new KeyValuePair <string, IEnumerable <string> >("SAMLResponse", new string[] { responseFormValue }), }; if (relayState != null) { formData.Add(new KeyValuePair <string, IEnumerable <string> >("RelayState", new string[] { relayState })); } var r = new HttpRequestData( "POST", new Uri("http://localhost"), "/ModulePath", formData, null); var ids = new ClaimsIdentity[] { new ClaimsIdentity("Federation") }; ids[0].AddClaim(new Claim(ClaimTypes.NameIdentifier, "SomeUser", null, "https://idp5.example.com")); var expected = new CommandResult() { Principal = new ClaimsPrincipal(ids), HttpStatusCode = HttpStatusCode.SeeOther, Location = relayState != null ? new Uri(relayState, UriKind.RelativeOrAbsolute) : null, }; new AcsCommand().Run(r, options) .Location.OriginalString.Should().Be(relayState); }
public async Task KentorAuthServicesAuthenticationMiddleware_AcsWorks() { var context = OwinTestHelpers.CreateOwinContext(); context.Request.Method = "POST"; var authProps = new AuthenticationProperties() { IssuedUtc = new DateTime(1975, 05, 05, 05, 05, 05, DateTimeKind.Utc) }; authProps.Dictionary["Test"] = "TestValue"; var state = new StoredRequestState(new EntityId("https://idp.example.com"), new Uri("http://localhost/LoggedIn"), new Saml2Id("InResponseToId"), authProps.Dictionary); var relayState = SecureKeyGenerator.CreateRelayState(); var cookieData = HttpRequestData.ConvertBinaryData( CreateAppBuilder().CreateDataProtector( typeof(KentorAuthServicesAuthenticationMiddleware).FullName) .Protect(state.Serialize())); context.Request.Headers["Cookie"] = $"Kentor.{relayState}={cookieData}"; var response = @"<saml2p:Response xmlns:saml2p=""urn:oasis:names:tc:SAML:2.0:protocol"" xmlns:saml2=""urn:oasis:names:tc:SAML:2.0:assertion"" ID = """ + MethodBase.GetCurrentMethod().Name + @""" Version=""2.0"" IssueInstant=""2013-01-01T00:00:00Z"" InResponseTo=""InResponseToId"" > <saml2:Issuer> https://idp.example.com </saml2:Issuer> <saml2p:Status> <saml2p:StatusCode Value=""urn:oasis:names:tc:SAML:2.0:status:Success"" /> </saml2p:Status> <saml2:Assertion Version=""2.0"" ID=""" + MethodBase.GetCurrentMethod().Name + @"_Assertion1"" IssueInstant=""2013-09-25T00:00:00Z""> <saml2:Issuer>https://idp.example.com</saml2:Issuer> <saml2:Subject> <saml2:NameID>SomeUser</saml2:NameID> <saml2:SubjectConfirmation Method=""urn:oasis:names:tc:SAML:2.0:cm:bearer"" /> </saml2:Subject> <saml2:Conditions NotOnOrAfter=""2100-01-01T00:00:00Z"" /> </saml2:Assertion> </saml2p:Response>"; var bodyData = new KeyValuePair <string, string>[] { new KeyValuePair <string, string>("SAMLResponse", Convert.ToBase64String(Encoding.UTF8.GetBytes(SignedXmlHelper.SignXml(response)))), new KeyValuePair <string, string>("RelayState", relayState) }; var encodedBodyData = new FormUrlEncodedContent(bodyData); context.Request.Body = encodedBodyData.ReadAsStreamAsync().Result; context.Request.ContentType = encodedBodyData.Headers.ContentType.ToString(); context.Request.Host = new HostString("localhost"); context.Request.Path = new PathString("/AuthServices/Acs"); var signInAsAuthenticationType = "AuthType"; var ids = new ClaimsIdentity[] { new ClaimsIdentity(signInAsAuthenticationType), new ClaimsIdentity(signInAsAuthenticationType) }; ids[0].AddClaim(new Claim(ClaimTypes.NameIdentifier, "SomeUser", null, "https://idp.example.com")); ids[1].AddClaim(new Claim(ClaimTypes.Role, "RoleFromClaimsAuthManager", null, "ClaimsAuthenticationManagerStub")); var subject = new KentorAuthServicesAuthenticationMiddleware(null, CreateAppBuilder(), StubFactory.CreateOwinOptions()); await subject.Invoke(context); context.Response.StatusCode.Should().Be(303); context.Response.Headers["Location"].Should().Be("http://localhost/LoggedIn"); context.Response.Headers["Set-Cookie"].Should().Be($"Kentor.{relayState}=; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT"); context.Authentication.AuthenticationResponseGrant.Principal.Identities .ShouldBeEquivalentTo(ids, opt => opt.IgnoringCyclicReferences()); context.Authentication.AuthenticationResponseGrant.Properties.RedirectUri .Should().Be("http://localhost/LoggedIn", "the StoredRequestState.ReturnUrl should overtake the value in the AuthProperties and be stored in the AuthProps"); context.Authentication.AuthenticationResponseGrant.Properties.Dictionary["Test"] .Should().Be("TestValue"); context.Authentication.AuthenticationResponseGrant.Properties.IssuedUtc .Should().Be(authProps.IssuedUtc); }