Comparing changes

v0.2.2 v0.2.3
35 commits 46 files changed

Commits

6fc2745 rename variable. mo 2017-12-12 00:02:29
4073768 remove unused methods. mo 2017-12-11 23:54:01
4b9d634 extract certificate template. mo 2017-12-11 23:45:01
37ec473 extract render method. mo 2017-12-11 23:26:51
d750290 remove empty block. mo 2017-12-11 22:23:49
19e60d2 remove unneccessary loop. mo 2017-12-11 22:20:43
1e5e862 remove duplication. mo 2017-12-11 22:11:22
b778bcb extract template class. mo 2017-12-11 20:18:50
8a8db84 provide default template_name. mo 2017-12-11 20:07:16
89cc749 merge with master. mo 2017-12-11 16:20:41
14771fc bump version. mo 2017-12-10 21:34:51
8a6f103 add specs. mo 2017-12-10 18:58:21
1604c8d implement inspect. mo 2017-12-10 18:55:15
8ac6fd1 add specs for metadata methods. mo 2017-12-10 18:47:27
00ca0d9 add spec for composite metadata. mo 2017-12-08 19:11:09
da60950 register composite metadata. mo 2017-12-05 20:29:07
7884966 create composite metadata. mo 2017-12-05 20:21:37
9f5e75e add spec for parsing .from mo 2017-12-04 20:25:17
lib/saml/kit/bindings/binding.rb
@@ -25,6 +25,26 @@ module Saml
           { binding: binding, location: location }
         end
 
+        def ==(other)
+          self.to_s == other.to_s
+        end
+
+        def eql?(other)
+          self == other
+        end
+
+        def hash
+          to_s.hash
+        end
+
+        def to_s
+          "#{location}#{binding}"
+        end
+
+        def inspect
+          to_h.inspect
+        end
+
         protected
 
         def saml_param_from(params)
lib/saml/kit/builders/templates/assertion.builder
@@ -0,0 +1,29 @@
+xml.Assertion(assertion_options) do
+  xml.Issuer issuer
+  signature_for(reference_id: reference_id, xml: xml) unless encrypt
+  xml.Subject do
+    xml.NameID name_id, Format: name_id_format
+    xml.SubjectConfirmation Method: Saml::Kit::Namespaces::BEARER do
+      xml.SubjectConfirmationData "", subject_confirmation_data_options
+    end
+  end
+  xml.Conditions conditions_options do
+    xml.AudienceRestriction do
+      xml.Audience request.issuer
+    end
+  end
+  xml.AuthnStatement authn_statement_options do
+    xml.AuthnContext do
+      xml.AuthnContextClassRef Saml::Kit::Namespaces::PASSWORD
+    end
+  end
+  if assertion_attributes.any?
+    xml.AttributeStatement do
+      assertion_attributes.each do |key, value|
+        xml.Attribute Name: key, NameFormat: Saml::Kit::Namespaces::URI, FriendlyName: key do
+          xml.AttributeValue value.to_s
+        end
+      end
+    end
+  end
+end
lib/saml/kit/builders/templates/authentication_request.builder
@@ -0,0 +1,6 @@
+xml.instruct!
+xml.tag!('samlp:AuthnRequest', request_options) do
+  xml.tag!('saml:Issuer', issuer)
+  signature_for(reference_id: id, xml: xml)
+  xml.tag!('samlp:NameIDPolicy', Format: name_id_format)
+end
lib/saml/kit/builders/templates/certificate.builder
@@ -0,0 +1,7 @@
+xml.KeyDescriptor use: use do
+  xml.KeyInfo "xmlns": Saml::Kit::Namespaces::XMLDSIG do
+    xml.X509Data do
+      xml.X509Certificate stripped
+    end
+  end
+end
lib/saml/kit/builders/templates/identity_provider_metadata.builder
@@ -0,0 +1,32 @@
+xml.instruct!
+xml.EntityDescriptor entity_descriptor_options do
+  signature_for(reference_id: id, xml: xml)
+  xml.IDPSSODescriptor idp_sso_descriptor_options do
+    if configuration.signing_certificate_pem.present?
+      render configuration.signing_certificate, xml: xml
+    end
+    if configuration.encryption_certificate_pem.present?
+      render configuration.encryption_certificate, xml: xml
+    end
+    logout_urls.each do |item|
+      xml.SingleLogoutService Binding: item[:binding], Location: item[:location]
+    end
+    name_id_formats.each do |format|
+      xml.NameIDFormat format
+    end
+    single_sign_on_urls.each do |item|
+      xml.SingleSignOnService Binding: item[:binding], Location: item[:location]
+    end
+    attributes.each do |attribute|
+      xml.tag! 'saml:Attribute', Name: attribute
+    end
+  end
+  xml.Organization do
+    xml.OrganizationName organization_name, 'xml:lang': "en"
+    xml.OrganizationDisplayName organization_name, 'xml:lang': "en"
+    xml.OrganizationURL organization_url, 'xml:lang': "en"
+  end
+  xml.ContactPerson contactType: "technical" do
+    xml.Company "mailto:#{contact_email}"
+  end
+end
lib/saml/kit/builders/templates/logout_request.builder
@@ -0,0 +1,6 @@
+xml.instruct!
+xml.LogoutRequest logout_request_options do
+  xml.Issuer({ xmlns: Saml::Kit::Namespaces::ASSERTION }, issuer)
+  signature_for(reference_id: id, xml: xml)
+  xml.NameID name_id_options, user.name_id_for(name_id_format)
+end
lib/saml/kit/builders/templates/logout_response.builder
@@ -0,0 +1,8 @@
+xml.instruct!
+xml.LogoutResponse logout_response_options do
+  xml.Issuer(issuer, xmlns: Saml::Kit::Namespaces::ASSERTION)
+  signature_for(reference_id: id, xml: xml)
+  xml.Status do
+    xml.StatusCode Value: status_code
+  end
+end
lib/saml/kit/builders/templates/response.builder
@@ -0,0 +1,11 @@
+xml.instruct!
+xml.Response response_options do
+  xml.Issuer(issuer, xmlns: Saml::Kit::Namespaces::ASSERTION)
+  signature_for(reference_id: id, xml: xml)
+  xml.Status do
+    xml.StatusCode Value: status_code
+  end
+  encryption_for(xml: xml) do |xml|
+    render assertion, xml: xml
+  end
+end
lib/saml/kit/builders/templates/service_provider_metadata.builder
@@ -0,0 +1,29 @@
+xml.instruct!
+xml.EntityDescriptor entity_descriptor_options do
+  signature_for(reference_id: id, xml: xml)
+  xml.SPSSODescriptor descriptor_options do
+    if configuration.signing_certificate_pem.present?
+      render configuration.signing_certificate, xml: xml
+    end
+    if configuration.encryption_certificate_pem.present?
+      render configuration.encryption_certificate, xml: xml
+    end
+    logout_urls.each do |item|
+      xml.SingleLogoutService Binding: item[:binding], Location: item[:location]
+    end
+    name_id_formats.each do |format|
+      xml.NameIDFormat format
+    end
+    acs_urls.each_with_index do |item, index|
+      xml.AssertionConsumerService Binding: item[:binding], Location: item[:location], index: index, isDefault: index == 0 ? true : false
+    end
+  end
+  xml.Organization do
+    xml.OrganizationName organization_name, 'xml:lang': "en"
+    xml.OrganizationDisplayName organization_name, 'xml:lang': "en"
+    xml.OrganizationURL organization_url, 'xml:lang': "en"
+  end
+  xml.ContactPerson contactType: "technical" do
+    xml.Company "mailto:#{contact_email}"
+  end
+end
lib/saml/kit/builders/templates/xml_encryption.builder
@@ -0,0 +1,16 @@
+xml.EncryptedAssertion xmlns: Saml::Kit::Namespaces::ASSERTION do
+  xml.EncryptedData xmlns: Saml::Kit::Namespaces::XMLENC do
+    xml.EncryptionMethod Algorithm: "http://www.w3.org/2001/04/xmlenc#aes256-cbc"
+    xml.KeyInfo xmlns: Saml::Kit::Namespaces::XMLDSIG do
+      xml.EncryptedKey xmlns: Saml::Kit::Namespaces::XMLENC do
+        xml.EncryptionMethod Algorithm: "http://www.w3.org/2001/04/xmlenc#rsa-1_5"
+        xml.CipherData do
+          xml.CipherValue Base64.encode64(public_key.public_encrypt(key))
+        end
+      end
+    end
+    xml.CipherData do
+      xml.CipherValue Base64.encode64(iv + encrypted)
+    end
+  end
+end
lib/saml/kit/builders/templates/xml_signature.builder
@@ -0,0 +1,20 @@
+xml.Signature "xmlns" => Saml::Kit::Namespaces::XMLDSIG do
+  xml.SignedInfo do
+    xml.CanonicalizationMethod Algorithm: "http://www.w3.org/2001/10/xml-exc-c14n#"
+    xml.SignatureMethod Algorithm: signature_method
+    xml.Reference URI: "##{reference_id}" do
+      xml.Transforms do
+        xml.Transform Algorithm: "http://www.w3.org/2000/09/xmldsig#enveloped-signature"
+        xml.Transform Algorithm: "http://www.w3.org/2001/10/xml-exc-c14n#"
+      end
+      xml.DigestMethod Algorithm: digest_method
+      xml.DigestValue ""
+    end
+  end
+  xml.SignatureValue ""
+  xml.KeyInfo do
+    xml.X509Data do
+      xml.X509Certificate x509_certificate
+    end
+  end
+end
lib/saml/kit/builders/assertion.rb
@@ -0,0 +1,62 @@
+module Saml
+  module Kit
+    module Builders
+      class Assertion
+        include Templatable
+        extend Forwardable
+
+        def_delegators :@response_builder, :encrypt, :sign, :request, :issuer, :reference_id, :now, :configuration, :user, :version
+
+        def initialize(response_builder)
+          @response_builder = response_builder
+        end
+
+        def name_id_format
+          request.name_id_format
+        end
+
+        def name_id
+          user.name_id_for(name_id_format)
+        end
+
+        def assertion_attributes
+          user.assertion_attributes_for(request)
+        end
+
+        private
+
+        def assertion_options
+          {
+            ID: reference_id,
+            IssueInstant: now.iso8601,
+            Version: version,
+            xmlns: Namespaces::ASSERTION,
+          }
+        end
+
+        def subject_confirmation_data_options
+          {
+            InResponseTo: request.id,
+            NotOnOrAfter: 3.hours.since(now).utc.iso8601,
+            Recipient: request.assertion_consumer_service_url,
+          }
+        end
+
+        def conditions_options
+          {
+            NotBefore: now.utc.iso8601,
+            NotOnOrAfter: configuration.session_timeout.from_now.utc.iso8601,
+          }
+        end
+
+        def authn_statement_options
+          {
+            AuthnInstant: now.iso8601,
+            SessionIndex: reference_id,
+            SessionNotOnOrAfter: 3.hours.since(now).utc.iso8601,
+          }
+        end
+      end
+    end
+  end
+end
lib/saml/kit/builders/authentication_request.rb
@@ -2,26 +2,29 @@ module Saml
   module Kit
     module Builders
       class AuthenticationRequest
