public void MetadataCommand_Run_CallsNotifications()
        {
            var request = new HttpRequestData("GET", new Uri("http://localhost/AuthServices"));

            var options = StubFactory.CreateOptions();

            options.Notifications.MetadataCreated = (md, urls) =>
            {
                md.CacheDuration = new TimeSpan(0, 0, 17);
                urls.ApplicationUrl.Host.Should().Be("localhost");
            };

            CommandResult notifiedCommandResult = null;
            options.Notifications.MetadataCommandResultCreated = cr =>
            {
                notifiedCommandResult = cr;
            };

            var subject = new MetadataCommand();
            var actualCommandResult = subject.Run(request, options);
            actualCommandResult.Should().BeSameAs(notifiedCommandResult);

            var parsedResult = XElement.Parse(actualCommandResult.Content);
            parsedResult.Attribute("cacheDuration").Value
                .Should().Be("PT17S");
        }
        public void MetadataCommand_Run_CompleteMetadata()
        {
            var options = StubFactory.CreateOptions();
            options.SPOptions.DiscoveryServiceUrl = new Uri("http://ds.example.com");
            options.SPOptions.AuthenticateRequestSigningBehavior = SigningBehavior.Always;
            options.SPOptions.ServiceCertificates.Add(new ServiceCertificate()
            {
                Certificate = SignedXmlHelper.TestCertSignOnly,
                Use = CertificateUse.Signing,
                MetadataPublishOverride = MetadataPublishOverrideType.PublishUnspecified
            });

            var subject = new MetadataCommand().Run(request, options);

            var payloadXml = XmlHelpers.FromString(subject.Content);

            // Validate signature, location of it  and then drop it. It contains
            // a reference to the ID which makes it unsuitable for string matching.
            payloadXml.DocumentElement.IsSignedBy(SignedXmlHelper.TestCertSignOnly).Should().BeTrue();
            payloadXml.DocumentElement.FirstChild.LocalName.Should().Be("Signature");
            payloadXml.DocumentElement.FirstChild["KeyInfo"].Should().NotBeNull();
            payloadXml.DocumentElement.RemoveChild("Signature", SignedXml.XmlDsigNamespaceUrl);

            // Ignore the ID attribute, it is just filled with a GUID that can't be easily tested.
            payloadXml.DocumentElement.Attributes.Remove("ID");

            // Test and then drop validUntil, can't be text compared.
            DateTime.Parse(payloadXml.DocumentElement.Attributes["validUntil"].Value)
                .Should().BeCloseTo(DateTime.UtcNow.AddDays(24).ToLocalTime(), 2000);
            payloadXml.DocumentElement.Attributes.Remove("validUntil");

            var expectedXml =
            "<EntityDescriptor entityID=\"https://github.com/KentorIT/authservices\" cacheDuration=\"PT42S\" xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" xmlns=\"urn:oasis:names:tc:SAML:2.0:metadata\">"
            + "<SPSSODescriptor AuthnRequestsSigned=\"true\" WantAssertionsSigned=\"true\" protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol\">"
            + "<Extensions>"
            + "<DiscoveryResponse Binding=\"urn:oasis:names:tc:SAML:profiles:SSO:idp-discovery-protocol\" Location=\"http://localhost/AuthServices/SignIn\" index=\"0\" isDefault=\"true\" xmlns=\"urn:oasis:names:tc:SAML:profiles:SSO:idp-discovery-protocol\" />"
            + "</Extensions>"
            + "<KeyDescriptor><KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\"><X509Data><X509Certificate>MIIDIzCCAg+gAwIBAgIQg7mOjTf994NAVxZu4jqXpzAJBgUrDgMCHQUAMCQxIjAgBgNVBAMTGUtlbnRvci5BdXRoU2VydmljZXMuVGVzdHMwHhcNMTMwOTI1MTMzNTQ0WhcNMzkxMjMxMjM1OTU5WjAkMSIwIAYDVQQDExlLZW50b3IuQXV0aFNlcnZpY2VzLlRlc3RzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwVGpfvK9N//MnA5Jo1q2liyPR24406Dp25gv7LB3HK4DWgqsb7xXM6KIV/WVOyCV2g/O1ErBlB+HLhVZ4XUJvbqBbgAJqFO+TZwcCIe8u4nTEXeU660FdtkKClA17sbtMrAGdDfOPwVBHSuavdHeD7jHNI4RUDGKnEW13/0EvnHDilIetwODRxrX/+41R24sJThFbMczByS3OAL2dcIxoAynaGeM90gXsVYow1QhJUy21+cictikb7jW4mW6dvFCBrWIceom9J295DcQIHoxJy5NoZwMir/JV00qs1wDVoN20Ve1DC5ImwcG46XPF7efQ44yLh2j5Yexw+xloA81dwIDAQABo1kwVzBVBgNVHQEETjBMgBAWIahoZhXVUogbAqkS7zwfoSYwJDEiMCAGA1UEAxMZS2VudG9yLkF1dGhTZXJ2aWNlcy5UZXN0c4IQg7mOjTf994NAVxZu4jqXpzAJBgUrDgMCHQUAA4IBAQA2aGzmuKw4AYXWMhrGj5+i8vyAoifUn1QVOFsUukEA77CrqhqqaWFoeagfJp/45vlvrfrEwtF0QcWfmO9w1VvHwm7sk1G/cdYyJ71sU+llDsdPZm7LxQvWZYkK+xELcinQpSwt4ExavS+jLcHoOYHYwIZMBn3U8wZw7Kq29oGnoFQz7HLCEl/G9i3QRyvFITNlWTjoScaqMjHTzq6HCMaRsL09DLcY3KB+cedfpC0/MBlzaxZv0DctTulyaDfM9DCYOyokGN/rQ6qkAR0DDm8fVwknbJY7kURXNGoUetulTb5ow8BvD1gncOaYHSD0kbHZG+bLsUZDFatEr2KW8jbG</X509Certificate></X509Data></KeyInfo></KeyDescriptor>"
            + "<SingleLogoutService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect\" Location=\"http://localhost/AuthServices/Logout\" />"
            + "<SingleLogoutService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\" Location=\"http://localhost/AuthServices/Logout\" />"
            + "<AssertionConsumerService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\" Location=\"http://localhost/AuthServices/Acs\" index=\"0\" isDefault=\"true\" />"
            + "<AssertionConsumerService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact\" Location=\"http://localhost/AuthServices/Acs\" index=\"1\" isDefault=\"false\" />"
            + "<AttributeConsumingService index=\"0\" isDefault=\"true\">"
            + "<ServiceName xml:lang=\"en\">attributeServiceName</ServiceName>"
            + "<RequestedAttribute Name=\"urn:attributeName\" isRequired=\"true\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"friendlyName\">"
            + "<saml2:AttributeValue>value1</saml2:AttributeValue>"
            + "<saml2:AttributeValue>value2</saml2:AttributeValue>"
            + "</RequestedAttribute>"
            + "<RequestedAttribute Name=\"someName\" isRequired=\"false\" />"
            + "</AttributeConsumingService>"
            + "</SPSSODescriptor>"
            + "<Organization>"
            + "<OrganizationName xml:lang=\"\">Kentor.AuthServices</OrganizationName>"
            + "<OrganizationDisplayName xml:lang=\"\">Kentor AuthServices</OrganizationDisplayName>"
            + "<OrganizationURL xml:lang=\"\">http://github.com/KentorIT/authservices</OrganizationURL>"
            + "</Organization>"
            + "<ContactPerson contactType=\"support\">"
            + "<Company>Kentor</Company>"
            + "<GivenName>Anders</GivenName>"
            + "<SurName>Abel</SurName>"
            + "<EmailAddress>[email protected]</EmailAddress>"
            + "<EmailAddress>[email protected]</EmailAddress>"
            + "<TelephoneNumber>+46 8 587 650 00</TelephoneNumber>"
            + "<TelephoneNumber>+46 708 96 50 63</TelephoneNumber>"
            + "</ContactPerson>"
            + "<ContactPerson contactType=\"technical\" />"
            + "</EntityDescriptor>";

            payloadXml.Should().BeEquivalentTo(XmlHelpers.FromString(expectedXml));
            subject.ContentType.Should().Be("application/samlmetadata+xml");
        }
        public void MetadataCommand_Run_MinimalMetadata()
        {
            var spOptions = new SPOptions()
            {
                EntityId = new EntityId("http://localhost/AuthServices"),
            };
            var options = new Options(spOptions);

            var result = new MetadataCommand().Run(request, options);

            XDocument subject = XDocument.Parse(result.Content);

            // Ignore the ID attribute, it is just filled with a GUID that can't be easily tested.
            subject.Root.Attribute("ID").Remove();

            var expectedXml = new XDocument(new XElement(Saml2Namespaces.Saml2Metadata + "EntityDescriptor",
                new XAttribute("entityID", "http://localhost/AuthServices"),
                new XAttribute("cacheDuration", "PT1H"),
                // Have to manually add the xmlns attribute here, as it will be present in the subject
                // data and the xml tree comparison will fail if it is not present in both. Just setting the
                // namespace of the elements does not inject the xmlns attribute into the node tree. It is
                // only done when outputting a string.
                // See http://stackoverflow.com/questions/24156689/xnode-deepequals-unexpectedly-returns-false
                new XAttribute(XNamespace.Xmlns + "saml2", Saml2Namespaces.Saml2),
                new XAttribute("xmlns", Saml2Namespaces.Saml2MetadataName),
                new XElement(Saml2Namespaces.Saml2Metadata + "SPSSODescriptor",
                    new XAttribute("protocolSupportEnumeration", "urn:oasis:names:tc:SAML:2.0:protocol"),
                    new XElement(Saml2Namespaces.Saml2Metadata + "AssertionConsumerService",
                        new XAttribute("Binding", Saml2Binding.HttpPostUri),
                        new XAttribute("Location", "http://localhost/AuthServices/Acs"),
                        new XAttribute("index", 0),
                        new XAttribute("isDefault", true)),
                    new XElement(Saml2Namespaces.Saml2Metadata + "AssertionConsumerService",
                        new XAttribute("Binding", Saml2Binding.HttpArtifactUri),
                        new XAttribute("Location", "http://localhost/AuthServices/Acs"),
                        new XAttribute("index", 1),
                        new XAttribute("isDefault", false)))));

            subject.Should().BeEquivalentTo(expectedXml);
        }
        public void MetadataCommand_Run_CompleteMetadata()
        {
            var options = StubFactory.CreateOptions();
            ((SPOptions)options.SPOptions).DiscoveryServiceUrl = new Uri("http://ds.example.com");

            var subject = new MetadataCommand().Run(request, options);

            XDocument payloadXml = XDocument.Parse(subject.Content);

            // Ignore the ID attribute, it is just filled with a GUID that can't be easily tested.
            payloadXml.Root.Attribute("ID").Remove();

            var expectedXml = new XDocument(new XElement(Saml2Namespaces.Saml2Metadata + "EntityDescriptor",
                new XAttribute("entityID", "https://github.com/KentorIT/authservices"),
                new XAttribute("cacheDuration", "PT42S"),
                // Have to manually add the xmlns attribute here, as it will be present in the subject
                // data and the xml tree comparison will fail if it is not present in both. Just setting the 
                // namespace of the elements does not inject the xmlns attribute into the node tree. It is
                // only done when outputting a string.
                // See http://stackoverflow.com/questions/24156689/xnode-deepequals-unexpectedly-returns-false
                new XAttribute("xmlns", Saml2Namespaces.Saml2MetadataName),
                new XAttribute(XNamespace.Xmlns + "saml2", Saml2Namespaces.Saml2),
                new XElement(Saml2Namespaces.Saml2Metadata + "SPSSODescriptor",
                    new XAttribute("protocolSupportEnumeration", "urn:oasis:names:tc:SAML:2.0:protocol"),
                    new XElement(Saml2Namespaces.Saml2Metadata + "Extensions",
                        new XElement(Saml2Namespaces.Saml2IdpDiscovery + "DiscoveryResponse",
                            new XAttribute("Binding", Saml2Binding.DiscoveryResponseUri),
                            new XAttribute("Location", "http://localhost/AuthServices/SignIn"),
                            new XAttribute("index", 0),
                            new XAttribute("isDefault", true),
                            new XAttribute("xmlns", Saml2Namespaces.Saml2IdpDiscoveryName))),
                    new XElement(Saml2Namespaces.Saml2Metadata + "AssertionConsumerService",
                        new XAttribute("Binding", Saml2Binding.HttpPostUri),
                        new XAttribute("Location", "http://localhost/AuthServices/Acs"),
                        new XAttribute("index", 0),
                        new XAttribute("isDefault", true)),
                    new XElement(Saml2Namespaces.Saml2Metadata + "AttributeConsumingService",
                        new XAttribute("index", 0),
                        new XAttribute("isDefault", true),
                        new XElement(Saml2Namespaces.Saml2Metadata + "ServiceName",
                            new XAttribute(XNamespace.Xml + "lang", "en"),
                            "attributeServiceName"),
                        new XElement(Saml2Namespaces.Saml2Metadata + "RequestedAttribute",
                            new XAttribute("Name", "urn:attributeName"),
                            new XAttribute("isRequired", "true"),
                            new XAttribute("NameFormat", "urn:oasis:names:tc:SAML:2.0:attrname-format:uri"),
                            new XAttribute("FriendlyName", "friendlyName"),
                            new XElement(Saml2Namespaces.Saml2 + "AttributeValue", "value1"),
                            new XElement(Saml2Namespaces.Saml2 + "AttributeValue", "value2")),
                        new XElement(Saml2Namespaces.Saml2Metadata + "RequestedAttribute",
                            new XAttribute("Name", "someName"),
                            new XAttribute("isRequired", "false")))),
                new XElement(Saml2Namespaces.Saml2Metadata + "Organization",
                    new XElement(Saml2Namespaces.Saml2Metadata + "OrganizationName",
                        new XAttribute(XNamespace.Xml + "lang", ""), "Kentor.AuthServices"),
                    new XElement(Saml2Namespaces.Saml2Metadata + "OrganizationDisplayName",
                        new XAttribute(XNamespace.Xml + "lang", ""), "Kentor AuthServices"),
                    new XElement(Saml2Namespaces.Saml2Metadata + "OrganizationURL",
                        new XAttribute(XNamespace.Xml + "lang", ""), "http://github.com/KentorIT/authservices")),
                new XElement(Saml2Namespaces.Saml2Metadata + "ContactPerson",
                    new XAttribute("contactType", "support"),
                    new XElement(Saml2Namespaces.Saml2Metadata + "Company", "Kentor"),
                    new XElement(Saml2Namespaces.Saml2Metadata + "GivenName", "Anders"),
                    new XElement(Saml2Namespaces.Saml2Metadata + "SurName", "Abel"),
                    new XElement(Saml2Namespaces.Saml2Metadata + "EmailAddress", "*****@*****.**"),
                    new XElement(Saml2Namespaces.Saml2Metadata + "EmailAddress", "*****@*****.**"),
                    new XElement(Saml2Namespaces.Saml2Metadata + "TelephoneNumber", "+46 8 587 650 00"),
                    new XElement(Saml2Namespaces.Saml2Metadata + "TelephoneNumber", "+46 708 96 50 63")),
                new XElement(Saml2Namespaces.Saml2Metadata + "ContactPerson",
                    new XAttribute("contactType", "technical"))));

            payloadXml.ShouldBeEquivalentTo(expectedXml, opt => opt.IgnoringCyclicReferences());
            subject.ContentType.Should().Be("application/samlmetadata+xml");
        }