saml_idp 0.7.1 → 0.10.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 +40 -12
- data/lib/saml_idp/configurator.rb +1 -0
- data/lib/saml_idp/controller.rb +6 -2
- data/lib/saml_idp/encryptor.rb +1 -1
- data/lib/saml_idp/incoming_metadata.rb +9 -1
- data/lib/saml_idp/request.rb +14 -0
- 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 +14 -0
- 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 +26 -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 +7 -7
- data/spec/lib/saml_idp/controller_spec.rb +23 -20
- data/spec/lib/saml_idp/encryptor_spec.rb +4 -4
- data/spec/lib/saml_idp/incoming_metadata_spec.rb +46 -0
- data/spec/lib/saml_idp/metadata_builder_spec.rb +7 -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 +3 -0
- data/spec/support/saml_request_macros.rb +2 -1
- data/spec/xml_security_spec.rb +12 -12
- metadata +85 -40
- data/spec/lib/saml_idp/.assertion_builder_spec.rb.swp +0 -0
@@ -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 }
|
@@ -9,11 +9,11 @@ module SamlIdp
|
|
9
9
|
) }
|
10
10
|
|
11
11
|
before do
|
12
|
-
Time.
|
12
|
+
allow(Time).to receive(:now).and_return Time.parse("Jul 31 2013")
|
13
13
|
end
|
14
14
|
|
15
15
|
it "builds a legit raw XML file" do
|
16
|
-
subject.raw.
|
16
|
+
expect(subject.raw).to eq("<ds:Signature xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\"><ds:SignedInfo xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\"><ds:CanonicalizationMethod Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/><ds:SignatureMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#rsa-sha256\"/><ds:Reference URI=\"#_abc\"><ds:Transforms><ds:Transform Algorithm=\"http://www.w3.org/2000/09/xmldsig#enveloped-signature\"/><ds:Transform Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/></ds:Transforms><ds:DigestMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#sha256\"/><ds:DigestValue>em8csGAWynywpe8S4nN64o56/4DosXi2XWMY6RJ6YfA=</ds:DigestValue></ds:Reference></ds:SignedInfo><ds:SignatureValue>jvEbD/rsiPKmoXy7Lhm+FGn88NPGlap4EcPZ2fvjBnk03YESs87FXAIiZZEzN5xq4sBZksUmZe2bV3rrr9sxQNgQawmrrvr66ot7cJiv0ETFArr6kQIZaR5g/V0M4ydxvrfefp6cQVI0hXvmxi830pq0tISiO4J7tyBNX/kvhZk=</ds:SignatureValue><KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\"><ds:X509Data><ds:X509Certificate>MIIDqzCCAxSgAwIBAgIBATANBgkqhkiG9w0BAQsFADCBhjELMAkGA1UEBhMCQVUxDDAKBgNVBAgTA05TVzEPMA0GA1UEBxMGU3lkbmV5MQwwCgYDVQQKDANQSVQxCTAHBgNVBAsMADEYMBYGA1UEAwwPbGF3cmVuY2VwaXQuY29tMSUwIwYJKoZIhvcNAQkBDBZsYXdyZW5jZS5waXRAZ21haWwuY29tMB4XDTEyMDQyODAyMjIyOFoXDTMyMDQyMzAyMjIyOFowgYYxCzAJBgNVBAYTAkFVMQwwCgYDVQQIEwNOU1cxDzANBgNVBAcTBlN5ZG5leTEMMAoGA1UECgwDUElUMQkwBwYDVQQLDAAxGDAWBgNVBAMMD2xhd3JlbmNlcGl0LmNvbTElMCMGCSqGSIb3DQEJAQwWbGF3cmVuY2UucGl0QGdtYWlsLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAuBywPNlC1FopGLYfF96SotiK8Nj6/nW084O4omRMifzy7x955RLEy673q2aiJNB3LvE6Xvkt9cGtxtNoOXw1g2UvHKpldQbr6bOEjLNeDNW7j0ob+JrRvAUOK9CRgdyw5MC6lwqVQQ5C1DnaT/2fSBFjasBFTR24dEpfTy8HfKECAwEAAaOCASUwggEhMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgUgMB0GA1UdDgQWBBQNBGmmt3ytKpcJaBaYNbnyU2xkazATBgNVHSUEDDAKBggrBgEFBQcDATAdBglghkgBhvhCAQ0EEBYOVGVzdCBYNTA5IGNlcnQwgbMGA1UdIwSBqzCBqIAUDQRpprd8rSqXCWgWmDW58lNsZGuhgYykgYkwgYYxCzAJBgNVBAYTAkFVMQwwCgYDVQQIEwNOU1cxDzANBgNVBAcTBlN5ZG5leTEMMAoGA1UECgwDUElUMQkwBwYDVQQLDAAxGDAWBgNVBAMMD2xhd3JlbmNlcGl0LmNvbTElMCMGCSqGSIb3DQEJAQwWbGF3cmVuY2UucGl0QGdtYWlsLmNvbYIBATANBgkqhkiG9w0BAQsFAAOBgQAEcVUPBX7uZmzqZJfy+tUPOT5ImNQj8VE2lerhnFjnGPHmHIqhpzgnwHQujJfs/a309Wm5qwcCaC1eO5cWjcG0x3OjdllsgYDatl5GAumtBx8J3NhWRqNUgitCIkQlxHIwUfgQaCushYgDDL5YbIQa++egCgpIZ+T0Dj5oRew//A==</ds:X509Certificate></ds:X509Data></KeyInfo></ds:Signature>")
|
17
17
|
end
|
18
18
|
end
|
19
19
|
end
|
@@ -11,15 +11,15 @@ module SamlIdp
|
|
11
11
|
) }
|
12
12
|
|
13
13
|
before do
|
14
|
-
Time.
|
14
|
+
allow(Time).to receive(:now).and_return Time.parse("Jul 31 2013")
|
15
15
|
end
|
16
16
|
|
17
17
|
it "builds a legit raw XML file" do
|
18
|
-
subject.raw.
|
18
|
+
expect(subject.raw).to eq("<ds:SignedInfo xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\"><ds:CanonicalizationMethod Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"></ds:CanonicalizationMethod><ds:SignatureMethod Algorithm=\"http://www.w3.org/2001/04/xmldsig-more#rsa-sha256\"></ds:SignatureMethod><ds:Reference URI=\"#_abc\"><ds:Transforms><ds:Transform Algorithm=\"http://www.w3.org/2000/09/xmldsig#enveloped-signature\"></ds:Transform><ds:Transform Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"></ds:Transform></ds:Transforms><ds:DigestMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#sha256\"></ds:DigestMethod><ds:DigestValue>em8csGAWynywpe8S4nN64o56/4DosXi2XWMY6RJ6YfA=</ds:DigestValue></ds:Reference></ds:SignedInfo>")
|
19
19
|
end
|
20
20
|
|
21
21
|
it "builds a legit digest of the XML file" do
|
22
|
-
subject.signed.
|
22
|
+
expect(subject.signed).to eq("hKLeWLRgatHcV6N5Fc8aKveqNp6Y/J4m2WSYp0awGFtsCTa/2nab32wI3du+3kuuIy59EDKeUhHVxEfyhoHUo6xTZuO2N7XcTpSonuZ/CB3WjozC2Q/9elss3z1rOC3154v5pW4puirLPRoG+Pwi8SmptxNRHczr6NvmfYmmGfo=")
|
23
23
|
end
|
24
24
|
end
|
25
25
|
end
|
@@ -2,7 +2,11 @@ class SamlController < ApplicationController
|
|
2
2
|
|
3
3
|
def consume
|
4
4
|
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse])
|
5
|
-
|
5
|
+
if Gem::Requirement.new('< 4.1') =~ Gem::Version.new(Rails.version)
|
6
|
+
render :text => response.name_id
|
7
|
+
else
|
8
|
+
render :plain => response.name_id
|
9
|
+
end
|
6
10
|
end
|
7
11
|
|
8
12
|
end
|
@@ -50,11 +50,5 @@ module RailsApp
|
|
50
50
|
# in your app. As such, your models will need to explicitly whitelist or blacklist accessible
|
51
51
|
# parameters by using an attr_accessible or attr_protected declaration.
|
52
52
|
# config.active_record.whitelist_attributes = true
|
53
|
-
|
54
|
-
# Enable the asset pipeline
|
55
|
-
config.assets.enabled = true
|
56
|
-
|
57
|
-
# Version of your assets, change this if you want to expire all your assets
|
58
|
-
config.assets.version = '1.0'
|
59
53
|
end
|
60
54
|
end
|
@@ -4,6 +4,7 @@ RailsApp::Application.configure do
|
|
4
4
|
# In the development environment your application's code is reloaded on
|
5
5
|
# every request. This slows down response time but is perfect for development
|
6
6
|
# since you don't have to restart the web server when you make code changes.
|
7
|
+
config.eager_load = false if config.respond_to?(:eager_load)
|
7
8
|
config.cache_classes = false
|
8
9
|
|
9
10
|
# Log error messages when you accidentally call methods on nil.
|
@@ -28,10 +29,4 @@ RailsApp::Application.configure do
|
|
28
29
|
# Log the query plan for queries taking more than this (works
|
29
30
|
# with SQLite, MySQL, and PostgreSQL)
|
30
31
|
#config.active_record.auto_explain_threshold_in_seconds = 0.5
|
31
|
-
|
32
|
-
# Do not compress assets
|
33
|
-
config.assets.compress = false
|
34
|
-
|
35
|
-
# Expands the lines which load the assets
|
36
|
-
config.assets.debug = true
|
37
32
|
end
|
@@ -2,6 +2,7 @@ RailsApp::Application.configure do
|
|
2
2
|
# Settings specified here will take precedence over those in config/application.rb
|
3
3
|
|
4
4
|
# Code is not reloaded between requests
|
5
|
+
config.eager_load = true if config.respond_to?(:eager_load)
|
5
6
|
config.cache_classes = true
|
6
7
|
|
7
8
|
# Full error reports are disabled and caching is turned on
|
@@ -5,6 +5,7 @@ RailsApp::Application.configure do
|
|
5
5
|
# test suite. You never need to work with it otherwise. Remember that
|
6
6
|
# your test database is "scratch space" for the test suite and is wiped
|
7
7
|
# and recreated between test runs. Don't rely on the data there!
|
8
|
+
config.eager_load = true if config.respond_to?(:eager_load)
|
8
9
|
config.cache_classes = true
|
9
10
|
|
10
11
|
# Configure static asset server for tests with Cache-Control for performance
|
data/spec/spec_helper.rb
CHANGED
@@ -45,4 +45,7 @@ RSpec.configure do |config|
|
|
45
45
|
end
|
46
46
|
end
|
47
47
|
|
48
|
+
SamlIdp::Default::SERVICE_PROVIDER[:metadata_url] = 'https://example.com/meta'
|
49
|
+
SamlIdp::Default::SERVICE_PROVIDER[:response_hosts] = ['foo.example.com']
|
50
|
+
SamlIdp::Default::SERVICE_PROVIDER[:assertion_consumer_logout_service_url] = 'https://foo.example.com/saml/logout'
|
48
51
|
Capybara.default_host = "https://app.example.com"
|
@@ -15,7 +15,7 @@ module SamlRequestMacros
|
|
15
15
|
'some_name_id',
|
16
16
|
OpenSSL::Digest::SHA256
|
17
17
|
)
|
18
|
-
request_builder.
|
18
|
+
Base64.strict_encode64(request_builder.signed)
|
19
19
|
end
|
20
20
|
|
21
21
|
def saml_settings(saml_acs_url = "https://foo.example.com/saml/consume")
|
@@ -23,6 +23,7 @@ module SamlRequestMacros
|
|
23
23
|
settings.assertion_consumer_service_url = saml_acs_url
|
24
24
|
settings.issuer = "http://example.com/issuer"
|
25
25
|
settings.idp_sso_target_url = "http://idp.com/saml/idp"
|
26
|
+
settings.assertion_consumer_logout_service_url = 'https://foo.example.com/saml/logout'
|
26
27
|
settings.idp_cert_fingerprint = SamlIdp::Default::FINGERPRINT
|
27
28
|
settings.name_identifier_format = SamlIdp::Default::NAME_ID_FORMAT
|
28
29
|
settings
|
data/spec/xml_security_spec.rb
CHANGED
@@ -7,7 +7,7 @@ module SamlIdp
|
|
7
7
|
let(:base64cert) { document.elements["//ds:X509Certificate"].text }
|
8
8
|
|
9
9
|
it "it run validate without throwing NS related exceptions" do
|
10
|
-
document.validate_doc(base64cert, true).
|
10
|
+
expect(document.validate_doc(base64cert, true)).to be_falsey
|
11
11
|
end
|
12
12
|
|
13
13
|
it "it run validate with throwing NS related exceptions" do
|
@@ -57,22 +57,22 @@ module SamlIdp
|
|
57
57
|
describe "Algorithms" do
|
58
58
|
it "validate using SHA1" do
|
59
59
|
document = XMLSecurity::SignedDocument.new(fixture(:adfs_response_sha1, false))
|
60
|
-
document.validate("F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72").
|
60
|
+
expect(document.validate("F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72")).to be_truthy
|
61
61
|
end
|
62
62
|
|
63
63
|
it "validate using SHA256" do
|
64
64
|
document = XMLSecurity::SignedDocument.new(fixture(:adfs_response_sha256, false))
|
65
|
-
document.validate("28:74:9B:E8:1F:E8:10:9C:A8:7C:A9:C3:E3:C5:01:6C:92:1C:B4:BA").
|
65
|
+
expect(document.validate("28:74:9B:E8:1F:E8:10:9C:A8:7C:A9:C3:E3:C5:01:6C:92:1C:B4:BA")).to be_truthy
|
66
66
|
end
|
67
67
|
|
68
68
|
it "validate using SHA384" do
|
69
69
|
document = XMLSecurity::SignedDocument.new(fixture(:adfs_response_sha384, false))
|
70
|
-
document.validate("F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72").
|
70
|
+
expect(document.validate("F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72")).to be_truthy
|
71
71
|
end
|
72
72
|
|
73
73
|
it "validate using SHA512" do
|
74
74
|
document = XMLSecurity::SignedDocument.new(fixture(:adfs_response_sha512, false))
|
75
|
-
document.validate("F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72").
|
75
|
+
expect(document.validate("F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72")).to be_truthy
|
76
76
|
end
|
77
77
|
end
|
78
78
|
|
@@ -83,7 +83,7 @@ module SamlIdp
|
|
83
83
|
document = XMLSecurity::SignedDocument.new(response)
|
84
84
|
inclusive_namespaces = document.send(:extract_inclusive_namespaces)
|
85
85
|
|
86
|
-
inclusive_namespaces.
|
86
|
+
expect(inclusive_namespaces).to eq %w[xs]
|
87
87
|
end
|
88
88
|
|
89
89
|
it "support implicit namespace resolution for exclusive canonicalization" do
|
@@ -91,7 +91,7 @@ module SamlIdp
|
|
91
91
|
document = XMLSecurity::SignedDocument.new(response)
|
92
92
|
inclusive_namespaces = document.send(:extract_inclusive_namespaces)
|
93
93
|
|
94
|
-
inclusive_namespaces.
|
94
|
+
expect(inclusive_namespaces).to eq %w[#default saml ds xs xsi]
|
95
95
|
end
|
96
96
|
|
97
97
|
it "return an empty list when inclusive namespace element is missing" do
|
@@ -101,7 +101,7 @@ module SamlIdp
|
|
101
101
|
document = XMLSecurity::SignedDocument.new(response)
|
102
102
|
inclusive_namespaces = document.send(:extract_inclusive_namespaces)
|
103
103
|
|
104
|
-
inclusive_namespaces.
|
104
|
+
expect(inclusive_namespaces).to be_empty
|
105
105
|
end
|
106
106
|
end
|
107
107
|
|
@@ -116,20 +116,20 @@ module SamlIdp
|
|
116
116
|
|
117
117
|
it "be able to validate a good response" do
|
118
118
|
Timecop.freeze Time.parse('2012-11-28 17:55:00 UTC') do
|
119
|
-
response.
|
120
|
-
response.
|
119
|
+
allow(response).to receive(:validate_subject_confirmation).and_return(true)
|
120
|
+
expect(response).to be_is_valid
|
121
121
|
end
|
122
122
|
end
|
123
123
|
|
124
124
|
it "fail before response is valid" do
|
125
125
|
Timecop.freeze Time.parse('2012-11-20 17:55:00 UTC') do
|
126
|
-
response.
|
126
|
+
expect(response).to_not be_is_valid
|
127
127
|
end
|
128
128
|
end
|
129
129
|
|
130
130
|
it "fail after response expires" do
|
131
131
|
Timecop.freeze Time.parse('2012-11-30 17:55:00 UTC') do
|
132
|
-
response.
|
132
|
+
expect(response).to_not be_is_valid
|
133
133
|
end
|
134
134
|
end
|
135
135
|
end
|