+        include Saml::Kit::Templatable
         attr_accessor :id, :now, :issuer, :assertion_consumer_service_url, :name_id_format, :sign, :destination
         attr_accessor :version
+        attr_reader :configuration
 
         def initialize(configuration: Saml::Kit.configuration, sign: true)
+          @configuration = configuration
           @id = Id.generate
           @issuer = configuration.issuer
           @name_id_format = Namespaces::PERSISTENT
           @now = Time.now.utc
-          @version = "2.0"
           @sign = sign
+          @version = "2.0"
         end
 
-        def to_xml
-          Signature.sign(sign: sign) do |xml, signature|
-            xml.tag!('samlp:AuthnRequest', request_options) do
-              xml.tag!('saml:Issuer', issuer)
-              signature.template(id)
-              xml.tag!('samlp:NameIDPolicy', Format: name_id_format)
-            end
-          end
+        def acs_url
+          Saml::Kit.deprecate("acs_url is deprecated. Use assertion_consumer_service_url instead")
+          self.assertion_consumer_service_url
+        end
+
+        def acs_url=(value)
+          Saml::Kit.deprecate("acs_url= is deprecated. Use assertion_consumer_service_url= instead")
+          self.assertion_consumer_service_url = value
         end
 
         def build
@@ -39,7 +42,9 @@ module Saml
             IssueInstant: now.utc.iso8601,
             Destination: destination,
           }
-          options[:AssertionConsumerServiceURL] = assertion_consumer_service_url if assertion_consumer_service_url.present?
+          if assertion_consumer_service_url.present?
+            options[:AssertionConsumerServiceURL] = assertion_consumer_service_url
+          end
           options
         end
       end
lib/saml/kit/builders/identity_provider_metadata.rb
@@ -2,19 +2,21 @@ module Saml
   module Kit
     module Builders
       class IdentityProviderMetadata
+        include Saml::Kit::Templatable
         attr_accessor :id, :organization_name, :organization_url, :contact_email, :entity_id, :attributes, :name_id_formats
         attr_accessor :want_authn_requests_signed, :sign
         attr_reader :logout_urls, :single_sign_on_urls
+        attr_reader :configuration
 
         def initialize(configuration = Saml::Kit.configuration)
-          @id = Id.generate
-          @entity_id = configuration.issuer
           @attributes = []
-          @name_id_formats = [Namespaces::PERSISTENT]
-          @single_sign_on_urls = []
-          @logout_urls = []
           @configuration = configuration
+          @entity_id = configuration.issuer
+          @id = Id.generate
+          @logout_urls = []
+          @name_id_formats = [Namespaces::PERSISTENT]
           @sign = true
+          @single_sign_on_urls = []
           @want_authn_requests_signed = true
         end
 
@@ -26,55 +28,6 @@ module Saml
           @logout_urls.push(location: url, binding: Bindings.binding_for(binding))
         end
 
-        def to_xml
-          Signature.sign(sign: sign) do |xml, signature|
-            xml.instruct!
-            xml.EntityDescriptor entity_descriptor_options do
-              signature.template(id)
-              xml.IDPSSODescriptor idp_sso_descriptor_options do
-                if @configuration.signing_certificate_pem.present?
-                  xml.KeyDescriptor use: "signing" do
-                    xml.KeyInfo "xmlns": Namespaces::XMLDSIG do
-                      xml.X509Data do
-                        xml.X509Certificate @configuration.stripped_signing_certificate
-                      end
-                    end
-                  end
-                end
-                if @configuration.encryption_certificate_pem.present?
-                  xml.KeyDescriptor use: "encryption" do
-                    xml.KeyInfo "xmlns": Namespaces::XMLDSIG do
-                      xml.X509Data do
-                        xml.X509Certificate @configuration.stripped_encryption_certificate
-                      end
-                    end
-                  end
-                end
-                logout_urls.each do |item|
-                  xml.SingleLogoutService Binding: item[:binding], Location: item[:location]
-                end
-                name_id_formats.each do |format|
-                  xml.NameIDFormat format
-                end
-                single_sign_on_urls.each do |item|
-                  xml.SingleSignOnService Binding: item[:binding], Location: item[:location]
-                end
-                attributes.each do |attribute|
-                  xml.tag! 'saml:Attribute', Name: attribute
-                end
-              end
-              xml.Organization do
-                xml.OrganizationName organization_name, 'xml:lang': "en"
-                xml.OrganizationDisplayName organization_name, 'xml:lang': "en"
-                xml.OrganizationURL organization_url, 'xml:lang': "en"
-              end
-              xml.ContactPerson contactType: "technical" do
-                xml.Company "mailto:#{contact_email}"
-              end
-            end
-          end
-        end
-
         def build
           Saml::Kit::IdentityProviderMetadata.new(to_xml)
         end
lib/saml/kit/builders/logout_request.rb
@@ -2,11 +2,13 @@ module Saml
   module Kit
     module Builders
       class LogoutRequest
+        include Saml::Kit::Templatable
         attr_accessor :id, :destination, :issuer, :name_id_format, :now
         attr_accessor :sign, :version
-        attr_reader :user
+        attr_reader :user, :configuration
 
         def initialize(user, configuration: Saml::Kit.configuration, sign: true)
+          @configuration = configuration
           @user = user
           @id = "_#{SecureRandom.uuid}"
           @issuer = configuration.issuer
@@ -16,17 +18,6 @@ module Saml
           @sign = sign
         end
 
-        def to_xml
-          Signature.sign(sign: sign) do |xml, signature|
-            xml.instruct!
-            xml.LogoutRequest logout_request_options do
-              xml.Issuer({ xmlns: Namespaces::ASSERTION }, issuer)
-              signature.template(id)
-              xml.NameID name_id_options, user.name_id_for(name_id_format)
-            end
-          end
-        end
-
         def build
           Saml::Kit::LogoutRequest.new(to_xml)
         end
lib/saml/kit/builders/logout_response.rb
@@ -2,30 +2,21 @@ module Saml
   module Kit
     module Builders
       class LogoutResponse
+        include Saml::Kit::Templatable
         attr_accessor :id, :issuer, :version, :status_code, :sign, :now, :destination
         attr_reader :request
+        attr_reader :configuration
 
         def initialize(user, request, configuration: Saml::Kit.configuration, sign: true)
-          @user = user
+          @configuration = configuration
+          @id = Id.generate
+          @issuer = configuration.issuer
           @now = Time.now.utc
           @request = request
-          @id = Id.generate
-          @version = "2.0"
-          @status_code = Namespaces::SUCCESS
           @sign = sign
-          @issuer = configuration.issuer
-        end
-
-        def to_xml
-          Signature.sign(sign: sign) do |xml, signature|
-            xml.LogoutResponse logout_response_options do
-              xml.Issuer(issuer, xmlns: Namespaces::ASSERTION)
-              signature.template(id)
-              xml.Status do
-                xml.StatusCode Value: status_code
-              end
-            end
-          end
+          @status_code = Namespaces::SUCCESS
+          @user = user
+          @version = "2.0"
         end
 
         def build
lib/saml/kit/builders/response.rb
@@ -2,12 +2,14 @@ module Saml
   module Kit
     module Builders
       class Response
+        include Templatable
         attr_reader :user, :request
         attr_accessor :id, :reference_id, :now
         attr_accessor :version, :status_code
         attr_accessor :issuer, :sign, :destination, :encrypt
