saml_idp 0.7.2 → 0.16.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 (57) hide show
  1. checksums.yaml +5 -5
  2. data/Gemfile +1 -1
  3. data/README.md +59 -52
  4. data/lib/saml_idp/assertion_builder.rb +28 -3
  5. data/lib/saml_idp/configurator.rb +7 -1
  6. data/lib/saml_idp/controller.rb +21 -13
  7. data/lib/saml_idp/encryptor.rb +0 -1
  8. data/lib/saml_idp/fingerprint.rb +19 -0
  9. data/lib/saml_idp/incoming_metadata.rb +22 -1
  10. data/lib/saml_idp/metadata_builder.rb +23 -8
  11. data/lib/saml_idp/persisted_metadata.rb +4 -0
  12. data/lib/saml_idp/request.rb +26 -6
  13. data/lib/saml_idp/response_builder.rb +26 -6
  14. data/lib/saml_idp/saml_response.rb +62 -28
  15. data/lib/saml_idp/service_provider.rb +15 -6
  16. data/lib/saml_idp/signable.rb +1 -2
  17. data/lib/saml_idp/version.rb +1 -1
  18. data/lib/saml_idp/xml_security.rb +1 -1
  19. data/lib/saml_idp.rb +2 -1
  20. data/saml_idp.gemspec +45 -42
  21. data/spec/acceptance/idp_controller_spec.rb +5 -4
  22. data/spec/lib/saml_idp/algorithmable_spec.rb +6 -6
  23. data/spec/lib/saml_idp/assertion_builder_spec.rb +151 -8
  24. data/spec/lib/saml_idp/attribute_decorator_spec.rb +8 -8
  25. data/spec/lib/saml_idp/configurator_spec.rb +9 -7
  26. data/spec/lib/saml_idp/controller_spec.rb +53 -20
  27. data/spec/lib/saml_idp/encryptor_spec.rb +4 -4
  28. data/spec/lib/saml_idp/fingerprint_spec.rb +14 -0
  29. data/spec/lib/saml_idp/incoming_metadata_spec.rb +60 -0
  30. data/spec/lib/saml_idp/metadata_builder_spec.rb +30 -17
  31. data/spec/lib/saml_idp/name_id_formatter_spec.rb +3 -3
  32. data/spec/lib/saml_idp/request_spec.rb +78 -27
  33. data/spec/lib/saml_idp/response_builder_spec.rb +5 -3
  34. data/spec/lib/saml_idp/saml_response_spec.rb +127 -12
  35. data/spec/lib/saml_idp/service_provider_spec.rb +2 -2
  36. data/spec/lib/saml_idp/signable_spec.rb +1 -1
  37. data/spec/lib/saml_idp/signature_builder_spec.rb +2 -2
  38. data/spec/lib/saml_idp/signed_info_builder_spec.rb +3 -3
  39. data/spec/rails_app/app/controllers/saml_controller.rb +1 -1
  40. data/spec/rails_app/app/controllers/saml_idp_controller.rb +55 -3
  41. data/{app → spec/rails_app/app}/views/saml_idp/idp/new.html.erb +1 -5
  42. data/{app → spec/rails_app/app}/views/saml_idp/idp/saml_post.html.erb +1 -1
  43. data/spec/rails_app/config/application.rb +1 -6
  44. data/spec/rails_app/config/boot.rb +1 -1
  45. data/spec/rails_app/config/environments/development.rb +2 -5
  46. data/spec/rails_app/config/environments/production.rb +1 -0
  47. data/spec/rails_app/config/environments/test.rb +1 -0
  48. data/spec/spec_helper.rb +23 -1
  49. data/spec/support/certificates/sp_cert_req.csr +12 -0
  50. data/spec/support/certificates/sp_private_key.pem +16 -0
  51. data/spec/support/certificates/sp_x509_cert.crt +18 -0
  52. data/spec/support/saml_request_macros.rb +66 -4
  53. data/spec/support/security_helpers.rb +10 -0
  54. data/spec/xml_security_spec.rb +12 -12
  55. metadata +135 -81
  56. data/app/controllers/saml_idp/idp_controller.rb +0 -59
  57. data/spec/lib/saml_idp/.assertion_builder_spec.rb.swp +0 -0
