main
  1# frozen_string_literal: true
  2
  3RSpec.describe Saml::Kit::LogoutRequest do
  4  subject { described_class.build(user, configuration: configuration) }
  5
  6  let(:user) { User.new(name_id: name_id) }
  7  let(:name_id) { SecureRandom.uuid }
  8  let(:entity_id) { FFaker::Internet.uri('https') }
  9  let(:registry) { instance_double(Saml::Kit::DefaultRegistry) }
 10  let(:configuration) do
 11    Saml::Kit::Configuration.new do |config|
 12      config.entity_id = entity_id
 13      config.registry = registry
 14      config.generate_key_pair_for(use: :signing)
 15    end
 16  end
 17
 18  it 'parses the issuer' do
 19    subject = described_class.build(user, configuration: configuration) do |builder|
 20      builder.issuer = entity_id
 21    end
 22    expect(subject.issuer).to eql(entity_id)
 23  end
 24
 25  it 'parses the issue instant' do
 26    travel_to 1.second.from_now
 27    expect(subject.issue_instant).to eql(Time.now.utc)
 28  end
 29
 30  specify { expect(subject.version).to eql('2.0') }
 31
 32  it 'parses the destination' do
 33    destination = FFaker::Internet.uri('https')
 34    subject = described_class.build(user, configuration: configuration) do |builder|
 35      builder.destination = destination
 36    end
 37    expect(subject.destination).to eql(destination)
 38  end
 39
 40  specify { expect(subject.name_id).to eql(name_id) }
 41  specify { expect(subject.name_id_format).to eql(Saml::Kit::Namespaces::PERSISTENT) }
 42
 43  describe '#valid?' do
 44    let(:metadata) do
 45      Saml::Kit::ServiceProviderMetadata.build(configuration: configuration) do |builder|
 46        builder.entity_id = entity_id
 47        builder.add_single_logout_service(FFaker::Internet.uri('https'), binding: :http_post)
 48      end
 49    end
 50
 51    before do
 52      allow(registry).to receive(:metadata_for).and_return(metadata)
 53    end
 54
 55    it 'is valid when left untampered' do
 56      expect(subject).to be_valid
 57    end
 58
 59    it 'is invalid if the document has been tampered with' do
 60      issuer = FFaker::Internet.uri('https')
 61      raw_xml = described_class.build(user, configuration: configuration) do |builder|
 62        builder.issuer = issuer
 63      end.to_xml.gsub(issuer, 'corrupt')
 64
 65      expect(described_class.new(raw_xml)).to be_invalid
 66    end
 67
 68    it 'is invalid when blank' do
 69      subject = described_class.new('')
 70      expect(subject).to be_invalid
 71      expect(subject.errors[:content]).to be_present
 72    end
 73
 74    it 'is invalid when not a LogoutRequest' do
 75      subject = described_class.new(Saml::Kit::IdentityProviderMetadata.build.to_xml)
 76      expect(subject).to be_invalid
 77      expect(subject.errors[:base]).to include(subject.error_message(:invalid))
 78    end
 79
 80    it 'is invalid when the fingerprint of the certificate does not match the registered fingerprint' do
 81      allow(metadata).to receive(:matches?).and_return(false)
 82      expect(subject).to be_invalid
 83      expect(subject.errors[:fingerprint]).to be_present
 84    end
 85
 86    it 'is invalid when the provider is not known' do
 87      allow(registry).to receive(:metadata_for).and_return(nil)
 88      expect(subject).to be_invalid
 89      expect(subject.errors[:provider]).to be_present
 90    end
 91
 92    it 'is invalid when single logout service url is not provided' do
 93      allow(metadata).to receive(:matches?).and_return(true)
 94      allow(metadata).to receive(:single_logout_services).and_return([])
 95
 96      expect(subject).to be_invalid
 97      expect(subject.errors[:single_logout_service]).to be_present
 98    end
 99
100    it 'is valid when a single logout service url is available via the registry' do
101      issuer = FFaker::Internet.uri('https')
102      allow(registry).to receive(:metadata_for).with(issuer).and_return(metadata)
103      allow(metadata).to receive(:matches?).and_return(true)
104      allow(metadata).to receive(:single_logout_services).and_return([
105        Saml::Kit::Bindings::HttpPost.new(location: FFaker::Internet.uri('https'))
106      ])
107
108      subject = described_class.build(user, configuration: configuration) do |builder|
109        builder.issuer = issuer
110      end
111      expect(subject).to be_valid
112    end
113
114    it 'validates the schema of the request' do
115      id = Xml::Kit::Id.generate
116      key_pair = ::Xml::Kit::KeyPair.generate(use: :signing)
117      signed_xml = ::Xml::Kit::Signatures.sign(key_pair: key_pair) do |xml, signature|
118        xml.LogoutRequest ID: id do
119          signature.template(id)
120          xml.Fake do
121            xml.NotAllowed 'Huh?'
122          end
123        end
124      end
125      expect(described_class.new(signed_xml)).to be_invalid
126    end
127  end
128
129  describe '#response_for' do
130    let(:provider) do
131      Saml::Kit::IdentityProviderMetadata.build do |builder|
132        builder.add_single_logout_service(FFaker::Internet.uri('https'), binding: :http_post)
133      end
134    end
135
136    it 'serializes a logout response for a particular user' do
137      allow(subject).to receive(:provider).and_return(provider)
138
139      _, saml_params = subject.response_for(binding: :http_post)
140      response_binding = provider.single_logout_service_for(binding: :http_post)
141      result = response_binding.deserialize(saml_params)
142      expect(result).to be_instance_of(Saml::Kit::LogoutResponse)
143    end
144  end
145end