+        attr_reader :configuration
 
-        def initialize(user, request)
+        def initialize(user, request, configuration: Saml::Kit.configuration)
           @user = user
           @request = request
           @id = Id.generate
@@ -19,6 +21,7 @@ module Saml
           @destination = destination_for(request)
           @sign = want_assertions_signed
           @encrypt = false
+          @configuration = configuration
         end
 
         def want_assertions_signed
@@ -28,97 +31,18 @@ module Saml
           true
         end
 
-        def to_xml
-          Signature.sign(sign: sign) do |xml, signature|
-            xml.Response response_options do
-              xml.Issuer(issuer, xmlns: Namespaces::ASSERTION)
-              signature.template(id)
-              xml.Status do
-                xml.StatusCode Value: status_code
-              end
-              assertion(xml, signature)
-            end
-          end
-        end
-
         def build
           Saml::Kit::Response.new(to_xml, request_id: request.id)
         end
 
         private
 
-        def assertion(xml, signature)
-          with_encryption(xml) do |xml|
-            xml.Assertion(assertion_options) do
-              xml.Issuer issuer
-              signature.template(reference_id) unless encrypt
-              xml.Subject do
-                xml.NameID user.name_id_for(request.name_id_format), Format: request.name_id_format
-                xml.SubjectConfirmation Method: Namespaces::BEARER do
-                  xml.SubjectConfirmationData "", subject_confirmation_data_options
-                end
-              end
-              xml.Conditions conditions_options do
-                xml.AudienceRestriction do
-                  xml.Audience request.issuer
-                end
-              end
-              xml.AuthnStatement authn_statement_options do
-                xml.AuthnContext do
-                  xml.AuthnContextClassRef Namespaces::PASSWORD
-                end
-              end
-              assertion_attributes = user.assertion_attributes_for(request)
-              if assertion_attributes.any?
-                xml.AttributeStatement do
-                  assertion_attributes.each do |key, value|
-                    xml.Attribute Name: key, NameFormat: Namespaces::URI, FriendlyName: key do
-                      xml.AttributeValue value.to_s
-                    end
-                  end
-                end
-              end
-            end
-          end
+        def assertion
+          @assertion ||= Saml::Kit::Builders::Assertion.new(self)
         end
 
-        def with_encryption(xml)
-          if encrypt
-            temp = ::Builder::XmlMarkup.new
-            yield temp
-            raw_xml_to_encrypt = temp.target!
-
-            encryption_certificate = request.provider.encryption_certificates.first
-            public_key = encryption_certificate.public_key
-
-            cipher = OpenSSL::Cipher.new('AES-256-CBC')
-            cipher.encrypt
-            key = cipher.random_key
-            iv = cipher.random_iv
-            encrypted = cipher.update(raw_xml_to_encrypt) + cipher.final
-
-            Saml::Kit.logger.debug ['+iv', iv].inspect
-            Saml::Kit.logger.debug ['+key', key].inspect
-
-            xml.EncryptedAssertion xmlns: Namespaces::ASSERTION do
-              xml.EncryptedData xmlns: Namespaces::XMLENC do
-                xml.EncryptionMethod Algorithm: "http://www.w3.org/2001/04/xmlenc#aes256-cbc"
-                xml.KeyInfo xmlns: Namespaces::XMLDSIG do
-                  xml.EncryptedKey xmlns: Namespaces::XMLENC do
-                    xml.EncryptionMethod Algorithm: "http://www.w3.org/2001/04/xmlenc#rsa-1_5"
-                    xml.CipherData do
-                      xml.CipherValue Base64.encode64(public_key.public_encrypt(key))
-                    end
-                  end
-                end
-                xml.CipherData do
-                  xml.CipherValue Base64.encode64(iv + encrypted)
-                end
-              end
-            end
-          else
-            yield xml
-          end
+        def encryption_certificate
+          request.provider.encryption_certificates.first
         end
 
         def destination_for(request)
@@ -129,10 +53,6 @@ module Saml
           end
         end
 
-        def configuration
-          Saml::Kit.configuration
-        end
-
         def response_options
           {
             ID: id,
@@ -144,38 +64,6 @@ module Saml
             xmlns: Namespaces::PROTOCOL,
           }
         end
-
-        def assertion_options
-          {
-            ID: reference_id,
-            IssueInstant: now.iso8601,
-            Version: "2.0",
-            xmlns: Namespaces::ASSERTION,
-          }
-        end
-
-        def subject_confirmation_data_options
-          {
-            InResponseTo: request.id,
-            NotOnOrAfter: 3.hours.since(now).utc.iso8601,
-            Recipient: request.assertion_consumer_service_url,
-          }
-        end
-
-        def conditions_options
-          {
-            NotBefore: now.utc.iso8601,
-            NotOnOrAfter: Saml::Kit.configuration.session_timeout.from_now.utc.iso8601,
-          }
-        end
-
-        def authn_statement_options
-          {
-            AuthnInstant: now.iso8601,
-            SessionIndex: assertion_options[:ID],
-            SessionNotOnOrAfter: 3.hours.since(now).utc.iso8601,
-          }
-        end
       end
     end
   end
lib/saml/kit/builders/service_provider_metadata.rb
@@ -2,14 +2,17 @@ module Saml
   module Kit
     module Builders
       class ServiceProviderMetadata
+        include Saml::Kit::Templatable
         attr_accessor :id, :entity_id, :acs_urls, :logout_urls, :name_id_formats, :sign
+        attr_accessor :organization_name, :organization_url, :contact_email
         attr_accessor :want_assertions_signed
+        attr_reader :configuration
 
         def initialize(configuration = Saml::Kit.configuration)
-          @id = Id.generate
+          @acs_urls = []
           @configuration = configuration
           @entity_id = configuration.issuer
-          @acs_urls = []
+          @id = Id.generate
           @logout_urls = []
           @name_id_formats = [Namespaces::PERSISTENT]
           @sign = true
@@ -24,44 +27,6 @@ module Saml
           @logout_urls.push(location: url, binding: Bindings.binding_for(binding))
         end
 
-        def to_xml
-          Signature.sign(sign: sign) do |xml, signature|
-            xml.instruct!
-            xml.EntityDescriptor entity_descriptor_options do
-              signature.template(id)
-              xml.SPSSODescriptor descriptor_options do
-                if @configuration.signing_certificate_pem.present?
-                  xml.KeyDescriptor use: "signing" do
-                    xml.KeyInfo "xmlns": Namespaces::XMLDSIG do
-                      xml.X509Data do
-                        xml.X509Certificate @configuration.stripped_signing_certificate
-                      end
-                    end
-                  end
-                end
-                if @configuration.encryption_certificate_pem.present?
-                  xml.KeyDescriptor use: "encryption" do
-                    xml.KeyInfo "xmlns": Namespaces::XMLDSIG do
-                      xml.X509Data do
-                        xml.X509Certificate @configuration.stripped_encryption_certificate
-                      end
-                    end
-                  end
-                end
-                logout_urls.each do |item|
-                  xml.SingleLogoutService Binding: item[:binding], Location: item[:location]
-                end
-                name_id_formats.each do |format|
-                  xml.NameIDFormat format
-                end
-                acs_urls.each_with_index do |item, index|
-                  xml.AssertionConsumerService Binding: item[:binding], Location: item[:location], index: index, isDefault: index == 0 ? true : false
-                end
-              end
-            end
-          end
-        end
-
         def build
           Saml::Kit::ServiceProviderMetadata.new(to_xml)
         end
lib/saml/kit/builders/xml_encryption.rb
@@ -0,0 +1,20 @@
+module Saml
+  module Kit
+    module Builders
+      class XmlEncryption
+        attr_reader :public_key
+        attr_reader :key, :iv, :encrypted
+
+        def initialize(raw_xml, public_key)
+          @public_key = public_key
+          cipher = OpenSSL::Cipher.new('AES-256-CBC')
+          cipher.encrypt
+          @key = cipher.random_key
+          @iv = cipher.random_iv
+          @encrypted = cipher.update(raw_xml) + cipher.final
+        end
+      end
+    end
+  end
+end
+
lib/saml/kit/builders/xml_signature.rb
@@ -0,0 +1,41 @@
+module Saml
+  module Kit
+    module Builders
+      class XmlSignature
+        SIGNATURE_METHODS = {
+          SHA1: "http://www.w3.org/2000/09/xmldsig#rsa-sha1",
+          SHA224: "http://www.w3.org/2001/04/xmldsig-more#rsa-sha224",
+          SHA256: "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
+          SHA384: "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384",
+          SHA512: "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512",
+        }.freeze
+        DIGEST_METHODS = {
+          SHA1: "http://www.w3.org/2000/09/xmldsig#SHA1",
+          SHA224: "http://www.w3.org/2001/04/xmldsig-more#sha224",
+          SHA256: "http://www.w3.org/2001/04/xmlenc#sha256",
+          SHA384: "http://www.w3.org/2001/04/xmldsig-more#sha384",
+          SHA512: "http://www.w3.org/2001/04/xmlenc#sha512",
+        }.freeze
+
+        attr_reader :sign, :configuration
+        attr_reader :reference_id
+        attr_reader :x509_certificate
+
+        def initialize(reference_id, configuration:, sign: true)
+          @configuration = configuration
+          @reference_id = reference_id
+          @sign = sign
+          @x509_certificate = configuration.signing_certificate.stripped
+        end
+
+        def signature_method
+          SIGNATURE_METHODS[configuration.signature_method]
+        end
+
+        def digest_method
+          DIGEST_METHODS[configuration.digest_method]
+        end
+      end
+    end
+  end
+end
lib/saml/kit/builders.rb
@@ -1,9 +1,12 @@
+require 'saml/kit/builders/assertion'
 require 'saml/kit/builders/authentication_request'
 require 'saml/kit/builders/identity_provider_metadata'
 require 'saml/kit/builders/logout_request'
 require 'saml/kit/builders/logout_response'
 require 'saml/kit/builders/response'
 require 'saml/kit/builders/service_provider_metadata'