@@ -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,
@@ -36,14 +39,14 @@ module SamlIdp
36
39
 
37
40
  it "builds a legit raw XML file" do
38
41
  Timecop.travel(Time.zone.local(2010, 6, 1, 13, 0, 0)) do
39
- subject.raw.should == "<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 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=\"email-address\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"emailAddress\"><AttributeValue>foo@example.com</AttributeValue></Attribute></AttributeStatement></Assertion>"
42
+ expect(subject.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 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=\"email-address\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"emailAddress\"><AttributeValue>foo@example.com</AttributeValue></Attribute></AttributeStatement></Assertion>")
40
43
  end
41
44
  end
42
45
  end
43
46
 
44
47
  it "builds a legit raw XML file" do
45
48
  Timecop.travel(Time.zone.local(2010, 6, 1, 13, 0, 0)) do
46
- subject.raw.should == "<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=\"email-address\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"emailAddress\"><AttributeValue>foo@example.com</AttributeValue></Attribute></AttributeStatement></Assertion>"
49
+ expect(subject.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=\"email-address\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"emailAddress\"><AttributeValue>foo@example.com</AttributeValue></Attribute></AttributeStatement></Assertion>")
47
50
  end
48
51
  end
49
52
 
@@ -55,12 +58,12 @@ module SamlIdp
55
58
  email_address: ->(p) { "foo@example.com" }
56
59
  }
57
60
  }
58
- SamlIdp.stub(config: config)
61
+ allow(SamlIdp).to receive(:config).and_return(config)
59
62
  end
60
63
 
61
64
  it "doesn't include attribute statement" do
62
65
  Timecop.travel(Time.zone.local(2010, 6, 1, 13, 0, 0)) do
63
- subject.raw.should == "<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></Assertion>"
66
+ expect(subject.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></Assertion>")
64
67
  end
65
68
  end
66
69
  end
@@ -81,7 +84,7 @@ module SamlIdp
81
84
  expiry
82
85
  )
83
86
  Timecop.travel(Time.zone.local(2010, 6, 1, 13, 0, 0)) do
84
- builder.raw.should == "<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=\"emailAddress\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"emailAddress\"><AttributeValue>foo@example.com</AttributeValue></Attribute></AttributeStatement></Assertion>"
87
+ 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=\"emailAddress\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"emailAddress\"><AttributeValue>foo@example.com</AttributeValue></Attribute></AttributeStatement></Assertion>")
85
88
  end
86
89
  end
87
90
  end
@@ -100,14 +103,84 @@ module SamlIdp
100
103
  encryption_opts
101
104
  )
102
105
  encrypted_xml = builder.encrypt
103
- encrypted_xml.should_not match(audience_uri)
106
+ expect(encrypted_xml).to_not match(audience_uri)
107
+ end
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
104
177
  end
105
178
 
106
179
  describe "with custom session_expiry configuration" do
107
180
  let(:config) { SamlIdp::Configurator.new }
108
181
  before do
109
182
  config.session_expiry = 8
110
- SamlIdp.stub(config: config)
183
+ allow(SamlIdp).to receive(:config).and_return(config)
111
184
  end
112
185
 
113
186
  it "sets default session_expiry from config" do
@@ -123,7 +196,77 @@ module SamlIdp
123
196
  expiry,
124
197
  encryption_opts
125
198
  )
126
- builder.session_expiry.should == 8
199
+ expect(builder.session_expiry).to eq(8)
200
+ end
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
127
270
  end
128
271
  end
129
272
  end
@@ -12,19 +12,19 @@ module SamlIdp
12
12
  let(:values) { nil }
13
13
 
14
14
  it "has a valid name" do
15
- subject.name.should be_nil
15
+ expect(subject.name).to be_nil
16
16
  end
