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