+require 'saml/kit/builders/xml_encryption'
+require 'saml/kit/builders/xml_signature'
 
 module Saml
   module Kit
lib/saml/kit/certificate.rb
@@ -1,6 +1,8 @@
 module Saml
   module Kit
     class Certificate
+      BEGIN_CERT=/-----BEGIN CERTIFICATE-----/
+      END_CERT=/-----END CERTIFICATE-----/
       attr_reader :value, :use
 
       def initialize(value, use:)
@@ -48,6 +50,18 @@ module Saml
         value
       end
 
+      def to_h
+        { use: @use, x509: @value }
+      end
+
+      def inspect
+        to_h.inspect
+      end
+
+      def stripped
+        value.to_s.gsub(BEGIN_CERT, '').gsub(END_CERT, '').gsub(/\n/, '')
+      end
+
       def self.to_x509(value)
         OpenSSL::X509::Certificate.new(Base64.decode64(value))
       rescue OpenSSL::X509::CertificateError => error
lib/saml/kit/composite_metadata.rb
@@ -0,0 +1,36 @@
+module Saml
+  module Kit
+    class CompositeMetadata < Metadata
+      attr_reader :service_provider, :identity_provider
+
+      def initialize(xml)
+        super("IDPSSODescriptor", xml)
+        @service_provider = Saml::Kit::ServiceProviderMetadata.new(xml)
+        @identity_provider = Saml::Kit::IdentityProviderMetadata.new(xml)
+      end
+
+      def services(type)
+        xpath = "//md:EntityDescriptor/md:SPSSODescriptor/md:#{type}|//md:EntityDescriptor/md:IDPSSODescriptor/md:#{type}"
+        document.find_all(xpath).map do |item|
+          binding = item.attribute("Binding").value
+          location = item.attribute("Location").value
+          Saml::Kit::Bindings.create_for(binding, location)
+        end
+      end
+
+      def certificates
+        identity_provider.certificates + service_provider.certificates
+      end
+
+      def method_missing(name, *args)
+        if identity_provider.respond_to?(name)
+          identity_provider.public_send(name, *args)
+        elsif service_provider.respond_to?(name)
+          service_provider.public_send(name, *args)
+        else
+          super
+        end
+      end
+    end
+  end
+end
lib/saml/kit/configuration.rb
@@ -1,9 +1,6 @@
 module Saml
   module Kit
     class Configuration
-      BEGIN_CERT=/-----BEGIN CERTIFICATE-----/
-      END_CERT=/-----END CERTIFICATE-----/
-
       attr_accessor :issuer
       attr_accessor :signature_method, :digest_method
       attr_accessor :signing_certificate_pem, :signing_private_key_pem, :signing_private_key_password
@@ -23,20 +20,12 @@ module Saml
         @logger = Logger.new(STDOUT)
       end
 
-      def stripped_signing_certificate
-        normalize(signing_certificate_pem)
-      end
-
-      def stripped_encryption_certificate
-        normalize(encryption_certificate_pem)
-      end
-
-      def signing_x509
-        Certificate.to_x509(signing_certificate_pem)
+      def signing_certificate
+        Saml::Kit::Certificate.new(signing_certificate_pem, use: :signing)
       end
 
-      def encryption_x509
-        Certificate.to_x509(encryption_certificate_pem)
+      def encryption_certificate
+        Saml::Kit::Certificate.new(encryption_certificate_pem, use: :encryption)
       end
 
       def signing_private_key
@@ -46,12 +35,6 @@ module Saml
       def encryption_private_key
         OpenSSL::PKey::RSA.new(encryption_private_key_pem, encryption_private_key_password)
       end
-
-      private
-
-      def normalize(certificate)
-        certificate.to_s.gsub(BEGIN_CERT, '').gsub(END_CERT, '').gsub(/\n/, '')
-      end
     end
   end
 end
lib/saml/kit/metadata.rb
@@ -99,7 +99,9 @@ module Saml
       def self.from(content)
         hash = Hash.from_xml(content)
         entity_descriptor = hash["EntityDescriptor"]
-        if entity_descriptor.keys.include?("SPSSODescriptor")
+        if entity_descriptor.key?("SPSSODescriptor") && entity_descriptor.key?("IDPSSODescriptor")
+          Saml::Kit::CompositeMetadata.new(content)
+        elsif entity_descriptor.keys.include?("SPSSODescriptor")
           Saml::Kit::ServiceProviderMetadata.new(content)
         elsif entity_descriptor.keys.include?("IDPSSODescriptor")
           Saml::Kit::IdentityProviderMetadata.new(content)
lib/saml/kit/requestable.rb
@@ -3,9 +3,6 @@ module Saml
     module Requestable
       extend ActiveSupport::Concern
 
-      included do
-      end
-
       def query_string_parameter
         'SAMLRequest'
       end
lib/saml/kit/signature.rb
@@ -1,65 +1,23 @@
 module Saml
   module Kit
     class Signature
-      SIGNATURE_METHODS = {
-        SHA1: "http://www.w3.org/2000/09/xmldsig#rsa-sha1",
-        SHA224: "http://www.w3.org/2001/04/xmldsig-more#rsa-sha224",
-        SHA256: "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
-        SHA384: "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384",
-        SHA512: "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512",
-      }.freeze
-      DIGEST_METHODS = {
-        SHA1: "http://www.w3.org/2000/09/xmldsig#SHA1",
-        SHA224: "http://www.w3.org/2001/04/xmldsig-more#sha224",
-        SHA256: "http://www.w3.org/2001/04/xmlenc#sha256",
-        SHA384: "http://www.w3.org/2001/04/xmldsig-more#sha384",
-        SHA512: "http://www.w3.org/2001/04/xmlenc#sha512",
-      }.freeze
-
-      attr_reader :configuration, :sign, :xml
+      attr_reader :sign, :xml
+      attr_reader :configuration
 
       def initialize(xml, configuration:, sign: true)
-        @xml = xml
         @configuration = configuration
         @sign = sign
-        @reference_ids = []
+        @xml = xml
       end
 
       def template(reference_id)
         return unless sign
-        return if reference_id.blank?
-        @reference_ids << reference_id
-
-        xml.Signature "xmlns" => Namespaces::XMLDSIG do
-          xml.SignedInfo do
-            xml.CanonicalizationMethod Algorithm: "http://www.w3.org/2001/10/xml-exc-c14n#"
-            xml.SignatureMethod Algorithm: SIGNATURE_METHODS[configuration.signature_method]
-            xml.Reference URI: "##{reference_id}" do
-              xml.Transforms do
-                xml.Transform Algorithm: "http://www.w3.org/2000/09/xmldsig#enveloped-signature"
-                xml.Transform Algorithm: "http://www.w3.org/2001/10/xml-exc-c14n#"
-              end
-              xml.DigestMethod Algorithm: DIGEST_METHODS[configuration.digest_method]
-              xml.DigestValue ""
-            end
-          end
-          xml.SignatureValue ""
-          xml.KeyInfo do
-            xml.X509Data do
-              xml.X509Certificate configuration.stripped_signing_certificate
-            end
-          end
-        end
+        signature = signatures.build(reference_id)
+        Template.new(signature).to_xml(xml: xml)
       end
 
       def finalize
-        return xml.target! unless sign
-
-        raw_xml = xml.target!
-        @reference_ids.each do |reference_id|
-          raw_xml = Xmldsig::SignedDocument.new(raw_xml).sign(private_key)
-        end
-        raw_xml
+        signatures.complete(xml.target!)
       end
 
       def self.sign(sign: true, xml: ::Builder::XmlMarkup.new, configuration: Saml::Kit.configuration)
@@ -70,8 +28,8 @@ module Saml
 
       private
 
-      def private_key
-        configuration.signing_private_key
+      def signatures
+        @signatures ||= Saml::Kit::Signatures.new(configuration: configuration, sign: sign)
       end
     end
   end
