main
1# frozen_string_literal: true
2
3RSpec.describe Saml::Kit::ServiceProviderMetadata do
4 let(:entity_id) { FFaker::Internet.uri('https') }
5 let(:acs_post_url) { FFaker::Internet.uri('https') }
6 let(:acs_redirect_url) { FFaker::Internet.uri('https') }
7 let(:logout_post_url) { FFaker::Internet.uri('https') }
8 let(:logout_redirect_url) { FFaker::Internet.uri('https') }
9
10 describe described_class do
11 subject do
12 described_class.build do |builder|
13 builder.entity_id = entity_id
14 builder.add_assertion_consumer_service(acs_post_url, binding: :http_post)
15 builder.add_assertion_consumer_service(acs_redirect_url, binding: :http_redirect)
16 builder.add_single_logout_service(logout_post_url, binding: :http_post)
17 builder.add_single_logout_service(logout_redirect_url, binding: :http_redirect)
18 end
19 end
20
21 it 'returns each of the certificates' do
22 expected_certificates = Saml::Kit.configuration.certificates.map do |x|
23 ::Xml::Kit::Certificate.new(x.stripped, use: x.use)
24 end
25 expect(subject.certificates).to match_array(expected_certificates)
26 end
27
28 it 'returns each acs url and binding' do
29 expect(subject.assertion_consumer_services.map(&:to_h)).to match_array([
30 { location: acs_post_url, binding: Saml::Kit::Bindings::HTTP_POST },
31 { location: acs_redirect_url, binding: Saml::Kit::Bindings::HTTP_REDIRECT },
32 ])
33 end
34
35 it 'returns each logout url and binding' do
36 expect(subject.single_logout_services.map(&:to_h)).to match_array([
37 { location: logout_post_url, binding: Saml::Kit::Bindings::HTTP_POST },
38 { location: logout_redirect_url, binding: Saml::Kit::Bindings::HTTP_REDIRECT },
39 ])
40 end
41
42 it 'returns each of the nameid formats' do
43 expect(subject.name_id_formats).to match_array([
44 Saml::Kit::Namespaces::PERSISTENT
45 ])
46 end
47
48 it 'returns the entity id' do
49 expect(subject.entity_id).to eql(entity_id)
50 end
51 end
52
53 describe '#validate' do
54 let(:service_provider_metadata) do
55 described_class.build(configuration: configuration) do |builder|
56 builder.entity_id = entity_id
57 builder.add_assertion_consumer_service(acs_post_url, binding: :http_post)
58 builder.add_assertion_consumer_service(acs_redirect_url, binding: :http_redirect)
59 builder.add_single_logout_service(logout_post_url, binding: :http_post)
60 builder.add_single_logout_service(logout_redirect_url, binding: :http_redirect)
61 end.to_xml
62 end
63 let(:configuration) do
64 Saml::Kit::Configuration.new do |config|
65 config.generate_key_pair_for(use: :signing)
66 end
67 end
68
69 it 'valid when given valid service provider metadata' do
70 expect(described_class.new(service_provider_metadata)).to be_valid
71 end
72
73 it 'is invalid, when given identity provider metadata' do
74 subject = described_class.new(IO.read('spec/fixtures/metadata/okta.xml'))
75 expect(subject).to be_invalid
76 expect(subject.errors[:base]).to include(I18n.translate('saml/kit.errors.SPSSODescriptor.invalid'))
77 end
78
79 it 'is invalid, when the metadata is nil' do
80 subject = described_class.new(nil)
81 expect(subject).to be_invalid
82 expect(subject.errors[:metadata]).to include("can't be blank")
83 end
84
85 it 'is invalid, when the metadata does not validate against the xsd schema' do
86 xml = ::Builder::XmlMarkup.new
87 xml.instruct!
88 xml.EntityDescriptor 'xmlns': Saml::Kit::Namespaces::METADATA do
89 xml.SPSSODescriptor do
90 xml.Fake foo: :bar
91 end
92 end
93 subject = described_class.new(xml.target!)
94 expect(subject).not_to be_valid
95 expect(subject.errors[:base][0]).to include("1:0: ERROR: Element '{urn:oasis:names:tc:SAML:2.0:metadata}EntityDescriptor'")
96 end
97
98 it 'is invalid, when the signature is invalid' do
99 new_url = 'https://myserver.com/hacked'
100 metadata_xml = service_provider_metadata.gsub(acs_post_url, new_url)
101 subject = described_class.new(metadata_xml)
102 expect(subject).to be_invalid
103 expect(subject.errors[:digest_value]).to include('is invalid.')
104 end
105
106 it 'is invalid when 0 ACS endpoints are specified' do
107 xml = <<-XML.strip_heredoc
108 <?xml version="1.0" encoding="UTF-8"?>
109 <EntityDescriptor xmlns="#{Saml::Kit::Namespaces::METADATA}" ID="#{::Xml::Kit::Id.generate}" entityID="#{entity_id}">
110 <SPSSODescriptor AuthnRequestsSigned="false" WantAssertionsSigned="true" protocolSupportEnumeration="#{Saml::Kit::Namespaces::PROTOCOL}">
111 <SingleLogoutService Binding="#{Saml::Kit::Bindings::HTTP_POST}" Location="#{FFaker::Internet.uri('https')}"/>
112 <NameIDFormat>#{Saml::Kit::Namespaces::PERSISTENT}</NameIDFormat>
113 </SPSSODescriptor>
114 </EntityDescriptor>
115 XML
116 expect(described_class.new(xml)).to be_invalid
117 end
118 end
119
120 describe '#matches?' do
121 subject { described_class.build(configuration: configuration) }
122
123 let(:configuration) do
124 config = Saml::Kit::Configuration.new
125 config.generate_key_pair_for(use: :signing)
126 config
127 end
128
129 it 'returns true when the fingerprint matches one of the signing certificates' do
130 certificate = Hash.from_xml(subject.to_xml)['EntityDescriptor']['Signature']['KeyInfo']['X509Data']['X509Certificate']
131 fingerprint = ::Xml::Kit::Fingerprint.new(certificate)
132 expect(subject).to be_matches(fingerprint)
133 end
134
135 it 'returns false when the fingerprint does not match one of the signing certificates' do
136 certificate, = ::Xml::Kit::SelfSignedCertificate.new.create(passphrase: 'password')
137 fingerprint = ::Xml::Kit::Fingerprint.new(certificate)
138 expect(subject).not_to be_matches(fingerprint)
139 end
140 end
141
142 describe '.build' do
143 let(:assertion_consumer_service_url) { FFaker::Internet.uri('https') }
144
145 it 'provides a nice API for building metadata' do
146 result = described_class.build do |builder|
147 builder.entity_id = entity_id
148 builder.add_assertion_consumer_service(assertion_consumer_service_url, binding: :http_post)
149 end
150
151 expect(result).to be_instance_of(described_class)
152 expect(result.entity_id).to eql(entity_id)
153 expect(result.assertion_consumer_service_for(binding: :http_post).location).to eql(assertion_consumer_service_url)
154 end
155 end
156end