main
  1# frozen_string_literal: true
  2
  3RSpec.describe Saml::Kit::Metadata do
  4  describe '.from' do
  5    subject { described_class }
  6
  7    it 'returns an identity provider metadata' do
  8      xml = described_class.build_xml(&:build_identity_provider)
  9      expect(subject.from(xml)).to be_instance_of(Saml::Kit::IdentityProviderMetadata)
 10    end
 11
 12    it 'returns a service provider metadata' do
 13      xml = described_class.build_xml(&:build_service_provider)
 14      expect(subject.from(xml)).to be_instance_of(Saml::Kit::ServiceProviderMetadata)
 15    end
 16
 17    it 'generates a full metadata in a reasonable amount of time' do
 18      expect do
 19        described_class.build_xml do |x|
 20          x.build_identity_provider
 21          x.build_service_provider
 22        end
 23      end.to perform_under(50).ms
 24    end
 25
 26    it 'returns a composite' do
 27      xml = <<-XML.strip_heredoc
 28        <EntityDescriptor xmlns="#{Saml::Kit::Namespaces::METADATA}" ID="#{Xml::Kit::Id.generate}" entityID="#{FFaker::Internet.uri('https')}">
 29          <SPSSODescriptor AuthnRequestsSigned="false" WantAssertionsSigned="true" protocolSupportEnumeration="#{Saml::Kit::Namespaces::PROTOCOL}">
 30            <SingleLogoutService Binding="#{Saml::Kit::Bindings::HTTP_POST}" Location="#{FFaker::Internet.uri('https')}"/>
 31            <NameIDFormat>#{Saml::Kit::Namespaces::PERSISTENT}</NameIDFormat>
 32            <AssertionConsumerService Binding="#{Saml::Kit::Bindings::HTTP_POST}" Location="#{FFaker::Internet.uri('https')}" index="0" isDefault="true"/>
 33          </SPSSODescriptor>
 34          <IDPSSODescriptor WantAuthnRequestsSigned="true" protocolSupportEnumeration="#{Saml::Kit::Namespaces::PROTOCOL}">
 35            <SingleLogoutService Binding="#{Saml::Kit::Bindings::HTTP_POST}" Location="#{FFaker::Internet.uri('https')}"/>
 36            <NameIDFormat>#{Saml::Kit::Namespaces::PERSISTENT}</NameIDFormat>
 37            <SingleSignOnService Binding="#{Saml::Kit::Bindings::HTTP_POST}" Location="#{FFaker::Internet.uri('https')}"/>
 38            <SingleSignOnService Binding="#{Saml::Kit::Bindings::HTTP_REDIRECT}" Location="#{FFaker::Internet.uri('https')}"/>
 39          </IDPSSODescriptor>
 40          <Organization>
 41            <OrganizationName xml:lang="en">Acme, Inc</OrganizationName>
 42            <OrganizationDisplayName xml:lang="en">Acme, Inc</OrganizationDisplayName>
 43            <OrganizationURL xml:lang="en">http://localhost:5000/</OrganizationURL>
 44          </Organization>
 45          <ContactPerson contactType="technical">
 46            <Company>mailto:hi@example.com</Company>
 47          </ContactPerson>
 48        </EntityDescriptor>
 49      XML
 50      result = subject.from(xml)
 51      expect(result).to be_present
 52      expect(result.single_sign_on_services.count).to eq(2)
 53      expect(result.assertion_consumer_services.count).to eq(1)
 54      expect(result.single_logout_services.count).to eq(2)
 55      expect(result.organization_name).to eql('Acme, Inc')
 56      expect(result.organization_url).to eql('http://localhost:5000/')
 57      expect(result.contact_person_company).to eql('mailto:hi@example.com')
 58    end
 59  end
 60
 61  describe '#certificates' do
 62    it 'returns each certificate when missing a "use"' do
 63      configuration = Saml::Kit::Configuration.new do |config|
 64        config.generate_key_pair_for(use: :signing)
 65      end
 66      xml = described_class.build_xml(configuration: configuration) do |x|
 67        x.embed_signature = false
 68        x.build_identity_provider
 69      end
 70      modified_xml = xml.gsub(/use/, 'misuse')
 71      subject = described_class.from(modified_xml)
 72      expect(subject.certificates.count).to be(1)
 73    end
 74  end
 75
 76  describe '#signature' do
 77    it 'returns the signature' do
 78      subject = described_class.build do |x|
 79        x.sign_with(::Xml::Kit::KeyPair.generate(use: :signing))
 80        x.build_identity_provider
 81      end
 82
 83      expect(subject.signature).to be_present
 84    end
 85  end
 86
 87  describe 'validations' do
 88    it 'is invalid, when the digest value is invalid' do
 89      xml = described_class.build_xml do |x|
 90        x.entity_id = 'original'
 91        x.sign_with(::Xml::Kit::KeyPair.generate(use: :signing))
 92        x.build_identity_provider do |y|
 93          y.add_single_sign_on_service(FFaker::Internet.uri('https'), binding: :http_post)
 94        end
 95      end
 96
 97      subject = described_class.from(xml.gsub('original', 'altered'))
 98      expect(subject).not_to be_valid
 99      expect(subject.errors[:digest_value]).to include('is invalid.')
100    end
101
102    it 'is invalid when the signature is invalid' do
103      xml = described_class.build_xml do |x|
104        x.sign_with(::Xml::Kit::KeyPair.generate(use: :signing))
105        x.build_identity_provider do |y|
106          y.add_single_sign_on_service(FFaker::Internet.uri('https'), binding: :http_post)
107        end
108      end
109      document = Nokogiri::XML(xml)
110      node = document.at_xpath('/*/ds:Signature/ds:SignatureValue', ds: Xml::Kit::Namespaces::XMLDSIG)
111      node.content = Base64.encode64('invalid')
112
113      subject = described_class.from(document.to_s)
114      expect(subject).not_to be_valid
115      expect(subject.errors[:signature]).to include('is invalid.')
116    end
117  end
118end