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.
- checksums.yaml +4 -4
- data/README.md +54 -49
- data/lib/saml_idp/assertion_builder.rb +28 -3
- data/lib/saml_idp/configurator.rb +8 -3
- data/lib/saml_idp/controller.rb +27 -18
- data/lib/saml_idp/encryptor.rb +0 -1
- data/lib/saml_idp/fingerprint.rb +19 -0
- data/lib/saml_idp/incoming_metadata.rb +22 -0
- data/lib/saml_idp/metadata_builder.rb +25 -9
- data/lib/saml_idp/persisted_metadata.rb +4 -0
- data/lib/saml_idp/request.rb +90 -13
- 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 +2 -6
- data/lib/saml_idp/signable.rb +1 -2
- data/lib/saml_idp/signature_builder.rb +2 -1
- data/lib/saml_idp/signed_info_builder.rb +2 -2
- data/lib/saml_idp/version.rb +1 -1
- data/lib/saml_idp/xml_security.rb +19 -14
- data/lib/saml_idp.rb +4 -3
- data/saml_idp.gemspec +32 -31
- data/spec/lib/saml_idp/assertion_builder_spec.rb +143 -0
- data/spec/lib/saml_idp/configurator_spec.rb +40 -2
- data/spec/lib/saml_idp/controller_spec.rb +66 -8
- data/spec/lib/saml_idp/fingerprint_spec.rb +14 -0
- data/spec/lib/saml_idp/incoming_metadata_spec.rb +89 -1
- data/spec/lib/saml_idp/metadata_builder_spec.rb +24 -1
- data/spec/lib/saml_idp/request_spec.rb +153 -64
- data/spec/lib/saml_idp/response_builder_spec.rb +3 -1
- data/spec/lib/saml_idp/saml_response_spec.rb +141 -7
- data/spec/rails_app/app/controllers/saml_controller.rb +1 -5
- 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 +3 -4
- data/{app → spec/rails_app/app}/views/saml_idp/idp/saml_post.html.erb +1 -1
- data/spec/rails_app/config/application.rb +1 -0
- data/spec/rails_app/config/boot.rb +1 -1
- data/spec/rails_app/config/environments/development.rb +2 -0
- data/spec/spec_helper.rb +20 -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 +105 -4
- data/spec/support/security_helpers.rb +12 -2
- data/spec/xml_security_spec.rb +11 -7
- metadata +96 -62
- 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
|
|
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::
|
|
66
|
-
|
|
67
|
-
expect(response.
|
|
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="
|
|
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
|