lib/saml/kit/signatures.rb
@@ -0,0 +1,28 @@
+module Saml
+  module Kit
+    class Signatures
+      attr_reader :sign, :configuration
+
+      def initialize(configuration:, sign: true)
+        @configuration = configuration
+        @sign = sign
+      end
+
+      def build(reference_id)
+        Saml::Kit::Builders::XmlSignature.new(reference_id, configuration: configuration, sign: sign)
+      end
+
+      def complete(raw_xml)
+        return raw_xml unless sign
+
+        Xmldsig::SignedDocument.new(raw_xml).sign(private_key)
+      end
+
+      private
+
+      def private_key
+        configuration.signing_private_key
+      end
+    end
+  end
+end
lib/saml/kit/templatable.rb
@@ -0,0 +1,33 @@
+module Saml
+  module Kit
+    module Templatable
+      def to_xml(xml: ::Builder::XmlMarkup.new)
+        signatures.complete(render(self, xml: xml))
+      end
+
+      def signature_for(reference_id:, xml:)
+        return unless sign
+        render(signatures.build(reference_id), xml: xml)
+      end
+
+      def signatures
+        @signatures ||= Saml::Kit::Signatures.new(configuration: configuration, sign: sign)
+      end
+
+      def encryption_for(xml:)
+        if encrypt && encryption_certificate
+          temp = ::Builder::XmlMarkup.new
+          yield temp
+          xml_encryption = Saml::Kit::Builders::XmlEncryption.new(temp.target!, encryption_certificate.public_key)
+          render(xml_encryption, xml: xml)
+        else
+          yield xml
+        end
+      end
+
+      def render(model, options)
+        Saml::Kit::Template.new(model).to_xml(options)
+      end
+    end
+  end
+end
lib/saml/kit/template.rb
@@ -0,0 +1,29 @@
+module Saml
+  module Kit
+    class Template
+      attr_reader :target
+
+      def initialize(target)
+        @target = target
+      end
+
+      def to_xml(options)
+        template.render(target, options)
+      end
+
+      private
+
+      def template_name
+        "#{target.class.name.split("::").last.underscore}.builder"
+      end
+
+      def template_path
+        File.join(File.expand_path(File.dirname(__FILE__)), "builders/templates/#{template_name}")
+      end
+
+      def template
+        Tilt.new(template_path)
+      end
+    end
+  end
+end
lib/saml/kit/version.rb
@@ -1,5 +1,5 @@
 module Saml
   module Kit
-    VERSION = "0.2.2"
+    VERSION = "0.2.3"
   end
 end
lib/saml/kit/xml_decryption.rb
@@ -8,10 +8,10 @@ module Saml
       end
 
       def decrypt(data)
-        encrypt_data = data['EncryptedData']
-        symmetric_key = symmetric_key_from(encrypt_data)
-        cipher_text = Base64.decode64(encrypt_data["CipherData"]["CipherValue"])
-        to_plaintext(cipher_text, symmetric_key, encrypt_data["EncryptionMethod"]['Algorithm'])
+        encrypted_data = data['EncryptedData']
+        symmetric_key = symmetric_key_from(encrypted_data)
+        cipher_text = Base64.decode64(encrypted_data["CipherData"]["CipherValue"])
+        to_plaintext(cipher_text, symmetric_key, encrypted_data["EncryptionMethod"]['Algorithm'])
       end
 
       private
@@ -23,7 +23,7 @@ module Saml
       end
 
       def to_plaintext(cipher_text, symmetric_key, algorithm)
-        return Crypto.decryptor_for(algorithm, symmetric_key).decrypt(cipher_text)
+        Crypto.decryptor_for(algorithm, symmetric_key).decrypt(cipher_text)
       end
     end
   end
lib/saml/kit.rb
@@ -12,9 +12,11 @@ require "logger"
 require "net/http"
 require "nokogiri"
 require "securerandom"
+require "tilt"
 require "xmldsig"
 
 require "saml/kit/buildable"
+require "saml/kit/templatable"
 require "saml/kit/builders"
 require "saml/kit/namespaces"
 require "saml/kit/serializable"
@@ -34,6 +36,7 @@ require "saml/kit/fingerprint"
 require "saml/kit/logout_response"
 require "saml/kit/logout_request"
 require "saml/kit/metadata"
+require "saml/kit/composite_metadata"
 require "saml/kit/response"
 require "saml/kit/id"
 require "saml/kit/identity_provider_metadata"
@@ -41,6 +44,8 @@ require "saml/kit/invalid_document"
 require "saml/kit/self_signed_certificate"
 require "saml/kit/service_provider_metadata"
 require "saml/kit/signature"
+require "saml/kit/signatures"
+require "saml/kit/template"
 require "saml/kit/xml"
 require "saml/kit/xml_decryption"
 
@@ -64,6 +69,11 @@ module Saml
       def registry
         configuration.registry
       end
+
+      def deprecate(message)
+        @deprecation ||= ActiveSupport::Deprecation.new('next-release', 'saml-kit')
+        @deprecation.deprecation_warning(message)
+      end
     end
   end
 end
spec/saml/builders/authentication_request_spec.rb
@@ -24,5 +24,10 @@ RSpec.describe Saml::Kit::Builders::AuthenticationRequest do
       expect(result['AuthnRequest']['Issuer']).to eql(issuer)
       expect(result['AuthnRequest']['NameIDPolicy']['Format']).to eql(Saml::Kit::Namespaces::PERSISTENT)
     end
+
+    it 'marks acs_url as deprecated' do
+      subject.acs_url = FFaker::Internet.uri('https')
+      expect(subject.assertion_consumer_service_url).to eql(subject.acs_url)
+    end
   end
 end
spec/saml/builders/identity_provider_metadata_spec.rb
@@ -39,8 +39,8 @@ RSpec.describe Saml::Kit::Builders::IdentityProviderMetadata do
     expect(result['EntityDescriptor']['IDPSSODescriptor']['Attribute']['Name']).to eql("id")
     certificates = result['EntityDescriptor']['IDPSSODescriptor']['KeyDescriptor'].map { |x| x['KeyInfo']['X509Data']['X509Certificate'] }
     expect(certificates).to match_array([
-      Saml::Kit.configuration.stripped_signing_certificate,
-      Saml::Kit.configuration.stripped_encryption_certificate,
+      Saml::Kit.configuration.signing_certificate.stripped,
+      Saml::Kit.configuration.encryption_certificate.stripped,
     ])
     expect(result['EntityDescriptor']['Organization']['OrganizationName']).to eql(org_name)
     expect(result['EntityDescriptor']['Organization']['OrganizationDisplayName']).to eql(org_name)
spec/saml/builders/logout_response_spec.rb
@@ -1,8 +1,7 @@
 require 'spec_helper'
 
 RSpec.describe Saml::Kit::Builders::LogoutResponse do
-  subject { described_class.new(user, request, configuration: configuration) }
-  let(:configuration) { double(issuer: issuer)  }
+  subject { described_class.new(user, request) }
   let(:user) { double(:user, name_id_for: SecureRandom.uuid) }
   let(:request) { Saml::Kit::Builders::LogoutRequest.new(user).build }
   let(:issuer) { FFaker::Internet.http_url }
@@ -12,6 +11,7 @@ RSpec.describe Saml::Kit::Builders::LogoutResponse do
     it 'builds a logout response' do
       travel_to 1.second.from_now
 
+      subject.issuer = issuer
       subject.destination = destination
       result = subject.build
       expect(result.id).to be_present
spec/saml/builders/response_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe Saml::Kit::Builders::Response do
   let(:user) { double(:user, name_id_for: SecureRandom.uuid, assertion_attributes_for: { email: email, created_at: Time.now.utc.iso8601 }) }
   let(:request) { double(:request, id: Saml::Kit::Id.generate, assertion_consumer_service_url: assertion_consumer_service_url, issuer: issuer, name_id_format: Saml::Kit::Namespaces::EMAIL_ADDRESS, provider: provider, trusted?: true, signed?: true) }
   let(:provider) { double(want_assertions_signed: false, encryption_certificates: [Saml::Kit::Certificate.new(encryption_pem, use: :encryption)]) }
-  let(:encryption_pem) { Saml::Kit.configuration.stripped_encryption_certificate }
+  let(:encryption_pem) { Saml::Kit.configuration.encryption_certificate.stripped }
   let(:issuer) { FFaker::Internet.uri("https") }
 
   before :each do
@@ -103,6 +103,22 @@ RSpec.describe Saml::Kit::Builders::Response do
       expect(decrypted_hash['Assertion']['AuthnStatement']['AuthnContext']).to be_present
       expect(decrypted_hash['Assertion']['AuthnStatement']['AuthnContext']['AuthnContextClassRef']).to be_present
     end
+
+    it 'generates a signed response and encrypted assertion' do
+      subject.encrypt = true
+      subject.sign = true
+      result = Hash.from_xml(subject.to_xml)
+      expect(result['Response']['Signature']).to be_present
+      expect(result['Response']['EncryptedAssertion']).to be_present
+    end
+
+    it 'generates a signed response and assertion' do
+      subject.encrypt = false
+      subject.sign = true
+      result = Hash.from_xml(subject.to_xml)
+      expect(result['Response']['Signature']).to be_present
+      expect(result['Response']['Assertion']['Signature']).to be_present
+    end
   end
 
   describe "#destination" do
spec/saml/builders/service_provider_metadata_spec.rb
@@ -2,10 +2,16 @@ require 'spec_helper'
 
 RSpec.describe Saml::Kit::Builders::ServiceProviderMetadata do
   let(:assertion_consumer_service_url) { FFaker::Internet.http_url }
+  let(:email) { FFaker::Internet.email }
+  let(:org_name) { FFaker::Movie.title }
+  let(:url) { FFaker::Internet.uri("https") }
   let(:entity_id) { FFaker::Internet.uri("https") }
 
   it 'builds the service provider metadata' do
