saml_idp 0.9.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +54 -49
  3. data/lib/saml_idp/assertion_builder.rb +28 -3
  4. data/lib/saml_idp/configurator.rb +8 -3
  5. data/lib/saml_idp/controller.rb +27 -18
  6. data/lib/saml_idp/encryptor.rb +0 -1
  7. data/lib/saml_idp/fingerprint.rb +19 -0
  8. data/lib/saml_idp/incoming_metadata.rb +22 -0
  9. data/lib/saml_idp/metadata_builder.rb +25 -9
  10. data/lib/saml_idp/persisted_metadata.rb +4 -0
  11. data/lib/saml_idp/request.rb +90 -13
  12. data/lib/saml_idp/response_builder.rb +26 -6
  13. data/lib/saml_idp/saml_response.rb +62 -28
  14. data/lib/saml_idp/service_provider.rb +2 -6
  15. data/lib/saml_idp/signable.rb +1 -2
  16. data/lib/saml_idp/signature_builder.rb +2 -1
  17. data/lib/saml_idp/signed_info_builder.rb +2 -2
  18. data/lib/saml_idp/version.rb +1 -1
  19. data/lib/saml_idp/xml_security.rb +19 -14
  20. data/lib/saml_idp.rb +4 -3
  21. data/saml_idp.gemspec +32 -31
  22. data/spec/lib/saml_idp/assertion_builder_spec.rb +143 -0
  23. data/spec/lib/saml_idp/configurator_spec.rb +40 -2
  24. data/spec/lib/saml_idp/controller_spec.rb +66 -8
  25. data/spec/lib/saml_idp/fingerprint_spec.rb +14 -0
  26. data/spec/lib/saml_idp/incoming_metadata_spec.rb +89 -1
  27. data/spec/lib/saml_idp/metadata_builder_spec.rb +24 -1
  28. data/spec/lib/saml_idp/request_spec.rb +153 -64
  29. data/spec/lib/saml_idp/response_builder_spec.rb +3 -1
  30. data/spec/lib/saml_idp/saml_response_spec.rb +141 -7
  31. data/spec/rails_app/app/controllers/saml_controller.rb +1 -5
  32. data/spec/rails_app/app/controllers/saml_idp_controller.rb +55 -3
  33. data/{app → spec/rails_app/app}/views/saml_idp/idp/new.html.erb +3 -4
  34. data/{app → spec/rails_app/app}/views/saml_idp/idp/saml_post.html.erb +1 -1
  35. data/spec/rails_app/config/application.rb +1 -0
  36. data/spec/rails_app/config/boot.rb +1 -1
  37. data/spec/rails_app/config/environments/development.rb +2 -0
  38. data/spec/spec_helper.rb +20 -1
  39. data/spec/support/certificates/sp_cert_req.csr +12 -0
  40. data/spec/support/certificates/sp_private_key.pem +16 -0
  41. data/spec/support/certificates/sp_x509_cert.crt +18 -0
  42. data/spec/support/saml_request_macros.rb +105 -4
  43. data/spec/support/security_helpers.rb +12 -2
  44. data/spec/xml_security_spec.rb +11 -7
  45. metadata +96 -62
  46. data/app/controllers/saml_idp/idp_controller.rb +0 -59
@@ -19,6 +19,9 @@ module SamlIdp
19
19
  key_transport: 'rsa-oaep-mgf1p',
20
20
  }
21
21
  end
