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.
- checksums.yaml +5 -5
- data/Gemfile +1 -1
- data/README.md +59 -52
- data/lib/saml_idp/assertion_builder.rb +28 -3
- data/lib/saml_idp/configurator.rb +7 -1
- data/lib/saml_idp/controller.rb +21 -13
- data/lib/saml_idp/encryptor.rb +0 -1
- data/lib/saml_idp/fingerprint.rb +19 -0
- data/lib/saml_idp/incoming_metadata.rb +22 -1
- data/lib/saml_idp/metadata_builder.rb +23 -8
- data/lib/saml_idp/persisted_metadata.rb +4 -0
- data/lib/saml_idp/request.rb +26 -6
- data/lib/saml_idp/response_builder.rb +26 -6
- data/lib/saml_idp/saml_response.rb +62 -28
- data/lib/saml_idp/service_provider.rb +15 -6
- data/lib/saml_idp/signable.rb +1 -2
- data/lib/saml_idp/version.rb +1 -1
- data/lib/saml_idp/xml_security.rb +1 -1
- data/lib/saml_idp.rb +2 -1
- data/saml_idp.gemspec +45 -42
- data/spec/acceptance/idp_controller_spec.rb +5 -4
- data/spec/lib/saml_idp/algorithmable_spec.rb +6 -6
- data/spec/lib/saml_idp/assertion_builder_spec.rb +151 -8
- data/spec/lib/saml_idp/attribute_decorator_spec.rb +8 -8
- data/spec/lib/saml_idp/configurator_spec.rb +9 -7
- data/spec/lib/saml_idp/controller_spec.rb +53 -20
- data/spec/lib/saml_idp/encryptor_spec.rb +4 -4
- data/spec/lib/saml_idp/fingerprint_spec.rb +14 -0
- data/spec/lib/saml_idp/incoming_metadata_spec.rb +60 -0
- data/spec/lib/saml_idp/metadata_builder_spec.rb +30 -17
- data/spec/lib/saml_idp/name_id_formatter_spec.rb +3 -3
- data/spec/lib/saml_idp/request_spec.rb +78 -27
- data/spec/lib/saml_idp/response_builder_spec.rb +5 -3
- data/spec/lib/saml_idp/saml_response_spec.rb +127 -12
- data/spec/lib/saml_idp/service_provider_spec.rb +2 -2
- data/spec/lib/saml_idp/signable_spec.rb +1 -1
- data/spec/lib/saml_idp/signature_builder_spec.rb +2 -2
- data/spec/lib/saml_idp/signed_info_builder_spec.rb +3 -3
- data/spec/rails_app/app/controllers/saml_controller.rb +1 -1
- data/spec/rails_app/app/controllers/saml_idp_controller.rb +55 -3
- data/{app → spec/rails_app/app}/views/saml_idp/idp/new.html.erb +1 -5
- data/{app → spec/rails_app/app}/views/saml_idp/idp/saml_post.html.erb +1 -1
- data/spec/rails_app/config/application.rb +1 -6
- data/spec/rails_app/config/boot.rb +1 -1
- data/spec/rails_app/config/environments/development.rb +2 -5
- data/spec/rails_app/config/environments/production.rb +1 -0
- data/spec/rails_app/config/environments/test.rb +1 -0
- data/spec/spec_helper.rb +23 -1
- data/spec/support/certificates/sp_cert_req.csr +12 -0
- data/spec/support/certificates/sp_private_key.pem +16 -0
- data/spec/support/certificates/sp_x509_cert.crt +18 -0
- data/spec/support/saml_request_macros.rb +66 -4
- data/spec/support/security_helpers.rb +10 -0
- data/spec/xml_security_spec.rb +12 -12
- metadata +135 -81
- data/app/controllers/saml_idp/idp_controller.rb +0 -59
- 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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
36
|
-
response.issuers.first.
|
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
|
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.
|
52
|
-
response.issuers.first.
|
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
|
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.
|
64
|
-
response.issuer.
|
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.
|
74
|
-
response.issuers.first.
|
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
|
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.
|
86
|
-
response.decrypted_document.to_s.
|
87
|
-
response.name_id.
|
88
|
-
response.issuers.first.
|
89
|
-
response.is_valid
|
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.
|
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.
|
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
|