+    subject.contact_email = email
     subject.entity_id = entity_id
+    subject.organization_name = org_name
+    subject.organization_url = url
     subject.add_assertion_consumer_service(assertion_consumer_service_url, binding: :http_post)
     subject.name_id_formats = [
       Saml::Kit::Namespaces::PERSISTENT,
@@ -32,8 +38,13 @@ RSpec.describe Saml::Kit::Builders::ServiceProviderMetadata do
     expect(result['EntityDescriptor']['Signature']).to be_present
     expect(result['EntityDescriptor']['SPSSODescriptor']['KeyDescriptor'].map { |x| x['use'] }).to match_array(['signing', 'encryption'])
     expect(result['EntityDescriptor']['SPSSODescriptor']['KeyDescriptor'].map { |x| x['KeyInfo']['X509Data']['X509Certificate'] }).to match_array([
-      Saml::Kit.configuration.stripped_signing_certificate,
-      Saml::Kit.configuration.stripped_encryption_certificate,
+      Saml::Kit.configuration.signing_certificate.stripped,
+      Saml::Kit.configuration.encryption_certificate.stripped,
     ])
+    expect(result['EntityDescriptor']['Organization']['OrganizationName']).to eql(org_name)
+    expect(result['EntityDescriptor']['Organization']['OrganizationDisplayName']).to eql(org_name)
+    expect(result['EntityDescriptor']['Organization']['OrganizationURL']).to eql(url)
+    expect(result['EntityDescriptor']['ContactPerson']['contactType']).to eql("technical")
+    expect(result['EntityDescriptor']['ContactPerson']['Company']).to eql("mailto:#{email}")
   end
 end
spec/saml/certificate_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 RSpec.describe Saml::Kit::Certificate do
-  subject { described_class.new(Saml::Kit.configuration.stripped_signing_certificate, use: :signing) }
+  subject { Saml::Kit.configuration.signing_certificate }
 
   describe "#fingerprint" do
     it 'returns a fingerprint' do
spec/saml/composite_metadata_spec.rb
@@ -0,0 +1,171 @@
+require 'spec_helper'
+
+RSpec.describe Saml::Kit::CompositeMetadata do
+  subject { described_class.new(xml) }
+  let(:post_binding) { Saml::Kit::Bindings::HTTP_POST  }
+  let(:redirect_binding) { Saml::Kit::Bindings::HTTP_REDIRECT }
+  let(:sign_on_service) { FFaker::Internet.uri("https") }
+  let(:assertion_consumer_service) { FFaker::Internet.uri("https") }
+  let(:sp_logout_service) { FFaker::Internet.uri("https") }
+  let(:idp_logout_service) { FFaker::Internet.uri("https") }
+  let(:entity_id) { FFaker::Internet.uri("https") }
+  let(:xml) do
+    <<-XML
+<EntityDescriptor xmlns="#{Saml::Kit::Namespaces::METADATA}" ID="#{Saml::Kit::Id.generate}" entityID="#{entity_id}">
+  <SPSSODescriptor AuthnRequestsSigned="false" WantAssertionsSigned="true" protocolSupportEnumeration="#{Saml::Kit::Namespaces::PROTOCOL}">
+    <KeyDescriptor use="signing">
+      <KeyInfo xmlns="#{Saml::Kit::Namespaces::XMLDSIG}">
+        <X509Data>
+          <X509Certificate>SP-Signing-Certificate</X509Certificate>
+        </X509Data>
+      </KeyInfo>
+    </KeyDescriptor>
+    <KeyDescriptor use="encryption">
+      <KeyInfo xmlns="#{Saml::Kit::Namespaces::XMLDSIG}">
+        <X509Data>
+          <X509Certificate>SP-Encryption-Certificate</X509Certificate>
+        </X509Data>
+      </KeyInfo>
+    </KeyDescriptor>
+    <SingleLogoutService Binding="#{post_binding}" Location="#{sp_logout_service}"/>
+    <NameIDFormat>#{Saml::Kit::Namespaces::PERSISTENT}</NameIDFormat>
+    <AssertionConsumerService Binding="#{post_binding}" Location="#{assertion_consumer_service}" index="0" isDefault="true"/>
+  </SPSSODescriptor>
+  <IDPSSODescriptor WantAuthnRequestsSigned="true" protocolSupportEnumeration="#{Saml::Kit::Namespaces::PROTOCOL}">
+    <KeyDescriptor use="signing">
+      <KeyInfo xmlns="#{Saml::Kit::Namespaces::XMLDSIG}">
+        <X509Data>
+          <X509Certificate>IDP-Signing-Certificate</X509Certificate>
+        </X509Data>
+      </KeyInfo>
+    </KeyDescriptor>
+    <KeyDescriptor use="encryption">
+      <KeyInfo xmlns="#{Saml::Kit::Namespaces::XMLDSIG}">
+        <X509Data>
+          <X509Certificate>IDP-Encryption-Certificate</X509Certificate>
+        </X509Data>
+      </KeyInfo>
+    </KeyDescriptor>
+    <SingleLogoutService Binding="#{post_binding}" Location="#{idp_logout_service}"/>
+    <NameIDFormat>#{Saml::Kit::Namespaces::PERSISTENT}</NameIDFormat>
+    <SingleSignOnService Binding="#{post_binding}" Location="#{sign_on_service}"/>
+    <SingleSignOnService Binding="#{redirect_binding}" Location="#{sign_on_service}"/>
+    <Attribute xmlns="#{Saml::Kit::Namespaces::ASSERTION}" Name="id" ></Attribute>
+  </IDPSSODescriptor>
+  <Organization>
+    <OrganizationName xml:lang="en">Acme, Inc</OrganizationName>
+    <OrganizationDisplayName xml:lang="en">Acme, Inc</OrganizationDisplayName>
+    <OrganizationURL xml:lang="en">http://localhost:5000/</OrganizationURL>
+  </Organization>
+  <ContactPerson contactType="technical">
+    <Company>mailto:hi@example.com</Company>
+  </ContactPerson>
+</EntityDescriptor>
+    XML
+  end
+
+  describe "#single_sign_on_services" do
+    it 'returns the single sign on services from the idp' do
+      expect(subject.single_sign_on_services).to match_array([
+        Saml::Kit::Bindings::HttpPost.new(location: sign_on_service),
+        Saml::Kit::Bindings::HttpRedirect.new(location: sign_on_service),
+      ])
+    end
+  end
+
+  describe "#single_sign_on_service_for" do
+    it 'returns the post binding' do
+      expect(subject.single_sign_on_service_for(binding: :http_post)).to eql(
+        Saml::Kit::Bindings::HttpPost.new(location: sign_on_service)
+      )
+    end
+  end
+
+  it { expect(subject.want_authn_requests_signed).to be_truthy }
+  it { expect(subject.attributes).to match_array([name: 'id', format: nil]) }
+  it { expect(subject.login_request_for(binding: :http_post)).to be_present }
+  it do
+    expect(subject.assertion_consumer_services).to match_array([
+      Saml::Kit::Bindings::HttpPost.new(location: assertion_consumer_service)
+    ])
+  end
+  it do
+    expect(subject.assertion_consumer_service_for(binding: :http_post)).to eql(
+      Saml::Kit::Bindings::HttpPost.new(location: assertion_consumer_service)
+    )
+  end
+  it { expect(subject.want_assertions_signed).to be_truthy }
+  it { expect(subject.entity_id).to eql(entity_id) }
+  it { expect(subject.name_id_formats).to match_array([Saml::Kit::Namespaces::PERSISTENT]) }
+  it do
+    expect(subject.certificates).to match_array([
+      Saml::Kit::Certificate.new('SP-Signing-Certificate', use: :signing),
+      Saml::Kit::Certificate.new('SP-Encryption-Certificate', use: :encryption),
+      Saml::Kit::Certificate.new('IDP-Signing-Certificate', use: :signing),
+      Saml::Kit::Certificate.new('IDP-Encryption-Certificate', use: :encryption),
+    ])
+  end
+
+  it do
+    expect(subject.encryption_certificates).to match_array([
+      Saml::Kit::Certificate.new('SP-Encryption-Certificate', use: :encryption),
+      Saml::Kit::Certificate.new('IDP-Encryption-Certificate', use: :encryption),
+    ])
+  end
+  it do
+    expect(subject.signing_certificates).to match_array([
+      Saml::Kit::Certificate.new('SP-Signing-Certificate', use: :signing),
+      Saml::Kit::Certificate.new('IDP-Signing-Certificate', use: :signing),
+    ])
+  end
+  it do
+    expect(subject.services('SingleLogoutService')).to match_array([
+      Saml::Kit::Bindings::HttpPost.new(location: sp_logout_service),
+      Saml::Kit::Bindings::HttpPost.new(location: idp_logout_service),
+    ])
+  end
+  it do
+    expect(subject.service_for(type: 'SingleLogoutService', binding: :http_post)).to eql(
+      Saml::Kit::Bindings::HttpPost.new(location: sp_logout_service)
+    )
+  end
+  it do
+    expect(subject.services('AssertionConsumerService')).to match_array([
+      Saml::Kit::Bindings::HttpPost.new(location: assertion_consumer_service),
+    ])
+  end
+  it do
+    expect(subject.service_for(type: 'AssertionConsumerService', binding: :http_post)).to eql(
+      Saml::Kit::Bindings::HttpPost.new(location: assertion_consumer_service),
+    )
+  end
+  it do
+    expect(subject.services('SingleSignOnService')).to match_array([
+      Saml::Kit::Bindings::HttpPost.new(location: sign_on_service),
+      Saml::Kit::Bindings::HttpRedirect.new(location: sign_on_service),
+    ])
+  end
+  it do
+    expect(subject.service_for(type: 'SingleSignOnService', binding: :http_post)).to eql(
+      Saml::Kit::Bindings::HttpPost.new(location: sign_on_service),
+    )
+  end
+
+  it do
+    expect(subject.single_logout_services).to match_array([
+      Saml::Kit::Bindings::HttpPost.new(location: sp_logout_service),
+      Saml::Kit::Bindings::HttpPost.new(location: idp_logout_service),
+    ])
+  end
+
+  it do
+    expect(subject.single_logout_service_for(binding: :http_post)).to eql(
+      Saml::Kit::Bindings::HttpPost.new(location: sp_logout_service)
+    )
+  end
+
+  it do
+    user = double(:user, name_id_for: SecureRandom.uuid)
+    expect(subject.logout_request_for(user)).to be_present
+  end
+end
spec/saml/composite_metadata_spec.rb.orig
@@ -0,0 +1,187 @@
+require 'spec_helper'
+
+RSpec.describe Saml::Kit::CompositeMetadata do
+  subject { described_class.new(xml) }
+  let(:post_binding) { Saml::Kit::Bindings::HTTP_POST  }
+  let(:redirect_binding) { Saml::Kit::Bindings::HTTP_REDIRECT }
+  let(:sign_on_service) { FFaker::Internet.uri("https") }
+  let(:assertion_consumer_service) { FFaker::Internet.uri("https") }
+  let(:sp_logout_service) { FFaker::Internet.uri("https") }
+  let(:idp_logout_service) { FFaker::Internet.uri("https") }
+  let(:entity_id) { FFaker::Internet.uri("https") }
+  let(:xml) do
+    <<-XML
+<EntityDescriptor xmlns="#{Saml::Kit::Namespaces::METADATA}" ID="#{Saml::Kit::Id.generate}" entityID="#{entity_id}">
+  <SPSSODescriptor AuthnRequestsSigned="false" WantAssertionsSigned="true" protocolSupportEnumeration="#{Saml::Kit::Namespaces::PROTOCOL}">
+    <KeyDescriptor use="signing">
+      <KeyInfo xmlns="#{Saml::Kit::Namespaces::XMLDSIG}">
+        <X509Data>
+          <X509Certificate>SP-Signing-Certificate</X509Certificate>
+        </X509Data>
+      </KeyInfo>
+    </KeyDescriptor>
+    <KeyDescriptor use="encryption">
+      <KeyInfo xmlns="#{Saml::Kit::Namespaces::XMLDSIG}">
+        <X509Data>
+          <X509Certificate>SP-Encryption-Certificate</X509Certificate>
+        </X509Data>
+      </KeyInfo>
+    </KeyDescriptor>
+    <SingleLogoutService Binding="#{post_binding}" Location="#{sp_logout_service}"/>
+    <NameIDFormat>#{Saml::Kit::Namespaces::PERSISTENT}</NameIDFormat>
+    <AssertionConsumerService Binding="#{post_binding}" Location="#{assertion_consumer_service}" index="0" isDefault="true"/>
+  </SPSSODescriptor>
+  <IDPSSODescriptor WantAuthnRequestsSigned="true" protocolSupportEnumeration="#{Saml::Kit::Namespaces::PROTOCOL}">
+    <KeyDescriptor use="signing">
+      <KeyInfo xmlns="#{Saml::Kit::Namespaces::XMLDSIG}">
+        <X509Data>
+          <X509Certificate>IDP-Signing-Certificate</X509Certificate>
+        </X509Data>
+      </KeyInfo>
+    </KeyDescriptor>
+    <KeyDescriptor use="encryption">
+      <KeyInfo xmlns="#{Saml::Kit::Namespaces::XMLDSIG}">
+        <X509Data>
+          <X509Certificate>IDP-Encryption-Certificate</X509Certificate>
+        </X509Data>
+      </KeyInfo>
+    </KeyDescriptor>
+    <SingleLogoutService Binding="#{post_binding}" Location="#{idp_logout_service}"/>
+    <NameIDFormat>#{Saml::Kit::Namespaces::PERSISTENT}</NameIDFormat>
+    <SingleSignOnService Binding="#{post_binding}" Location="#{sign_on_service}"/>
+    <SingleSignOnService Binding="#{redirect_binding}" Location="#{sign_on_service}"/>
+    <Attribute xmlns="#{Saml::Kit::Namespaces::ASSERTION}" Name="id" ></Attribute>
+  </IDPSSODescriptor>
+  <Organization>
+    <OrganizationName xml:lang="en">Acme, Inc</OrganizationName>
+    <OrganizationDisplayName xml:lang="en">Acme, Inc</OrganizationDisplayName>
+    <OrganizationURL xml:lang="en">http://localhost:5000/</OrganizationURL>
+  </Organization>
+  <ContactPerson contactType="technical">
+    <Company>mailto:hi@example.com</Company>
+  </ContactPerson>
+</EntityDescriptor>
+    XML
+  end
+
+  describe "#single_sign_on_services" do
+    it 'returns the single sign on services from the idp' do
+      expect(subject.single_sign_on_services).to match_array([
+        Saml::Kit::Bindings::HttpPost.new(location: sign_on_service),
+        Saml::Kit::Bindings::HttpRedirect.new(location: sign_on_service),
+      ])
+    end
+  end
+
+  describe "#single_sign_on_service_for" do
+<<<<<<< HEAD
+    it 'returns the http post binding' do
+=======
+    it 'returns the post binding' do
+>>>>>>> 14771fc562487f9c80443197a89e776d256f47c2
+      expect(subject.single_sign_on_service_for(binding: :http_post)).to eql(
+        Saml::Kit::Bindings::HttpPost.new(location: sign_on_service)
+      )
+    end
+<<<<<<< HEAD
+
+    it 'returns the http redirect binding' do
+      expect(subject.single_sign_on_service_for(binding: :http_redirect)).to eql(
+        Saml::Kit::Bindings::HttpRedirect.new(location: sign_on_service)
+      )
+    end
+  end
+
+  describe "#assertion_consumer_services" do
+=======
+  end
+
+  it { expect(subject.want_authn_requests_signed).to be_truthy }
+  it { expect(subject.attributes).to match_array([name: 'id', format: nil]) }
+  it { expect(subject.login_request_for(binding: :http_post)).to be_present }
+  it do
+    expect(subject.assertion_consumer_services).to match_array([
+      Saml::Kit::Bindings::HttpPost.new(location: assertion_consumer_service)
+    ])
+  end
+  it do
+    expect(subject.assertion_consumer_service_for(binding: :http_post)).to eql(
+      Saml::Kit::Bindings::HttpPost.new(location: assertion_consumer_service)
+    )
+  end
+  it { expect(subject.want_assertions_signed).to be_truthy }
+  it { expect(subject.entity_id).to eql(entity_id) }
+  it { expect(subject.name_id_formats).to match_array([Saml::Kit::Namespaces::PERSISTENT]) }
+  it do
+    expect(subject.certificates).to match_array([
+      Saml::Kit::Certificate.new('SP-Signing-Certificate', use: :signing),
+      Saml::Kit::Certificate.new('SP-Encryption-Certificate', use: :encryption),
+      Saml::Kit::Certificate.new('IDP-Signing-Certificate', use: :signing),
+      Saml::Kit::Certificate.new('IDP-Encryption-Certificate', use: :encryption),
+    ])
+  end
+
+  it do
+    expect(subject.encryption_certificates).to match_array([
+      Saml::Kit::Certificate.new('SP-Encryption-Certificate', use: :encryption),
+      Saml::Kit::Certificate.new('IDP-Encryption-Certificate', use: :encryption),
+    ])
+  end
+  it do
+    expect(subject.signing_certificates).to match_array([
+      Saml::Kit::Certificate.new('SP-Signing-Certificate', use: :signing),
+      Saml::Kit::Certificate.new('IDP-Signing-Certificate', use: :signing),
+    ])
+  end
+  it do
+    expect(subject.services('SingleLogoutService')).to match_array([
+      Saml::Kit::Bindings::HttpPost.new(location: sp_logout_service),
+      Saml::Kit::Bindings::HttpPost.new(location: idp_logout_service),
+    ])
+  end
+  it do
+    expect(subject.service_for(type: 'SingleLogoutService', binding: :http_post)).to eql(
+      Saml::Kit::Bindings::HttpPost.new(location: sp_logout_service)
+    )
+  end
+  it do
+    expect(subject.services('AssertionConsumerService')).to match_array([
+      Saml::Kit::Bindings::HttpPost.new(location: assertion_consumer_service),
+    ])
+  end
+  it do
+    expect(subject.service_for(type: 'AssertionConsumerService', binding: :http_post)).to eql(
+      Saml::Kit::Bindings::HttpPost.new(location: assertion_consumer_service),
+    )
+  end
+  it do
+    expect(subject.services('SingleSignOnService')).to match_array([
+      Saml::Kit::Bindings::HttpPost.new(location: sign_on_service),
+      Saml::Kit::Bindings::HttpRedirect.new(location: sign_on_service),
+    ])
+  end
+  it do
+    expect(subject.service_for(type: 'SingleSignOnService', binding: :http_post)).to eql(
+      Saml::Kit::Bindings::HttpPost.new(location: sign_on_service),
+    )
+  end
+
+  it do
+    expect(subject.single_logout_services).to match_array([
+      Saml::Kit::Bindings::HttpPost.new(location: sp_logout_service),
+      Saml::Kit::Bindings::HttpPost.new(location: idp_logout_service),
+    ])
+  end
+
+  it do
+    expect(subject.single_logout_service_for(binding: :http_post)).to eql(
+      Saml::Kit::Bindings::HttpPost.new(location: sp_logout_service)
+    )
+  end
+
+  it do
+    user = double(:user, name_id_for: SecureRandom.uuid)
+    expect(subject.logout_request_for(user)).to be_present
+>>>>>>> 14771fc562487f9c80443197a89e776d256f47c2
+  end
+end
spec/saml/default_registry_spec.rb
@@ -43,7 +43,37 @@ RSpec.describe Saml::Kit::DefaultRegistry do
       expect(result).to be_present
       expect(result).to be_instance_of(Saml::Kit::IdentityProviderMetadata)
     end
-  end
 
-  xit 'registers metadata that serves as both an IDP and SP'
+    it 'registers metadata that serves as both an IDP and SP' do
+      xml = <<-XML
+<EntityDescriptor xmlns="#{Saml::Kit::Namespaces::METADATA}" ID="#{Saml::Kit::Id.generate}" entityID="#{entity_id}">
+  <SPSSODescriptor AuthnRequestsSigned="false" WantAssertionsSigned="true" protocolSupportEnumeration="#{Saml::Kit::Namespaces::PROTOCOL}">
+    <SingleLogoutService Binding="#{Saml::Kit::Bindings::HTTP_POST}" Location="#{FFaker::Internet.uri("https")}"/>
+    <NameIDFormat>#{Saml::Kit::Namespaces::PERSISTENT}</NameIDFormat>
+    <AssertionConsumerService Binding="#{Saml::Kit::Bindings::HTTP_POST}" Location="#{FFaker::Internet.uri("https")}" index="0" isDefault="true"/>
+  </SPSSODescriptor>
+  <IDPSSODescriptor WantAuthnRequestsSigned="true" protocolSupportEnumeration="#{Saml::Kit::Namespaces::PROTOCOL}">
+    <SingleLogoutService Binding="#{Saml::Kit::Bindings::HTTP_POST}" Location="#{FFaker::Internet.uri("https")}"/>
+    <NameIDFormat>#{Saml::Kit::Namespaces::PERSISTENT}</NameIDFormat>
+    <SingleSignOnService Binding="#{Saml::Kit::Bindings::HTTP_POST}" Location="#{FFaker::Internet.uri("https")}"/>
+    <SingleSignOnService Binding="#{Saml::Kit::Bindings::HTTP_REDIRECT}" Location="#{FFaker::Internet.uri("https")}"/>
+  </IDPSSODescriptor>
+  <Organization>
+    <OrganizationName xml:lang="en">Acme, Inc</OrganizationName>
+    <OrganizationDisplayName xml:lang="en">Acme, Inc</OrganizationDisplayName>
+    <OrganizationURL xml:lang="en">http://localhost:5000/</OrganizationURL>
+  </Organization>
+  <ContactPerson contactType="technical">
+    <Company>mailto:hi@example.com</Company>
+  </ContactPerson>
+</EntityDescriptor>
+      XML
+      stub_request(:get, url).to_return(status: 200, body: xml)
+      subject.register_url(url)
+
+      result = subject.metadata_for(entity_id)
+      expect(result).to be_present
+      expect(result).to be_instance_of(Saml::Kit::CompositeMetadata)
+    end
+  end
 end
spec/saml/http_post_spec.rb
@@ -0,0 +1,26 @@
+require 'spec_helper'
+
+RSpec.describe Saml::Kit::Bindings::HttpPost do
+  describe "equality" do
+    let(:location) { FFaker::Internet.uri("https") }
+    subject { Saml::Kit::Bindings::HttpPost.new(location: location) }
+
+    it 'is referentially equal' do
+      expect(subject).to eql(subject)
+    end
+
+    it 'is equal by value' do
+      expect(subject).to eql(
+        Saml::Kit::Bindings::HttpPost.new(location: location)
+      )
+    end
+
+    it 'is not equal' do
+      expect(subject).to_not eql(
+        Saml::Kit::Bindings::HttpPost.new(
+          location: FFaker::Internet.uri("https")
+        )
+      )
+    end
+  end
+end
spec/saml/metadata_spec.rb
@@ -0,0 +1,49 @@
+require 'spec_helper'
+
+RSpec.describe Saml::Kit::Metadata do
+  describe ".from" do
+    subject { described_class }
+
+    it 'returns an identity provider metadata' do
+      xml = Saml::Kit::IdentityProviderMetadata.build.to_xml
+      expect(subject.from(xml)).to be_instance_of(Saml::Kit::IdentityProviderMetadata)
+    end
+
+    it 'returns a service provider metadata' do
+      xml = Saml::Kit::ServiceProviderMetadata.build.to_xml
+      expect(subject.from(xml)).to be_instance_of(Saml::Kit::ServiceProviderMetadata)
+    end
+
+    it 'returns a composite' do
+      xml = <<-XML
+<EntityDescriptor xmlns="#{Saml::Kit::Namespaces::METADATA}" ID="#{Saml::Kit::Id.generate}" entityID="#{FFaker::Internet.uri("https")}">
+  <SPSSODescriptor AuthnRequestsSigned="false" WantAssertionsSigned="true" protocolSupportEnumeration="#{Saml::Kit::Namespaces::PROTOCOL}">
+    <SingleLogoutService Binding="#{Saml::Kit::Bindings::HTTP_POST}" Location="#{FFaker::Internet.uri("https")}"/>
+    <NameIDFormat>#{Saml::Kit::Namespaces::PERSISTENT}</NameIDFormat>
+    <AssertionConsumerService Binding="#{Saml::Kit::Bindings::HTTP_POST}" Location="#{FFaker::Internet.uri("https")}" index="0" isDefault="true"/>
+  </SPSSODescriptor>
+  <IDPSSODescriptor WantAuthnRequestsSigned="true" protocolSupportEnumeration="#{Saml::Kit::Namespaces::PROTOCOL}">
+    <SingleLogoutService Binding="#{Saml::Kit::Bindings::HTTP_POST}" Location="#{FFaker::Internet.uri("https")}"/>
+    <NameIDFormat>#{Saml::Kit::Namespaces::PERSISTENT}</NameIDFormat>
+    <SingleSignOnService Binding="#{Saml::Kit::Bindings::HTTP_POST}" Location="#{FFaker::Internet.uri("https")}"/>
+    <SingleSignOnService Binding="#{Saml::Kit::Bindings::HTTP_REDIRECT}" Location="#{FFaker::Internet.uri("https")}"/>
+  </IDPSSODescriptor>
+  <Organization>
+    <OrganizationName xml:lang="en">Acme, Inc</OrganizationName>
+    <OrganizationDisplayName xml:lang="en">Acme, Inc</OrganizationDisplayName>
+    <OrganizationURL xml:lang="en">http://localhost:5000/</OrganizationURL>
+  </Organization>
+  <ContactPerson contactType="technical">
+    <Company>mailto:hi@example.com</Company>
+  </ContactPerson>
+</EntityDescriptor>
+      XML
+      result = subject.from(xml)
+      expect(result).to be_present
+
+      expect(result.single_sign_on_services.count).to eql(2)
+      expect(result.assertion_consumer_services.count).to eql(1)
+      expect(result.single_logout_services.count).to eql(2)
+    end
+  end
+end
spec/saml/service_provider_metadata_spec.rb
@@ -20,8 +20,8 @@ RSpec.describe Saml::Kit::ServiceProviderMetadata do
 
     it 'returns each of the certificates' do
       expect(subject.certificates).to match_array([
-        Saml::Kit::Certificate.new(Saml::Kit.configuration.stripped_signing_certificate, use: :signing),
-        Saml::Kit::Certificate.new(Saml::Kit.configuration.stripped_encryption_certificate, use: :encryption),
+        Saml::Kit::Certificate.new(Saml::Kit.configuration.signing_certificate.stripped, use: :signing),
+        Saml::Kit::Certificate.new(Saml::Kit.configuration.encryption_certificate.stripped, use: :encryption),
       ])
     end
 
saml-kit.gemspec
@@ -25,6 +25,7 @@ Gem::Specification.new do |spec|
   spec.add_dependency "activemodel", ">= 4.2.0"
   spec.add_dependency "builder", "~> 3.2"
   spec.add_dependency "nokogiri", "~> 1.8"
+  spec.add_dependency "tilt", "~> 2.0"
   spec.add_dependency "xmldsig", "~> 0.6"
   spec.add_development_dependency "bundler", "~> 1.15"
   spec.add_development_dependency "ffaker", "~> 2.7"