17
17
 
18
18
  it "has a valid friendly_name" do
19
- subject.friendly_name.should be_nil
19
+ expect(subject.friendly_name).to be_nil
20
20
  end
21
21
 
22
22
  it "has a valid name_format" do
23
- subject.name_format.should == Saml::XML::Namespaces::Formats::Attr::URI
23
+ expect(subject.name_format).to eq(Saml::XML::Namespaces::Formats::Attr::URI)
24
24
  end
25
25
 
26
26
  it "has a valid values" do
27
- subject.values.should == []
27
+ expect(subject.values).to eq []
28
28
  end
29
29
 
30
30
  describe "with values set" do
@@ -34,19 +34,19 @@ module SamlIdp
34
34
  let(:values) { :val }
35
35
 
36
36
  it "has a valid name" do
37
- subject.name.should == name
37
+ expect(subject.name).to eq(name)
38
38
  end
39
39
 
40
40
  it "has a valid friendly_name" do
41
- subject.friendly_name.should == friendly_name
41
+ expect(subject.friendly_name).to eq(friendly_name)
42
42
  end
43
43
 
44
44
  it "has a valid name_format" do
45
- subject.name_format.should == name_format
45
+ expect(subject.name_format).to eq(name_format)
46
46
  end
47
47
 
48
48
  it "has a valid values" do
49
- subject.values.should == [values]
49
+ expect(subject.values).to eq [values]
50
50
  end
51
51
  end
52
52
  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,34 +17,35 @@ 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
- subject.x509_certificate.should == Default::X509_CERTIFICATE
23
+ expect(subject.x509_certificate).to eq(Default::X509_CERTIFICATE)
22
24
  end
23
25
 
24
26
  it "has a valid secret_key" do
25
- subject.secret_key.should == Default::SECRET_KEY
27
+ expect(subject.secret_key).to eq(Default::SECRET_KEY)
26
28
  end
27
29
 
28
30
  it "has a valid algorithm" do
29
- subject.algorithm.should == :sha1
31
+ expect(subject.algorithm).to eq(:sha1)
30
32
  end
31
33
 
32
34
  it "has a valid reference_id_generator" do
33
- subject.reference_id_generator.should respond_to :call
35
+ expect(subject.reference_id_generator).to respond_to :call
34
36
  end
35
37
 
36
38
 
37
39
  it "can call service provider finder" do
38
- subject.service_provider.finder.should respond_to :call
40
+ expect(subject.service_provider.finder).to respond_to :call
39
41
  end
40
42
 
41
43
  it "can call service provider metadata persister" do
42
- subject.service_provider.metadata_persister.should respond_to :call
44
+ expect(subject.service_provider.metadata_persister).to respond_to :call
43
45
  end
44
46
 
45
47
  it 'has a valid session_expiry' do
46
- subject.session_expiry.should == 0
48
+ expect(subject.session_expiry).to eq(0)
47
49
  end
48
50
  end
49
51
  end
@@ -7,6 +7,9 @@ describe SamlIdp::Controller do
7
7
  def render(*)
8
8
  end
9
9
 
10
+ def head(*)
11
+ end
12
+
10
13
  def params
11
14
  @params ||= {}
12
15
  end
@@ -14,8 +17,32 @@ describe SamlIdp::Controller do
14
17
  it "should find the SAML ACS URL" do
15
18
  requested_saml_acs_url = "https://example.com/saml/consume"
16
19
  params[:SAMLRequest] = make_saml_request(requested_saml_acs_url)