22
+ let(:session_expiry) { nil }
23
+ let(:name_id_formats_opt) { nil }
24
+ let(:asserted_attributes_opt) { nil }
22
25
  subject { described_class.new(
23
26
  reference_id,
24
27
  issuer_uri,
@@ -103,6 +106,76 @@ module SamlIdp
103
106
  expect(encrypted_xml).to_not match(audience_uri)
104
107
  end
105
108
 
109
+ describe "with name_id_formats_opt" do
110
+ let(:name_id_formats_opt) {
111
+ {
112
+ persistent: -> (principal) {
113
+ principal.unique_identifier
114
+ }
115
+ }
116
+ }
117
+ it "delegates name_id_formats to opts" do
118
+ UserWithUniqueId = Struct.new(:unique_identifier, :email, :asserted_attributes)
119
+ principal = UserWithUniqueId.new('unique_identifier_123456', 'foo@example.com', { emailAddress: { getter: :email } })
120
+ builder = described_class.new(
121
+ reference_id,
122
+ issuer_uri,
123
+ principal,
124
+ audience_uri,
125
+ saml_request_id,
126
+ saml_acs_url,
127
+ algorithm,
128
+ authn_context_classref,
129
+ expiry,
130
+ encryption_opts,
131
+ session_expiry,
132
+ name_id_formats_opt,
133
+ asserted_attributes_opt
134
+ )
135
+ Timecop.travel(Time.zone.local(2010, 6, 1, 13, 0, 0)) do
136
+ expect(builder.raw).to eq("<Assertion xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID=\"_abc\" IssueInstant=\"2010-06-01T13:00:00Z\" Version=\"2.0\"><Issuer>http://sportngin.com</Issuer><Subject><NameID Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:persistent\">unique_identifier_123456</NameID><SubjectConfirmation Method=\"urn:oasis:names:tc:SAML:2.0:cm:bearer\"><SubjectConfirmationData InResponseTo=\"123\" NotOnOrAfter=\"2010-06-01T13:03:00Z\" Recipient=\"http://saml.acs.url\"></SubjectConfirmationData></SubjectConfirmation></Subject><Conditions NotBefore=\"2010-06-01T12:59:55Z\" NotOnOrAfter=\"2010-06-01T16:00:00Z\"><AudienceRestriction><Audience>http://example.com</Audience></AudienceRestriction></Conditions><AuthnStatement AuthnInstant=\"2010-06-01T13:00:00Z\" SessionIndex=\"_abc\"><AuthnContext><AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</AuthnContextClassRef></AuthnContext></AuthnStatement><AttributeStatement><Attribute Name=\"emailAddress\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"emailAddress\"><AttributeValue>foo@example.com</AttributeValue></Attribute></AttributeStatement></Assertion>")
137
+ end
138
+ end
139
+ end
140
+
141
+ describe "with asserted_attributes_opt" do
142
+ let(:asserted_attributes_opt) {
143
+ {
144
+ 'GivenName' => {
145
+ getter: :first_name
146
+ },
147
+ 'SurName' => {
148
+ getter: -> (principal) {
149
+ principal.last_name
150
+ }
151
+ }
152
+ }
153
+ }
154
+
155
+ it "delegates asserted_attributes to opts" do
156
+ UserWithName = Struct.new(:email, :first_name, :last_name)
157
+ principal = UserWithName.new('foo@example.com', 'George', 'Washington')
158
+ builder = described_class.new(
159
+ reference_id,
160
+ issuer_uri,
161
+ principal,
162
+ audience_uri,
163
+ saml_request_id,
164
+ saml_acs_url,
165
+ algorithm,
166
+ authn_context_classref,
167
+ expiry,
168
+ encryption_opts,
169
+ session_expiry,
170
+ name_id_formats_opt,
171
+ asserted_attributes_opt
172
+ )
173
+ Timecop.travel(Time.zone.local(2010, 6, 1, 13, 0, 0)) do
174
+ expect(builder.raw).to eq("<Assertion xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID=\"_abc\" IssueInstant=\"2010-06-01T13:00:00Z\" Version=\"2.0\"><Issuer>http://sportngin.com</Issuer><Subject><NameID Format=\"urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress\">foo@example.com</NameID><SubjectConfirmation Method=\"urn:oasis:names:tc:SAML:2.0:cm:bearer\"><SubjectConfirmationData InResponseTo=\"123\" NotOnOrAfter=\"2010-06-01T13:03:00Z\" Recipient=\"http://saml.acs.url\"></SubjectConfirmationData></SubjectConfirmation></Subject><Conditions NotBefore=\"2010-06-01T12:59:55Z\" NotOnOrAfter=\"2010-06-01T16:00:00Z\"><AudienceRestriction><Audience>http://example.com</Audience></AudienceRestriction></Conditions><AuthnStatement AuthnInstant=\"2010-06-01T13:00:00Z\" SessionIndex=\"_abc\"><AuthnContext><AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</AuthnContextClassRef></AuthnContext></AuthnStatement><AttributeStatement><Attribute Name=\"GivenName\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"GivenName\"><AttributeValue>George</AttributeValue></Attribute><Attribute Name=\"SurName\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"SurName\"><AttributeValue>Washington</AttributeValue></Attribute></AttributeStatement></Assertion>")
175
+ end
176
+ end
177
+ end
178
+
106
179
  describe "with custom session_expiry configuration" do
107
180
  let(:config) { SamlIdp::Configurator.new }
108
181
  before do
@@ -126,5 +199,75 @@ module SamlIdp
126
199
  expect(builder.session_expiry).to eq(8)
127
200
  end
128
201
  end
202
+
203
+ describe "with name_id_formats_opt" do
204
+ let(:name_id_formats_opt) {
205
+ {
206
+ persistent: -> (principal) {
207
+ principal.unique_identifier
208
+ }
209
+ }
210
+ }
211
+ it "delegates name_id_formats to opts" do
212
+ UserWithUniqueId = Struct.new(:unique_identifier, :email, :asserted_attributes)
213
+ principal = UserWithUniqueId.new('unique_identifier_123456', 'foo@example.com', { emailAddress: { getter: :email } })
214
+ builder = described_class.new(
215
+ reference_id,
216
+ issuer_uri,
217
+ principal,
218
+ audience_uri,
219
+ saml_request_id,
220
+ saml_acs_url,
221
+ algorithm,
222
+ authn_context_classref,
223
+ expiry,
224
+ encryption_opts,
225
+ session_expiry,
226
+ name_id_formats_opt,
227
+ asserted_attributes_opt
228
+ )
229
+ Timecop.travel(Time.zone.local(2010, 6, 1, 13, 0, 0)) do
230
+ expect(builder.raw).to eq("<Assertion xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID=\"_abc\" IssueInstant=\"2010-06-01T13:00:00Z\" Version=\"2.0\"><Issuer>http://sportngin.com</Issuer><Subject><NameID Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:persistent\">unique_identifier_123456</NameID><SubjectConfirmation Method=\"urn:oasis:names:tc:SAML:2.0:cm:bearer\"><SubjectConfirmationData InResponseTo=\"123\" NotOnOrAfter=\"2010-06-01T13:03:00Z\" Recipient=\"http://saml.acs.url\"></SubjectConfirmationData></SubjectConfirmation></Subject><Conditions NotBefore=\"2010-06-01T12:59:55Z\" NotOnOrAfter=\"2010-06-01T16:00:00Z\"><AudienceRestriction><Audience>http://example.com</Audience></AudienceRestriction></Conditions><AuthnStatement AuthnInstant=\"2010-06-01T13:00:00Z\" SessionIndex=\"_abc\"><AuthnContext><AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</AuthnContextClassRef></AuthnContext></AuthnStatement><AttributeStatement><Attribute Name=\"emailAddress\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"emailAddress\"><AttributeValue>foo@example.com</AttributeValue></Attribute></AttributeStatement></Assertion>")
231
+ end
232
+ end
233
+ end
234
+
235
+ describe "with asserted_attributes_opt" do
236
+ let(:asserted_attributes_opt) {
237
+ {
238
+ 'GivenName' => {
239
+ getter: :first_name
240
+ },
241
+ 'SurName' => {
242
+ getter: -> (principal) {
243
+ principal.last_name
244
+ }
245
+ }
246
+ }
247
+ }
248
+
249
+ it "delegates asserted_attributes to opts" do
250
+ UserWithName = Struct.new(:email, :first_name, :last_name)
251
+ principal = UserWithName.new('foo@example.com', 'George', 'Washington')
252
+ builder = described_class.new(
253
+ reference_id,
254
+ issuer_uri,
255
+ principal,
256
+ audience_uri,
257
+ saml_request_id,
258
+ saml_acs_url,
259
+ algorithm,
260
+ authn_context_classref,
261
+ expiry,
262
+ encryption_opts,
263
+ session_expiry,
264
+ name_id_formats_opt,
265
+ asserted_attributes_opt
266
+ )
267
+ Timecop.travel(Time.zone.local(2010, 6, 1, 13, 0, 0)) do
268
+ expect(builder.raw).to eq("<Assertion xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID=\"_abc\" IssueInstant=\"2010-06-01T13:00:00Z\" Version=\"2.0\"><Issuer>http://sportngin.com</Issuer><Subject><NameID Format=\"urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress\">foo@example.com</NameID><SubjectConfirmation Method=\"urn:oasis:names:tc:SAML:2.0:cm:bearer\"><SubjectConfirmationData InResponseTo=\"123\" NotOnOrAfter=\"2010-06-01T13:03:00Z\" Recipient=\"http://saml.acs.url\"></SubjectConfirmationData></SubjectConfirmation></Subject><Conditions NotBefore=\"2010-06-01T12:59:55Z\" NotOnOrAfter=\"2010-06-01T16:00:00Z\"><AudienceRestriction><Audience>http://example.com</Audience></AudienceRestriction></Conditions><AuthnStatement AuthnInstant=\"2010-06-01T13:00:00Z\" SessionIndex=\"_abc\"><AuthnContext><AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</AuthnContextClassRef></AuthnContext></AuthnStatement><AttributeStatement><Attribute Name=\"GivenName\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"GivenName\"><AttributeValue>George</AttributeValue></Attribute><Attribute Name=\"SurName\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"SurName\"><AttributeValue>Washington</AttributeValue></Attribute></AttributeStatement></Assertion>")
269
+ end
270
+ end
271
+ end
129
272
  end
130
273
  end
@@ -9,6 +9,7 @@ module SamlIdp
9
9
  it { should respond_to :base_saml_location }
10
10
  it { should respond_to :reference_id_generator }
11
11
  it { should respond_to :attribute_service_location }
12
+ it { should respond_to :single_service_redirect_location }
12
13
  it { should respond_to :single_service_post_location }
13
14
  it { should respond_to :single_logout_service_post_location }
14
15
  it { should respond_to :single_logout_service_redirect_location }
@@ -16,13 +17,14 @@ module SamlIdp
16
17
  it { should respond_to :attributes }
17
18
  it { should respond_to :service_provider }
18
19
  it { should respond_to :session_expiry }
20
+ it { should respond_to :logger }
19
21
 
20
22
  it "has a valid x509_certificate" do
21
- expect(subject.x509_certificate).to eq(Default::X509_CERTIFICATE)
23
+ expect(subject.x509_certificate.call).to eq(Default::X509_CERTIFICATE)
22
24
  end
23
25
 
24
26
  it "has a valid secret_key" do
25
- expect(subject.secret_key).to eq(Default::SECRET_KEY)
27
+ expect(subject.secret_key.call).to eq(Default::SECRET_KEY)
26
28
  end
27
29
 
28
30
  it "has a valid algorithm" do
@@ -45,5 +47,41 @@ module SamlIdp
45
47
  it 'has a valid session_expiry' do
46
48
  expect(subject.session_expiry).to eq(0)
47
49
  end
50
+
51
+ context "logger initialization" do
52
+ context 'when Rails has been properly initialized' do
53
+ before do
54
+ stub_const("Rails", double(logger: double("Rails.logger")))
55
+ end
56
+
57
+ it 'sets logger to Rails.logger' do
58
+ expect(subject.logger).to eq(Rails.logger)
59
+ end
60
+ end
61
+
62
+ context 'when Rails is not fully initialized' do
63
+ before do
64
+ stub_const("Rails", Class.new)
65
+ end
66
+
67
+ it 'sets logger to a lambda' do
68
+ expect(subject.logger).to be_a(Proc)
69
+ expect { subject.logger.call("test") }.to output("test\n").to_stdout
70
+ end
71
+ end
72
+
73
+ context 'when Rails is not defined' do
74
+ it 'sets logger to a lambda' do
75
+ hide_const("Rails")
76
+
77
+ expect(subject.logger).to be_a(Proc)
78
+ expect { subject.logger.call("test") }.to output("test\n").to_stdout
79
+ end
80
+ end
81
+
82
+ after do
83
+ hide_const("Rails")
84
+ end
85
+ end
48
86
  end
49
87
  end
@@ -21,6 +21,30 @@ describe SamlIdp::Controller do
21
21
  expect(saml_acs_url).to eq(requested_saml_acs_url)
22
22
  end
23
23
 
24
+ context "When SP metadata required to validate auth request signature" do
25
+ before do
26
+ idp_configure("https://foo.example.com/saml/consume", true)
27
+ params[:SAMLRequest] = make_saml_request("https://foo.example.com/saml/consume", true)
28
+ end
29
+
30
+ it 'SP metadata sign_authn_request attribute should be true' do
31
+ # Signed auth request will be true in the metadata
32
+ expect(SamlIdp.config.service_provider.persisted_metadata_getter.call(nil,nil)[:sign_authn_request]).to eq(true)
33
+ end
34
+
35
+ it 'should call xml signature validation method' do
36
+ signed_doc = SamlIdp::XMLSecurity::SignedDocument.new(decode_saml_request(params[:SAMLRequest]))
37
+ allow(signed_doc).to receive(:validate).and_return(true)
38
+ allow(SamlIdp::XMLSecurity::SignedDocument).to receive(:new).and_return(signed_doc)
39
+ validate_saml_request
40
+ expect(signed_doc).to have_received(:validate).once
41
+ end
42
+
43
+ it 'should successfully validate signature' do
44
+ expect(validate_saml_request).to eq(true)
45
+ end
46
+ end
47
+
24
48
  context "SAML Responses" do
25
49
  let(:principal) { double email_address: "foo@example.com" }
26
50
  let (:encryption_opts) do
@@ -42,6 +66,16 @@ describe SamlIdp::Controller do
42
66
  end
43
67
  end
44
68
 
69
+ context '#encode_authn_response' do
70
+ it 'uses default values when opts are not provided' do
71
+ saml_response = encode_authn_response(principal, { audience_uri: 'http://example.com/issuer', issuer_uri: 'http://example.com', acs_url: 'https://foo.example.com/saml/consume', signed_assertion: false })
72
+
73
+ response = OneLogin::RubySaml::Response.new(saml_response)
74
+ response.settings = saml_settings
75
+ expect(response.document.to_s).to_not include("<ds:Signature>")
76
+ end
77
+ end
78
+
45
79
  context "solicited Response" do
46
80
  before(:each) do
47
81
  params[:SAMLRequest] = make_saml_request
@@ -57,17 +91,13 @@ describe SamlIdp::Controller do
57
91
  expect(response.is_valid?).to be_truthy
58
92
  end
59
93
 
60
- it "should create a SAML Logout Response" do
61
- params[:SAMLRequest] = make_saml_logout_request
62
- expect(validate_saml_request).to eq(true)
63
- expect(saml_request.logout_request?).to eq true
94
+ it "should by default create a SAML Response with a signed assertion" do
64
95
  saml_response = encode_response(principal)
65
- response = OneLogin::RubySaml::Logoutresponse.new(saml_response, saml_settings)
66
- expect(response.validate).to eq(true)
67
- expect(response.issuer).to eq("http://example.com")
96
+ response = OneLogin::RubySaml::Response.new(saml_response)
97
+ response.settings = saml_settings("https://foo.example.com/saml/consume", true)
98
+ expect(response.is_valid?).to be_truthy
68
99
  end
69
100
 
70
-
71
101
  [:sha1, :sha256, :sha384, :sha512].each do |algorithm_name|
72
102
  it "should create a SAML Response using the #{algorithm_name} algorithm" do
73
103
  self.algorithm = algorithm_name
@@ -94,4 +124,32 @@ describe SamlIdp::Controller do
94
124
  end
95
125
  end
96
126
  end
127
+
128
+ context "Single Logout Request" do
129
+ before do
130
+ idp_configure("https://foo.example.com/saml/consume", true)
131
+ slo_request = make_saml_sp_slo_request(security_options: { embed_sign: false })
132
+ params[:SAMLRequest] = slo_request['SAMLRequest']
133
+ params[:RelayState] = slo_request['RelayState']
134
+ params[:SigAlg] = slo_request['SigAlg']
135
+ params[:Signature] = slo_request['Signature']
136
+ end
137
+
138
+ it 'should successfully validate signature' do
139
+ expect(validate_saml_request).to eq(true)
140
+ end
141
+
142
+ context "solicited Response" do
143
+ let(:principal) { double email_address: "foo@example.com" }
144
+
145
+ it "should create a SAML Logout Response" do
146
+ expect(validate_saml_request).to eq(true)
147
+ expect(saml_request.logout_request?).to eq true
148
+ saml_response = encode_response(principal)
149
+ response = OneLogin::RubySaml::Logoutresponse.new(saml_response, saml_settings)
150
+ expect(response.validate).to eq(true)
151
+ expect(response.issuer).to eq("http://idp.com/saml/idp")
152
+ end
153
+ end
154
+ end
97
155
  end
@@ -0,0 +1,14 @@
1
+ require 'spec_helper'
2
+
3
+ module SamlIdp
4
+ describe Fingerprint do
5
+ describe "certificate_digest" do
6
+ let(:cert) { sp_x509_cert }
7
+ let(:fingerprint) { "a2:cb:f6:6b:bc:2a:33:b9:4f:f3:c3:7e:26:a4:21:cd:41:83:ef:26:88:fa:ba:71:37:40:07:3e:d5:76:04:b7" }
8
+
9
+ it "returns the fingerprint string" do
10
+ expect(Fingerprint.certificate_digest(cert, :sha256)).to eq(fingerprint)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -1,9 +1,10 @@
1
1
  require 'spec_helper'
2
+
2
3
  module SamlIdp
3
4
 
4
5
  metadata_1 = <<-eos
5
6
  <md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="test" entityID="https://test-saml.com/saml">
6
- <md:SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol" AuthnRequestsSigned="true" WantAssertionsSigned="false">
7
+ <md:SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol" AuthnRequestsSigned="false" WantAssertionsSigned="false">
7
8
  </md:SPSSODescriptor>
8
9
  </md:EntityDescriptor>
9
10
  eos
@@ -22,6 +23,55 @@ module SamlIdp
22
23
  </md:EntityDescriptor>
23
24
  eos
24
25
 
26
+ metadata_4 = <<-eos
27
+ <md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="test" entityID="https://test-saml.com/saml">
28
+ <md:SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
29
+ </md:SPSSODescriptor>
30
+ </md:EntityDescriptor>
31
+ eos
32
+
33
+ metadata_5 = <<-eos
34
+ <md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="test" entityID="https://test-saml.com/saml">
35
+ <md:SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
36
+ <md:KeyDescriptor>
37
+ <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
38
+ <ds:X509Data>
39
+ <ds:X509Certificate>MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnht3GR...</ds:X509Certificate>
40
+ </ds:X509Data>
41
+ </ds:KeyInfo>
42
+ </md:KeyDescriptor>
43
+ </md:SPSSODescriptor>
44
+ </md:EntityDescriptor>
45
+ eos
46
+
47
+ metadata_6 = <<-eos
48
+ <md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="test" entityID="https://test-saml.com/saml">
49
+ <md:SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
50
+ <md:KeyDescriptor use="signing">
51
+ <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
52
+ <ds:X509Data>
53
+ <ds:X509Certificate>MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmw6vGr...</ds:X509Certificate>
54
+ </ds:X509Data>
55
+ </ds:KeyInfo>
56
+ </md:KeyDescriptor>
57
+ </md:SPSSODescriptor>
58
+ </md:EntityDescriptor>
59
+ eos
60
+
61
+ metadata_7 = <<-eos
62
+ <md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="test" entityID="https://test-saml.com/saml">
63
+ <md:SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
64
+ <md:KeyDescriptor use="encryption">
65
+ <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
66
+ <ds:X509Data>
67
+ <ds:X509Certificate>MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1dX3Gr...</ds:X509Certificate>
68
+ </ds:X509Data>
69
+ </ds:KeyInfo>
70
+ </md:KeyDescriptor>
71
+ </md:SPSSODescriptor>
72
+ </md:EntityDescriptor>
73
+ eos
74
+
25
75
  describe IncomingMetadata do
26
76
  it 'should properly set sign_assertions to false' do
27
77
  metadata = SamlIdp::IncomingMetadata.new(metadata_1)
@@ -36,11 +86,49 @@ module SamlIdp
36
86
  it 'should properly set sign_assertions to true' do
37
87
  metadata = SamlIdp::IncomingMetadata.new(metadata_2)
38
88
  expect(metadata.sign_assertions).to eq(true)
89
+ expect(metadata.sign_authn_request).to eq(true)
39
90
  end
40
91
 
41
92
  it 'should properly set sign_assertions to false when WantAssertionsSigned is not included' do
42
93
  metadata = SamlIdp::IncomingMetadata.new(metadata_3)
43
94
  expect(metadata.sign_assertions).to eq(false)
44
95
  end
96
+
97
+ it 'should properly set sign_authn_request to false when AuthnRequestsSigned is not included' do
98
+ metadata = SamlIdp::IncomingMetadata.new(metadata_4)
99
+ expect(metadata.sign_authn_request).to eq(false)
100
+ end
101
+
102
+ it 'should properly set unspecified_certificate when present' do
103
+ metadata = SamlIdp::IncomingMetadata.new(metadata_5)
104
+ expect(metadata.unspecified_certificate).to eq('MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnht3GR...')
105
+ end
106
+
107
+ it 'should return empty unspecified_certificate when not present' do
108
+ metadata = SamlIdp::IncomingMetadata.new(metadata_1)
109
+ expect(metadata.unspecified_certificate).to eq('')
110
+ end
111
+
112
+ it 'should properly set signing_certificate when present but not unspecified_certificate' do
113
+ metadata = SamlIdp::IncomingMetadata.new(metadata_6)
114
+ expect(metadata.signing_certificate).to eq('MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmw6vGr...')
115
+ expect(metadata.unspecified_certificate).to eq('')
116
+ end
117
+
118
+ it 'should return empty signing_certificate when not present' do
119
+ metadata = SamlIdp::IncomingMetadata.new(metadata_1)
120
+ expect(metadata.signing_certificate).to eq('')
121
+ end
122
+
123
+ it 'should properly set encryption_certificate when present but not unspecified_certificate' do
124
+ metadata = SamlIdp::IncomingMetadata.new(metadata_7)
125
+ expect(metadata.encryption_certificate).to eq('MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1dX3Gr...')
126
+ expect(metadata.unspecified_certificate).to eq('')
127
+ end
128
+
129
+ it 'should return empty encryption_certificate when not present' do
130
+ metadata = SamlIdp::IncomingMetadata.new(metadata_1)
131
+ expect(metadata.encryption_certificate).to eq('')
132
+ end
45
133
  end
46
134
  end
@@ -6,12 +6,35 @@ module SamlIdp
6
6
  end
7
7
 
8
8
  it "signs valid xml" do
9
- expect(Saml::XML::Document.parse(subject.signed).valid_signature?(Default::FINGERPRINT)).to be_truthy
9
+ expect(Saml::XML::Document.parse(subject.signed).valid_signature?("", Default::FINGERPRINT)).to be_truthy
10
10
  end
11
11
 
12
12
  it "includes logout element" do
13
13
  subject.configurator.single_logout_service_post_location = 'https://example.com/saml/logout'
14
+ subject.configurator.single_logout_service_redirect_location = 'https://example.com/saml/logout'
14
15
  expect(subject.fresh).to match('<SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://example.com/saml/logout"/>')
16
+ expect(subject.fresh).to match('<SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://example.com/saml/logout"/>')
17
+ end
18
+
19
+ it 'will not includes empty logout endpoint' do
20
+ subject.configurator.single_logout_service_post_location = ''
21
+ subject.configurator.single_logout_service_redirect_location = nil
22
+ expect(subject.fresh).not_to match('<SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"')
23
+ expect(subject.fresh).not_to match('<SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"')
24
+ end
25
+
26
+ it 'will includes sso element' do
27
+ subject.configurator.single_service_post_location = 'https://example.com/saml/sso'
28
+ subject.configurator.single_service_redirect_location = 'https://example.com/saml/sso'
29
+ expect(subject.fresh).to match('<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://example.com/saml/sso"/>')
30
+ expect(subject.fresh).to match('<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://example.com/saml/sso"/>')
31
+ end
32
+
33
+ it 'will not includes empty sso element' do
34
+ subject.configurator.single_service_post_location = ''
35
+ subject.configurator.single_service_redirect_location = nil
36
+ expect(subject.fresh).not_to match('<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"')
37
+ expect(subject.fresh).not_to match('<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"')
15
38
  end
16
39
 
17
40
  context "technical contact" do