saml_idp 0.12.0 → 0.15.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 +24 -53
- data/lib/saml_idp/assertion_builder.rb +28 -3
- data/lib/saml_idp/configurator.rb +2 -0
- data/lib/saml_idp/controller.rb +14 -10
- data/lib/saml_idp/encryptor.rb +0 -1
- data/lib/saml_idp/request.rb +4 -3
- data/lib/saml_idp/response_builder.rb +12 -6
- data/lib/saml_idp/saml_response.rb +52 -30
- data/lib/saml_idp/version.rb +1 -1
- data/lib/saml_idp.rb +1 -1
- data/saml_idp.gemspec +30 -29
- data/spec/lib/saml_idp/assertion_builder_spec.rb +143 -0
- data/spec/lib/saml_idp/configurator_spec.rb +1 -0
- data/spec/lib/saml_idp/request_spec.rb +43 -9
- data/spec/lib/saml_idp/saml_response_spec.rb +103 -11
- 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 +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 -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 +1 -1
- metadata +68 -54
- 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
|
@@ -17,6 +17,7 @@ module SamlIdp
|
|
17
17
|
it { should respond_to :attributes }
|
18
18
|
it { should respond_to :service_provider }
|
19
19
|
it { should respond_to :session_expiry }
|
20
|
+
it { should respond_to :logger }
|
20
21
|
|
21
22
|
it "has a valid x509_certificate" do
|
22
23
|
expect(subject.x509_certificate).to eq(Default::X509_CERTIFICATE)
|
@@ -1,7 +1,10 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
module SamlIdp
|
3
3
|
describe Request do
|
4
|
-
let(:
|
4
|
+
let(:issuer) { 'localhost:3000' }
|
5
|
+
let(:raw_authn_request) do
|
6
|
+
"<samlp:AuthnRequest AssertionConsumerServiceURL='http://localhost:3000/saml/consume' Destination='http://localhost:1337/saml/auth' ID='_af43d1a0-e111-0130-661a-3c0754403fdb' IssueInstant='2013-08-06T22:01:35Z' Version='2.0' xmlns:samlp='urn:oasis:names:tc:SAML:2.0:protocol'><saml:Issuer xmlns:saml='urn:oasis:names:tc:SAML:2.0:assertion'>#{issuer}</saml:Issuer><samlp:NameIDPolicy AllowCreate='true' Format='urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress' xmlns:samlp='urn:oasis:names:tc:SAML:2.0:protocol'/><samlp:RequestedAuthnContext Comparison='exact'><saml:AuthnContextClassRef xmlns:saml='urn:oasis:names:tc:SAML:2.0:assertion'>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</saml:AuthnContextClassRef></samlp:RequestedAuthnContext></samlp:AuthnRequest>"
|
7
|
+
end
|
5
8
|
|
6
9
|
describe "deflated request" do
|
7
10
|
let(:deflated_request) { Base64.encode64(Zlib::Deflate.deflate(raw_authn_request, 9)[2..-5]) }
|
@@ -57,16 +60,47 @@ module SamlIdp
|
|
57
60
|
expect(subject.request['ID']).to eq(subject.request_id)
|
58
61
|
end
|
59
62
|
|
60
|
-
it
|
61
|
-
expect(subject.requested_authn_context).to eq(
|
63
|
+
it 'has a valid authn context' do
|
64
|
+
expect(subject.requested_authn_context).to eq('urn:oasis:names:tc:SAML:2.0:ac:classes:Password')
|
62
65
|
end
|
63
66
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
67
|
+
context 'the issuer is empty' do
|
68
|
+
let(:issuer) { nil }
|
69
|
+
let(:logger) { ->(msg) { puts msg } }
|
70
|
+
|
71
|
+
before do
|
72
|
+
allow(SamlIdp.config).to receive(:logger).and_return(logger)
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'is invalid' do
|
76
|
+
expect(subject.issuer).to_not eq('')
|
77
|
+
expect(subject.issuer).to be_nil
|
78
|
+
expect(subject.valid?).to eq(false)
|
79
|
+
end
|
80
|
+
|
81
|
+
context 'a Ruby Logger is configured' do
|
82
|
+
let(:logger) { Logger.new($stdout) }
|
83
|
+
|
84
|
+
before do
|
85
|
+
allow(logger).to receive(:info)
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'logs an error message' do
|
89
|
+
expect(subject.valid?).to be false
|
90
|
+
expect(logger).to have_received(:info).with('Unable to find service provider for issuer ')
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
context 'a logger lambda is configured' do
|
95
|
+
let(:logger) { double }
|
96
|
+
|
97
|
+
before { allow(logger).to receive(:call) }
|
98
|
+
|
99
|
+
it 'logs an error message' do
|
100
|
+
expect(subject.valid?).to be false
|
101
|
+
expect(logger).to have_received(:call).with('Unable to find service provider for issuer ')
|
102
|
+
end
|
103
|
+
end
|
70
104
|
end
|
71
105
|
end
|
72
106
|
|
@@ -26,6 +26,8 @@ module SamlIdp
|
|
26
26
|
end
|
27
27
|
let(:signed_response_opts) { true }
|
28
28
|
let(:unsigned_response_opts) { false }
|
29
|
+
let(:signed_assertion_opts) { true }
|
30
|
+
let(:compress_opts) { false }
|
29
31
|
let(:subject_encrypted) { described_class.new(reference_id,
|
30
32
|
response_id,
|
31
33
|
issuer_uri,
|
@@ -38,7 +40,11 @@ module SamlIdp
|
|
38
40
|
expiry,
|
39
41
|
encryption_opts,
|
40
42
|
session_expiry,
|
41
|
-
|
43
|
+
nil,
|
44
|
+
nil,
|
45
|
+
unsigned_response_opts,
|
46
|
+
signed_assertion_opts,
|
47
|
+
compress_opts
|
42
48
|
)
|
43
49
|
}
|
44
50
|
|
@@ -54,7 +60,11 @@ module SamlIdp
|
|
54
60
|
expiry,
|
55
61
|
nil,
|
56
62
|
session_expiry,
|
57
|
-
|
63
|
+
nil,
|
64
|
+
nil,
|
65
|
+
signed_response_opts,
|
66
|
+
signed_assertion_opts,
|
67
|
+
compress_opts
|
58
68
|
)
|
59
69
|
}
|
60
70
|
|
@@ -70,15 +80,97 @@ module SamlIdp
|
|
70
80
|
expect(subject.build).to be_present
|
71
81
|
end
|
72
82
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
83
|
+
context "encrypted" do
|
84
|
+
it "builds encrypted" do
|
85
|
+
expect(subject_encrypted.build).to_not match(audience_uri)
|
86
|
+
encoded_xml = subject_encrypted.build
|
87
|
+
resp_settings = saml_settings(saml_acs_url)
|
88
|
+
resp_settings.private_key = Default::SECRET_KEY
|
89
|
+
resp_settings.issuer = audience_uri
|
90
|
+
saml_resp = OneLogin::RubySaml::Response.new(encoded_xml, settings: resp_settings)
|
91
|
+
saml_resp.soft = false
|
92
|
+
expect(saml_resp.is_valid?).to eq(true)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
context "signed response" do
|
97
|
+
let(:resp_settings) do
|
98
|
+
resp_settings = saml_settings(saml_acs_url)
|
99
|
+
resp_settings.private_key = Default::SECRET_KEY
|
100
|
+
resp_settings.issuer = audience_uri
|
101
|
+
resp_settings
|
102
|
+
end
|
103
|
+
|
104
|
+
it "will build signed valid response" do
|
105
|
+
expect { subject.build }.not_to raise_error
|
106
|
+
signed_encoded_xml = subject.build
|
107
|
+
saml_resp = OneLogin::RubySaml::Response.new(signed_encoded_xml, settings: resp_settings)
|
108
|
+
expect(
|
109
|
+
Nokogiri::XML(saml_resp.response).at_xpath(
|
110
|
+
"//p:Response//ds:Signature",
|
111
|
+
{
|
112
|
+
"p" => "urn:oasis:names:tc:SAML:2.0:protocol",
|
113
|
+
"ds" => "http://www.w3.org/2000/09/xmldsig#"
|
114
|
+
}
|
115
|
+
)).to be_present
|
116
|
+
expect(saml_resp.send(:validate_signature)).to eq(true)
|
117
|
+
expect(saml_resp.is_valid?).to eq(true)
|
118
|
+
end
|
119
|
+
|
120
|
+
context "when signed_assertion_opts is true" do
|
121
|
+
it "builds a signed assertion" do
|
122
|
+
expect { subject.build }.not_to raise_error
|
123
|
+
signed_encoded_xml = subject.build
|
124
|
+
saml_resp = OneLogin::RubySaml::Response.new(signed_encoded_xml, settings: resp_settings)
|
125
|
+
expect(
|
126
|
+
Nokogiri::XML(saml_resp.response).at_xpath(
|
127
|
+
"//p:Response//a:Assertion//ds:Signature",
|
128
|
+
{
|
129
|
+
"p" => "urn:oasis:names:tc:SAML:2.0:protocol",
|
130
|
+
"a" => "urn:oasis:names:tc:SAML:2.0:assertion",
|
131
|
+
"ds" => "http://www.w3.org/2000/09/xmldsig#"
|
132
|
+
}
|
133
|
+
)).to be_present
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
context "when signed_assertion_opts is false" do
|
138
|
+
let(:signed_assertion_opts) { false }
|
139
|
+
|
140
|
+
it "builds a raw assertion" do
|
141
|
+
expect { subject.build }.not_to raise_error
|
142
|
+
signed_encoded_xml = subject.build
|
143
|
+
saml_resp = OneLogin::RubySaml::Response.new(signed_encoded_xml, settings: resp_settings)
|
144
|
+
expect(
|
145
|
+
Nokogiri::XML(saml_resp.response).at_xpath(
|
146
|
+
"//p:Response//a:Assertion",
|
147
|
+
{
|
148
|
+
"p" => "urn:oasis:names:tc:SAML:2.0:protocol",
|
149
|
+
"a" => "urn:oasis:names:tc:SAML:2.0:assertion"
|
150
|
+
}
|
151
|
+
)).to be_present
|
152
|
+
|
153
|
+
expect(
|
154
|
+
Nokogiri::XML(saml_resp.response).at_xpath(
|
155
|
+
"//p:Response//Assertion//ds:Signature",
|
156
|
+
{
|
157
|
+
"p" => "urn:oasis:names:tc:SAML:2.0:protocol",
|
158
|
+
"ds" => "http://www.w3.org/2000/09/xmldsig#"
|
159
|
+
}
|
160
|
+
)).to_not be_present
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
context "when compress opts is true" do
|
165
|
+
let(:compress_opts) { true }
|
166
|
+
it "will build a compressed valid response" do
|
167
|
+
expect { subject.build }.not_to raise_error
|
168
|
+
compressed_signed_encoded_xml = subject.build
|
169
|
+
saml_resp = OneLogin::RubySaml::Response.new(compressed_signed_encoded_xml, settings: resp_settings)
|
170
|
+
expect(saml_resp.send(:validate_signature)).to eq(true)
|
171
|
+
expect(saml_resp.is_valid?).to eq(true)
|
172
|
+
end
|
173
|
+
end
|
82
174
|
end
|
83
175
|
|
84
176
|
it "will build signed valid response" do
|
@@ -2,11 +2,7 @@ class SamlController < ApplicationController
|
|
2
2
|
|
3
3
|
def consume
|
4
4
|
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse])
|
5
|
-
|
6
|
-
render :text => response.name_id
|
7
|
-
else
|
8
|
-
render :plain => response.name_id
|
9
|
-
end
|
5
|
+
render :plain => response.name_id
|
10
6
|
end
|
11
7
|
|
12
8
|
end
|
@@ -1,9 +1,61 @@
|
|
1
|
-
class SamlIdpController <
|
1
|
+
class SamlIdpController < ApplicationController
|
2
|
+
include SamlIdp::Controller
|
3
|
+
|
4
|
+
if Rails::VERSION::MAJOR >= 4
|
5
|
+
before_action :add_view_path, only: [:new, :create, :logout]
|
6
|
+
before_action :validate_saml_request, only: [:new, :create, :logout]
|
7
|
+
else
|
8
|
+
before_filter :add_view_path, only: [:new, :create, :logout]
|
9
|
+
before_filter :validate_saml_request, only: [:new, :create, :logout]
|
10
|
+
end
|
11
|
+
|
12
|
+
def new
|
13
|
+
render template: "saml_idp/idp/new"
|
14
|
+
end
|
15
|
+
|
16
|
+
def show
|
17
|
+
render xml: SamlIdp.metadata.signed
|
18
|
+
end
|
19
|
+
|
20
|
+
def create
|
21
|
+
unless params[:email].blank? && params[:password].blank?
|
22
|
+
person = idp_authenticate(params[:email], params[:password])
|
23
|
+
if person.nil?
|
24
|
+
@saml_idp_fail_msg = "Incorrect email or password."
|
25
|
+
else
|
26
|
+
@saml_response = idp_make_saml_response(person)
|
27
|
+
render :template => "saml_idp/idp/saml_post", :layout => false
|
28
|
+
return
|
29
|
+
end
|
30
|
+
end
|
31
|
+
render :template => "saml_idp/idp/new"
|
32
|
+
end
|
33
|
+
|
34
|
+
def logout
|
35
|
+
idp_logout
|
36
|
+
@saml_response = idp_make_saml_response(nil)
|
37
|
+
render :template => "saml_idp/idp/saml_post", :layout => false
|
38
|
+
end
|
39
|
+
|
40
|
+
def idp_logout
|
41
|
+
raise NotImplementedError
|
42
|
+
end
|
43
|
+
private :idp_logout
|
44
|
+
|
2
45
|
def idp_authenticate(email, password)
|
3
46
|
{ :email => email }
|
4
47
|
end
|
48
|
+
protected :idp_authenticate
|
5
49
|
|
6
|
-
def idp_make_saml_response(
|
7
|
-
encode_response(
|
50
|
+
def idp_make_saml_response(person)
|
51
|
+
encode_response(person[:email])
|
8
52
|
end
|
53
|
+
protected :idp_make_saml_response
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def add_view_path
|
58
|
+
prepend_view_path("app/views")
|
59
|
+
end
|
60
|
+
|
9
61
|
end
|
@@ -1,22 +1,18 @@
|
|
1
1
|
<% if @saml_idp_fail_msg %>
|
2
2
|
<div id="saml_idp_fail_msg" class="flash error"><%= @saml_idp_fail_msg %></div>
|
3
3
|
<% end %>
|
4
|
-
|
5
4
|
<%= form_tag do %>
|
6
5
|
<%= hidden_field_tag("SAMLRequest", params[:SAMLRequest]) %>
|
7
6
|
<%= hidden_field_tag("RelayState", params[:RelayState]) %>
|
8
|
-
|
9
7
|
<p>
|
10
8
|
<%= label_tag :email %>
|
11
9
|
<%= email_field_tag :email, params[:email], :autocapitalize => "off", :autocorrect => "off", :autofocus => "autofocus", :spellcheck => "false", :size => 30, :class => "email_pwd txt" %>
|
12
10
|
</p>
|
13
|
-
|
14
11
|
<p>
|
15
12
|
<%= label_tag :password %>
|
16
13
|
<%= password_field_tag :password, params[:password], :autocapitalize => "off", :autocorrect => "off", :spellcheck => "false", :size => 30, :class => "email_pwd txt" %>
|
17
14
|
</p>
|
18
|
-
|
19
15
|
<p>
|
20
16
|
<%= submit_tag "Sign in", :class => "button big blueish" %>
|
21
17
|
</p>
|
22
|
-
<% end %>
|
18
|
+
<% end %>
|
@@ -18,6 +18,7 @@ module RailsApp
|
|
18
18
|
|
19
19
|
# Custom directories with classes and modules you want to be autoloadable.
|
20
20
|
# config.autoload_paths += %W(#{config.root}/extras)
|
21
|
+
config.autoload_paths += %w[app/controllers]
|
21
22
|
|
22
23
|
# Only load the plugins named here, in the order given (default is alphabetical).
|
23
24
|
# :all can be used as a placeholder for all plugins not explicitly named.
|
@@ -29,4 +29,6 @@ RailsApp::Application.configure do
|
|
29
29
|
# Log the query plan for queries taking more than this (works
|
30
30
|
# with SQLite, MySQL, and PostgreSQL)
|
31
31
|
#config.active_record.auto_explain_threshold_in_seconds = 0.5
|
32
|
+
|
33
|
+
config.hosts << "foo.example.com" if config.respond_to?(:hosts)
|
32
34
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -67,4 +67,4 @@ end
|
|
67
67
|
SamlIdp::Default::SERVICE_PROVIDER[:metadata_url] = 'https://example.com/meta'
|
68
68
|
SamlIdp::Default::SERVICE_PROVIDER[:response_hosts] = ['foo.example.com']
|
69
69
|
SamlIdp::Default::SERVICE_PROVIDER[:assertion_consumer_logout_service_url] = 'https://foo.example.com/saml/logout'
|
70
|
-
Capybara.default_host = "https://
|
70
|
+
Capybara.default_host = "https://foo.example.com"
|