17
- validate_saml_request
18
- saml_acs_url.should == requested_saml_acs_url
20
+ expect(validate_saml_request).to eq(true)
21
+ expect(saml_acs_url).to eq(requested_saml_acs_url)
22
+ end
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(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
19
46
  end
20
47
 
21
48
  context "SAML Responses" do
@@ -32,48 +59,54 @@ describe SamlIdp::Controller do
32
59
  it "should create a SAML Response" do
33
60
  saml_response = encode_response(principal, { audience_uri: 'http://example.com/issuer', issuer_uri: 'http://example.com', acs_url: 'https://foo.example.com/saml/consume' })
34
61
  response = OneLogin::RubySaml::Response.new(saml_response)
35
- response.name_id.should == "foo@example.com"
36
- response.issuers.first.should == "http://example.com"
62
+ expect(response.name_id).to eq("foo@example.com")
63
+ expect(response.issuers.first).to eq("http://example.com")
37
64
  response.settings = saml_settings
38
- response.is_valid?.should be_truthy
65
+ expect(response.is_valid?).to be_truthy
39
66
  end
40
67
  end
41
68
 
42
69
  context "solicited Response" do
43
70
  before(:each) do
44
71
  params[:SAMLRequest] = make_saml_request
45
- validate_saml_request
72
+ expect(validate_saml_request).to eq(true)
46
73
  end
47
74
 
48
75
  it "should create a SAML Response" do
49
76
  saml_response = encode_response(principal)
50
77
  response = OneLogin::RubySaml::Response.new(saml_response)
51
- response.name_id.should == "foo@example.com"
52
- response.issuers.first.should == "http://example.com"
78
+ expect(response.name_id).to eq("foo@example.com")
79
+ expect(response.issuers.first).to eq("http://example.com")
53
80
  response.settings = saml_settings
54
- response.is_valid?.should be_truthy
81
+ expect(response.is_valid?).to be_truthy
55
82
  end
56
83
 
57
84
  it "should create a SAML Logout Response" do
58
85
  params[:SAMLRequest] = make_saml_logout_request
59
- validate_saml_request
86
+ expect(validate_saml_request).to eq(true)
60
87
  expect(saml_request.logout_request?).to eq true
61
88
  saml_response = encode_response(principal)
62
89
  response = OneLogin::RubySaml::Logoutresponse.new(saml_response, saml_settings)
63
- response.validate.should == true
64
- response.issuer.should == "http://example.com"
90
+ expect(response.validate).to eq(true)
91
+ expect(response.issuer).to eq("http://example.com")
65
92
  end
66
93
 
94
+ it "should by default create a SAML Response with a signed assertion" do
95
+ saml_response = encode_response(principal)
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
99
+ end
67
100
 
68
101
  [:sha1, :sha256, :sha384, :sha512].each do |algorithm_name|
69
102
  it "should create a SAML Response using the #{algorithm_name} algorithm" do
70
103
  self.algorithm = algorithm_name
71
104
  saml_response = encode_response(principal)
72
105
  response = OneLogin::RubySaml::Response.new(saml_response)
73
- response.name_id.should == "foo@example.com"
74
- response.issuers.first.should == "http://example.com"
106
+ expect(response.name_id).to eq("foo@example.com")
107
+ expect(response.issuers.first).to eq("http://example.com")
75
108
  response.settings = saml_settings
76
- response.is_valid?.should be_truthy
109
+ expect(response.is_valid?).to be_truthy
77
110
  end
78
111
 
79
112
  it "should encrypt SAML Response assertion" do
@@ -82,11 +115,11 @@ describe SamlIdp::Controller do
82
115
  resp_settings = saml_settings
83
116
  resp_settings.private_key = SamlIdp::Default::SECRET_KEY
84
117
  response = OneLogin::RubySaml::Response.new(saml_response, settings: resp_settings)
85
- response.document.to_s.should_not match("foo@example.com")
86
- response.decrypted_document.to_s.should match("foo@example.com")
87
- response.name_id.should == "foo@example.com"
88
- response.issuers.first.should == "http://example.com"
89
- response.is_valid?.should be_truthy
118
+ expect(response.document.to_s).to_not match("foo@example.com")
119
+ expect(response.decrypted_document.to_s).to match("foo@example.com")
120
+ expect(response.name_id).to eq("foo@example.com")
121
+ expect(response.issuers.first).to eq("http://example.com")
122
+ expect(response.is_valid?).to be_truthy
90
123
  end
91
124
  end
92
125
  end
@@ -5,11 +5,11 @@ require 'saml_idp/encryptor'
5
5
  module SamlIdp
6
6
  describe Encryptor do
7
7
  let (:encryption_opts) do
8
- {
8
+ {
9
9
  cert: Default::X509_CERTIFICATE,
10
10
  block_encryption: 'aes256-cbc',
11
11
  key_transport: 'rsa-oaep-mgf1p',
12
- }
12
+ }
13
13
  end
14
14
 
15
15
  subject { described_class.new encryption_opts }
@@ -17,11 +17,11 @@ module SamlIdp
17
17
  it "encrypts XML" do
18
18
  raw_xml = '<foo>bar</foo>'
19
19
  encrypted_xml = subject.encrypt(raw_xml)
20
- encrypted_xml.should_not match 'bar'
20
+ expect(encrypted_xml).to_not match raw_xml
21
21
  encrypted_doc = Nokogiri::XML::Document.parse(encrypted_xml)
22
22
  encrypted_data = Xmlenc::EncryptedData.new(encrypted_doc.at_xpath('//xenc:EncryptedData', Xmlenc::NAMESPACES))
23
23
  decrypted_xml = encrypted_data.decrypt(subject.encryption_key)
24
- decrypted_xml.should == raw_xml
24
+ expect(decrypted_xml).to eq(raw_xml)
25
25
  end
26
26
  end
27
27
  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
@@ -0,0 +1,60 @@
1
+ require 'spec_helper'
2
+ module SamlIdp
3
+
4
+ metadata_1 = <<-eos
5
+ <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="false" WantAssertionsSigned="false">
7
+ </md:SPSSODescriptor>
8
+ </md:EntityDescriptor>
9
+ eos
10
+
11
+ metadata_2 = <<-eos
12
+ <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">
13
+ <md:SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol" AuthnRequestsSigned="true" WantAssertionsSigned="true">
14
+ </md:SPSSODescriptor>
15
+ </md:EntityDescriptor>
16
+ eos
17
+
18
+ metadata_3 = <<-eos
19
+ <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">
20
+ <md:SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol" AuthnRequestsSigned="true">
21
+ </md:SPSSODescriptor>
22
+ </md:EntityDescriptor>
23
+ eos
24
+
25
+ metadata_4 = <<-eos
26
+ <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">
27
+ <md:SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
28
+ </md:SPSSODescriptor>
29
+ </md:EntityDescriptor>
30
+ eos
31
+
32
+ describe IncomingMetadata do
33
+ it 'should properly set sign_assertions to false' do
34
+ metadata = SamlIdp::IncomingMetadata.new(metadata_1)
35
+ expect(metadata.sign_assertions).to eq(false)
36
+ expect(metadata.sign_authn_request).to eq(false)
37
+ end
38
+
39
+ it 'should properly set entity_id as https://test-saml.com/saml' do
40
+ metadata = SamlIdp::IncomingMetadata.new(metadata_1)
41
+ expect(metadata.entity_id).to eq('https://test-saml.com/saml')
42
+ end
43
+
44
+ it 'should properly set sign_assertions to true' do
45
+ metadata = SamlIdp::IncomingMetadata.new(metadata_2)
46
+ expect(metadata.sign_assertions).to eq(true)
47
+ expect(metadata.sign_authn_request).to eq(true)
48
+ end
49
+
50
+ it 'should properly set sign_assertions to false when WantAssertionsSigned is not included' do
51
+ metadata = SamlIdp::IncomingMetadata.new(metadata_3)
52
+ expect(metadata.sign_assertions).to eq(false)
53
+ end
54
+
55
+ it 'should properly set sign_authn_request to false when AuthnRequestsSigned is not included' do
56
+ metadata = SamlIdp::IncomingMetadata.new(metadata_4)
57
+ expect(metadata.sign_authn_request).to eq(false)
58
+ end
59
+ end
60
+ end