saml_idp 0.7.2 → 0.11.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 +41 -13
- data/lib/saml_idp/configurator.rb +5 -1
- data/lib/saml_idp/controller.rb +9 -5
- 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 +22 -3
- data/lib/saml_idp/response_builder.rb +19 -5
- data/lib/saml_idp/saml_response.rb +15 -3
- 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/saml_idp.gemspec +25 -23
- 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 +8 -8
- data/spec/lib/saml_idp/attribute_decorator_spec.rb +8 -8
- data/spec/lib/saml_idp/configurator_spec.rb +8 -7
- data/spec/lib/saml_idp/controller_spec.rb +47 -20
- data/spec/lib/saml_idp/encryptor_spec.rb +4 -4
- 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 +22 -22
- data/spec/lib/saml_idp/response_builder_spec.rb +5 -3
- data/spec/lib/saml_idp/saml_response_spec.rb +31 -8
- 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 +5 -1
- data/spec/rails_app/config/application.rb +0 -6
- data/spec/rails_app/config/environments/development.rb +1 -6
- 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 +22 -0
- 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 +64 -4
- data/spec/support/security_helpers.rb +10 -0
- data/spec/xml_security_spec.rb +12 -12
- metadata +89 -52
- data/spec/lib/saml_idp/.assertion_builder_spec.rb.swp +0 -0
@@ -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,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
|
@@ -2,18 +2,39 @@ require 'spec_helper'
|
|
2
2
|
module SamlIdp
|
3
3
|
describe MetadataBuilder do
|
4
4
|
it "has a valid fresh" do
|
5
|
-
subject.fresh.
|
5
|
+
expect(subject.fresh).to_not be_empty
|
6
6
|
end
|
7
7
|
|
8
8
|
it "signs valid xml" do
|
9
|
-
Saml::XML::Document.parse(subject.signed).valid_signature?(Default::FINGERPRINT).
|
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.
|
15
|
-
|
16
|
-
)
|
14
|
+
subject.configurator.single_logout_service_redirect_location = 'https://example.com/saml/logout'
|
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"')
|
17
38
|
end
|
18
39
|
|
19
40
|
context "technical contact" do
|
@@ -32,31 +53,23 @@ module SamlIdp
|
|
32
53
|
subject.configurator.technical_contact.telephone = "1-800-555-5555"
|
33
54
|
subject.configurator.technical_contact.email_address = "acme@example.com"
|
34
55
|
|
35
|
-
subject.fresh.
|
36
|
-
'<ContactPerson contactType="technical"><Company>ACME Corporation</Company><GivenName>Road</GivenName><SurName>Runner</SurName><EmailAddress>mailto:acme@example.com</EmailAddress><TelephoneNumber>1-800-555-5555</TelephoneNumber></ContactPerson>'
|
37
|
-
)
|
56
|
+
expect(subject.fresh).to match('<ContactPerson contactType="technical"><Company>ACME Corporation</Company><GivenName>Road</GivenName><SurName>Runner</SurName><EmailAddress>mailto:acme@example.com</EmailAddress><TelephoneNumber>1-800-555-5555</TelephoneNumber></ContactPerson>')
|
38
57
|
end
|
39
58
|
|
40
59
|
it "no fields" do
|
41
|
-
subject.fresh.
|
42
|
-
'<ContactPerson contactType="technical"></ContactPerson>'
|
43
|
-
)
|
60
|
+
expect(subject.fresh).to match('<ContactPerson contactType="technical"></ContactPerson>')
|
44
61
|
end
|
45
62
|
|
46
63
|
it "just email" do
|
47
64
|
subject.configurator.technical_contact.email_address = "acme@example.com"
|
48
|
-
subject.fresh.
|
49
|
-
'<ContactPerson contactType="technical"><EmailAddress>mailto:acme@example.com</EmailAddress></ContactPerson>'
|
50
|
-
)
|
65
|
+
expect(subject.fresh).to match('<ContactPerson contactType="technical"><EmailAddress>mailto:acme@example.com</EmailAddress></ContactPerson>')
|
51
66
|
end
|
52
67
|
|
53
68
|
end
|
54
69
|
|
55
70
|
it "includes logout element as HTTP Redirect" do
|
56
71
|
subject.configurator.single_logout_service_redirect_location = 'https://example.com/saml/logout'
|
57
|
-
subject.fresh.
|
58
|
-
'<SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://example.com/saml/logout"/>'
|
59
|
-
)
|
72
|
+
expect(subject.fresh).to match('<SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://example.com/saml/logout"/>')
|
60
73
|
end
|
61
74
|
end
|
62
75
|
end
|
@@ -7,7 +7,7 @@ module SamlIdp
|
|
7
7
|
let(:list) { { email_address: ->() { "foo@example.com" } } }
|
8
8
|
|
9
9
|
it "has a valid all" do
|
10
|
-
subject.all.
|
10
|
+
expect(subject.all).to eq ["urn:oasis:names:tc:SAML:2.0:nameid-format:emailAddress"]
|
11
11
|
end
|
12
12
|
|
13
13
|
end
|
@@ -21,7 +21,7 @@ module SamlIdp
|
|
21
21
|
}
|
22
22
|
|
23
23
|
it "has a valid all" do
|
24
|
-
subject.all.
|
24
|
+
expect(subject.all).to eq [
|
25
25
|
"urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
|
26
26
|
"urn:oasis:names:tc:SAML:2.0:nameid-format:undefined",
|
27
27
|
]
|
@@ -32,7 +32,7 @@ module SamlIdp
|
|
32
32
|
let(:list) { [:email_address, :undefined] }
|
33
33
|
|
34
34
|
it "has a valid all" do
|
35
|
-
subject.all.
|
35
|
+
expect(subject.all).to eq [
|
36
36
|
"urn:oasis:names:tc:SAML:2.0:nameid-format:emailAddress",
|
37
37
|
"urn:oasis:names:tc:SAML:2.0:nameid-format:undefined",
|
38
38
|
]
|
@@ -9,12 +9,12 @@ module SamlIdp
|
|
9
9
|
subject { described_class.from_deflated_request deflated_request }
|
10
10
|
|
11
11
|
it "inflates" do
|
12
|
-
subject.request_id.
|
12
|
+
expect(subject.request_id).to eq("_af43d1a0-e111-0130-661a-3c0754403fdb")
|
13
13
|
end
|
14
14
|
|
15
15
|
it "handles invalid SAML" do
|
16
16
|
req = described_class.from_deflated_request "bang!"
|
17
|
-
req.valid
|
17
|
+
expect(req.valid?).to eq(false)
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
@@ -22,51 +22,51 @@ module SamlIdp
|
|
22
22
|
subject { described_class.new raw_authn_request }
|
23
23
|
|
24
24
|
it "has a valid request_id" do
|
25
|
-
subject.request_id.
|
25
|
+
expect(subject.request_id).to eq("_af43d1a0-e111-0130-661a-3c0754403fdb")
|
26
26
|
end
|
27
27
|
|
28
28
|
it "has a valid acs_url" do
|
29
|
-
subject.acs_url.
|
29
|
+
expect(subject.acs_url).to eq("http://localhost:3000/saml/consume")
|
30
30
|
end
|
31
31
|
|
32
32
|
it "has a valid service_provider" do
|
33
|
-
subject.service_provider.
|
33
|
+
expect(subject.service_provider).to be_a ServiceProvider
|
34
34
|
end
|
35
35
|
|
36
36
|
it "has a valid service_provider" do
|
37
|
-
subject.service_provider.
|
37
|
+
expect(subject.service_provider).to be_truthy
|
38
38
|
end
|
39
39
|
|
40
40
|
it "has a valid issuer" do
|
41
|
-
subject.issuer.
|
41
|
+
expect(subject.issuer).to eq("localhost:3000")
|
42
42
|
end
|
43
43
|
|
44
44
|
it "has a valid valid_signature" do
|
45
|
-
subject.valid_signature
|
45
|
+
expect(subject.valid_signature?).to be_truthy
|
46
46
|
end
|
47
47
|
|
48
48
|
it "should return acs_url for response_url" do
|
49
|
-
subject.response_url.
|
49
|
+
expect(subject.response_url).to eq(subject.acs_url)
|
50
50
|
end
|
51
51
|
|
52
52
|
it "is a authn request" do
|
53
|
-
subject.authn_request
|
53
|
+
expect(subject.authn_request?).to eq(true)
|
54
54
|
end
|
55
55
|
|
56
56
|
it "fetches internal request" do
|
57
|
-
subject.request['ID'].
|
57
|
+
expect(subject.request['ID']).to eq(subject.request_id)
|
58
58
|
end
|
59
59
|
|
60
60
|
it "has a valid authn context" do
|
61
|
-
subject.requested_authn_context.
|
61
|
+
expect(subject.requested_authn_context).to eq("urn:oasis:names:tc:SAML:2.0:ac:classes:Password")
|
62
62
|
end
|
63
63
|
|
64
64
|
it "does not permit empty issuer" do
|
65
65
|
raw_req = raw_authn_request.gsub('localhost:3000', '')
|
66
66
|
authn_request = described_class.new raw_req
|
67
|
-
authn_request.issuer.
|
68
|
-
authn_request.issuer.
|
69
|
-
authn_request.valid
|
67
|
+
expect(authn_request.issuer).to_not eq('')
|
68
|
+
expect(authn_request.issuer).to be_nil
|
69
|
+
expect(authn_request.valid?).to eq(false)
|
70
70
|
end
|
71
71
|
end
|
72
72
|
|
@@ -76,31 +76,31 @@ module SamlIdp
|
|
76
76
|
subject { described_class.new raw_logout_request }
|
77
77
|
|
78
78
|
it "has a valid request_id" do
|
79
|
-
subject.request_id.
|
79
|
+
expect(subject.request_id).to eq('_some_response_id')
|
80
80
|
end
|
81
81
|
|
82
82
|
it "should be flagged as a logout_request" do
|
83
|
-
subject.logout_request
|
83
|
+
expect(subject.logout_request?).to eq(true)
|
84
84
|
end
|
85
85
|
|
86
86
|
it "should have a valid name_id" do
|
87
|
-
subject.name_id.
|
87
|
+
expect(subject.name_id).to eq('some_name_id')
|
88
88
|
end
|
89
89
|
|
90
90
|
it "should have a session index" do
|
91
|
-
subject.session_index.
|
91
|
+
expect(subject.session_index).to eq('abc123index')
|
92
92
|
end
|
93
93
|
|
94
94
|
it "should have a valid issuer" do
|
95
|
-
subject.issuer.
|
95
|
+
expect(subject.issuer).to eq('http://example.com')
|
96
96
|
end
|
97
97
|
|
98
98
|
it "fetches internal request" do
|
99
|
-
subject.request['ID'].
|
99
|
+
expect(subject.request['ID']).to eq(subject.request_id)
|
100
100
|
end
|
101
101
|
|
102
102
|
it "should return logout_url for response_url" do
|
103
|
-
subject.response_url.
|
103
|
+
expect(subject.response_url).to eq(subject.logout_url)
|
104
104
|
end
|
105
105
|
end
|
106
106
|
end
|
@@ -6,12 +6,14 @@ module SamlIdp
|
|
6
6
|
let(:saml_acs_url) { "http://sportngin.com" }
|
7
7
|
let(:saml_request_id) { "134" }
|
8
8
|
let(:assertion_and_signature) { "<Assertion xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID=\"_abc\" IssueInstant=\"2013-07-31T05:00:00Z\" Version=\"2.0\"><Issuer>http://sportngin.com</Issuer><signature>stuff</signature><Subject><NameID Format=\"urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress\">jon.phenow@sportngin.com</NameID><SubjectConfirmation Method=\"urn:oasis:names:tc:SAML:2.0:cm:bearer\"><SubjectConfirmationData InResponseTo=\"123\" NotOnOrAfter=\"2013-07-31T05:03:00Z\" Recipient=\"http://saml.acs.url\"/></SubjectConfirmation></Subject><Conditions NotBefore=\"2013-07-31T04:59:55Z\" NotOnOrAfter=\"2013-07-31T06:00:00Z\"><AudienceRestriction><Audience>http://example.com</Audience></AudienceRestriction></Conditions><AttributeStatement><Attribute Name=\"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress\"><AttributeValue>jon.phenow@sportngin.com</AttributeValue></Attribute></AttributeStatement><AuthnStatment AuthnInstant=\"2013-07-31T05:00:00Z\" SessionIndex=\"_abc\"><AuthnContext><AuthnContextClassRef>urn:federation:authentication:windows</AuthnContextClassRef></AuthnContext></AuthnStatment></Assertion>" }
|
9
|
+
let(:algorithm) { :sha256 }
|
9
10
|
subject { described_class.new(
|
10
11
|
response_id,
|
11
12
|
issuer_uri,
|
12
13
|
saml_acs_url,
|
13
14
|
saml_request_id,
|
14
|
-
assertion_and_signature
|
15
|
+
assertion_and_signature,
|
16
|
+
algorithm
|
15
17
|
) }
|
16
18
|
|
17
19
|
before do
|
@@ -25,7 +27,7 @@ module SamlIdp
|
|
25
27
|
|
26
28
|
it "builds a legit raw XML file" do
|
27
29
|
Timecop.travel(Time.zone.local(2010, 6, 1, 13, 0, 0)) do
|
28
|
-
subject.raw.
|
30
|
+
expect(subject.raw).to eq("<samlp:Response ID=\"_abc\" Version=\"2.0\" IssueInstant=\"2010-06-01T13:00:00Z\" Destination=\"http://sportngin.com\" Consent=\"urn:oasis:names:tc:SAML:2.0:consent:unspecified\" InResponseTo=\"134\" xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\"><Issuer xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\">http://example.com</Issuer><samlp:Status><samlp:StatusCode Value=\"urn:oasis:names:tc:SAML:2.0:status:Success\"/></samlp:Status><Assertion xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID=\"_abc\" IssueInstant=\"2013-07-31T05:00:00Z\" Version=\"2.0\"><Issuer>http://sportngin.com</Issuer><signature>stuff</signature><Subject><NameID Format=\"urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress\">jon.phenow@sportngin.com</NameID><SubjectConfirmation Method=\"urn:oasis:names:tc:SAML:2.0:cm:bearer\"><SubjectConfirmationData InResponseTo=\"123\" NotOnOrAfter=\"2013-07-31T05:03:00Z\" Recipient=\"http://saml.acs.url\"/></SubjectConfirmation></Subject><Conditions NotBefore=\"2013-07-31T04:59:55Z\" NotOnOrAfter=\"2013-07-31T06:00:00Z\"><AudienceRestriction><Audience>http://example.com</Audience></AudienceRestriction></Conditions><AttributeStatement><Attribute Name=\"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress\"><AttributeValue>jon.phenow@sportngin.com</AttributeValue></Attribute></AttributeStatement><AuthnStatment AuthnInstant=\"2013-07-31T05:00:00Z\" SessionIndex=\"_abc\"><AuthnContext><AuthnContextClassRef>urn:federation:authentication:windows</AuthnContextClassRef></AuthnContext></AuthnStatment></Assertion></samlp:Response>")
|
29
31
|
end
|
30
32
|
end
|
31
33
|
|
@@ -34,7 +36,7 @@ module SamlIdp
|
|
34
36
|
|
35
37
|
it "builds a legit raw XML file without a request ID" do
|
36
38
|
Timecop.travel(Time.zone.local(2010, 6, 1, 13, 0, 0)) do
|
37
|
-
subject.raw.
|
39
|
+
expect(subject.raw).to eq("<samlp:Response ID=\"_abc\" Version=\"2.0\" IssueInstant=\"2010-06-01T13:00:00Z\" Destination=\"http://sportngin.com\" Consent=\"urn:oasis:names:tc:SAML:2.0:consent:unspecified\" xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\"><Issuer xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\">http://example.com</Issuer><samlp:Status><samlp:StatusCode Value=\"urn:oasis:names:tc:SAML:2.0:status:Success\"/></samlp:Status><Assertion xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID=\"_abc\" IssueInstant=\"2013-07-31T05:00:00Z\" Version=\"2.0\"><Issuer>http://sportngin.com</Issuer><signature>stuff</signature><Subject><NameID Format=\"urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress\">jon.phenow@sportngin.com</NameID><SubjectConfirmation Method=\"urn:oasis:names:tc:SAML:2.0:cm:bearer\"><SubjectConfirmationData InResponseTo=\"123\" NotOnOrAfter=\"2013-07-31T05:03:00Z\" Recipient=\"http://saml.acs.url\"/></SubjectConfirmation></Subject><Conditions NotBefore=\"2013-07-31T04:59:55Z\" NotOnOrAfter=\"2013-07-31T06:00:00Z\"><AudienceRestriction><Audience>http://example.com</Audience></AudienceRestriction></Conditions><AttributeStatement><Attribute Name=\"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress\"><AttributeValue>jon.phenow@sportngin.com</AttributeValue></Attribute></AttributeStatement><AuthnStatment AuthnInstant=\"2013-07-31T05:00:00Z\" SessionIndex=\"_abc\"><AuthnContext><AuthnContextClassRef>urn:federation:authentication:windows</AuthnContextClassRef></AuthnContext></AuthnStatment></Assertion></samlp:Response>")
|
38
40
|
end
|
39
41
|
end
|
40
42
|
end
|
@@ -24,6 +24,8 @@ module SamlIdp
|
|
24
24
|
key_transport: 'rsa-oaep-mgf1p',
|
25
25
|
}
|
26
26
|
end
|
27
|
+
let(:signed_response_opts) { true }
|
28
|
+
let(:unsigned_response_opts) { false }
|
27
29
|
let(:subject_encrypted) { described_class.new(reference_id,
|
28
30
|
response_id,
|
29
31
|
issuer_uri,
|
@@ -35,7 +37,8 @@ module SamlIdp
|
|
35
37
|
authn_context_classref,
|
36
38
|
expiry,
|
37
39
|
encryption_opts,
|
38
|
-
session_expiry
|
40
|
+
session_expiry,
|
41
|
+
unsigned_response_opts
|
39
42
|
)
|
40
43
|
}
|
41
44
|
|
@@ -50,7 +53,8 @@ module SamlIdp
|
|
50
53
|
authn_context_classref,
|
51
54
|
expiry,
|
52
55
|
nil,
|
53
|
-
session_expiry
|
56
|
+
session_expiry,
|
57
|
+
signed_response_opts
|
54
58
|
)
|
55
59
|
}
|
56
60
|
|
@@ -63,23 +67,42 @@ module SamlIdp
|
|
63
67
|
end
|
64
68
|
|
65
69
|
it "has a valid build" do
|
66
|
-
subject.build.
|
70
|
+
expect(subject.build).to be_present
|
67
71
|
end
|
68
72
|
|
69
73
|
it "builds encrypted" do
|
70
|
-
subject_encrypted.build.
|
74
|
+
expect(subject_encrypted.build).to_not match(audience_uri)
|
71
75
|
encoded_xml = subject_encrypted.build
|
72
76
|
resp_settings = saml_settings(saml_acs_url)
|
73
77
|
resp_settings.private_key = Default::SECRET_KEY
|
74
78
|
resp_settings.issuer = audience_uri
|
75
79
|
saml_resp = OneLogin::RubySaml::Response.new(encoded_xml, settings: resp_settings)
|
76
80
|
saml_resp.soft = false
|
77
|
-
saml_resp.is_valid
|
81
|
+
expect(saml_resp.is_valid?).to eq(true)
|
82
|
+
end
|
83
|
+
|
84
|
+
it "will build signed valid response" do
|
85
|
+
expect { subject.build }.not_to raise_error
|
86
|
+
signed_encoded_xml = subject.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(signed_encoded_xml, settings: resp_settings)
|
91
|
+
expect(
|
92
|
+
Nokogiri::XML(saml_resp.response).at_xpath(
|
93
|
+
"//p:Response//ds:Signature",
|
94
|
+
{
|
95
|
+
"p" => "urn:oasis:names:tc:SAML:2.0:protocol",
|
96
|
+
"ds" => "http://www.w3.org/2000/09/xmldsig#"
|
97
|
+
}
|
98
|
+
)).to be_present
|
99
|
+
expect(saml_resp.send(:validate_signature)).to eq(true)
|
100
|
+
expect(saml_resp.is_valid?).to eq(true)
|
78
101
|
end
|
79
102
|
|
80
103
|
it "sets session expiration" do
|
81
104
|
saml_resp = OneLogin::RubySaml::Response.new(subject.build)
|
82
|
-
saml_resp.session_expires_at.
|
105
|
+
expect(saml_resp.session_expires_at).to eq Time.local(1990, "jan", 2).iso8601
|
83
106
|
end
|
84
107
|
|
85
108
|
context "session expiration is set to 0" do
|
@@ -89,14 +112,14 @@ module SamlIdp
|
|
89
112
|
resp_settings = saml_settings(saml_acs_url)
|
90
113
|
resp_settings.issuer = audience_uri
|
91
114
|
saml_resp = OneLogin::RubySaml::Response.new(subject.build, settings: resp_settings)
|
92
|
-
saml_resp.is_valid
|
115
|
+
expect(saml_resp.is_valid?).to eq(true)
|
93
116
|
end
|
94
117
|
|
95
118
|
it "doesn't set a session expiration" do
|
96
119
|
resp_settings = saml_settings(saml_acs_url)
|
97
120
|
resp_settings.issuer = audience_uri
|
98
121
|
saml_resp = OneLogin::RubySaml::Response.new(subject.build, settings: resp_settings)
|
99
|
-
saml_resp.session_expires_at.
|
122
|
+
expect(saml_resp.session_expires_at).to be_nil
|
100
123
|
end
|
101
124
|
end
|
102
125
|
end
|
@@ -14,11 +14,11 @@ module SamlIdp
|
|
14
14
|
let(:metadata_url) { "http://localhost:3000/metadata" }
|
15
15
|
|
16
16
|
it "has a valid fingerprint" do
|
17
|
-
subject.fingerprint.
|
17
|
+
expect(subject.fingerprint).to eq(fingerprint)
|
18
18
|
end
|
19
19
|
|
20
20
|
it "has a valid metadata_url" do
|
21
|
-
subject.metadata_url.
|
21
|
+
expect(subject.metadata_url).to eq(metadata_url)
|
22
22
|
end
|
23
23
|
|
24
24
|
it { should be_valid }
|