samlr 2.1.0 → 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of samlr might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/bin/samlr +20 -16
- data/lib/samlr/version.rb +3 -0
- metadata +12 -38
- data/.gitignore +0 -3
- data/.travis.yml +0 -5
- data/Gemfile +0 -8
- data/Rakefile +0 -12
- data/samlr.gemspec +0 -19
- data/test/fixtures/default_samlr_certificate.pem +0 -11
- data/test/fixtures/default_samlr_private_key.pem +0 -9
- data/test/fixtures/no_cert_response.xml +0 -2
- data/test/fixtures/sample_metadata.xml +0 -7
- data/test/fixtures/sample_response.xml +0 -2
- data/test/test_helper.rb +0 -55
- data/test/unit/test_assertion.rb +0 -71
- data/test/unit/test_condition.rb +0 -154
- data/test/unit/test_fingerprint.rb +0 -45
- data/test/unit/test_logout_request.rb +0 -73
- data/test/unit/test_reference.rb +0 -32
- data/test/unit/test_request.rb +0 -34
- data/test/unit/test_response.rb +0 -94
- data/test/unit/test_response_scenarios.rb +0 -126
- data/test/unit/test_signature.rb +0 -54
- data/test/unit/test_timestamp.rb +0 -58
- data/test/unit/test_tools.rb +0 -100
- data/test/unit/tools/test_certificate_builder.rb +0 -41
- data/test/unit/tools/test_logout_request_builder.rb +0 -26
- data/test/unit/tools/test_metadata_builder.rb +0 -26
- data/test/unit/tools/test_request_builder.rb +0 -35
- data/test/unit/tools/test_response_builder.rb +0 -19
@@ -1,45 +0,0 @@
|
|
1
|
-
require File.expand_path("test/test_helper")
|
2
|
-
|
3
|
-
describe Samlr::Fingerprint do
|
4
|
-
describe "#new" do
|
5
|
-
it "generates an invalid fingerprint for nil" do
|
6
|
-
refute Samlr::Fingerprint.new(nil).valid?
|
7
|
-
end
|
8
|
-
end
|
9
|
-
|
10
|
-
describe "::normalize!" do
|
11
|
-
it "converts input to fingerprint normal form" do
|
12
|
-
assert_equal "AF:44", Samlr::Fingerprint.normalize("aF44 :-+6t")
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
describe "#==" do
|
17
|
-
it "compares two fingerprints" do
|
18
|
-
assert (Samlr::Fingerprint.new("aa:33") == Samlr::Fingerprint.new("AA:33"))
|
19
|
-
assert (Samlr::Fingerprint.new("aa:33") != Samlr::Fingerprint.new("AA:34"))
|
20
|
-
assert (Samlr::Fingerprint.new("") != Samlr::Fingerprint.new(""))
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
describe "#compare!" do
|
25
|
-
it "raises when fingerprints do not equal" do
|
26
|
-
assert_raises(Samlr::FingerprintError) do
|
27
|
-
Samlr::Fingerprint.new("aa:34").compare!(Samlr::Fingerprint.new("bb:35"))
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
it "stores fingerprints on the exception" do
|
32
|
-
begin
|
33
|
-
Samlr::Fingerprint.new("aa:34").compare!(Samlr::Fingerprint.new("bb:35"))
|
34
|
-
flunk "Exception expected"
|
35
|
-
rescue Samlr::FingerprintError => e
|
36
|
-
assert_equal "Fingerprint mismatch", e.message
|
37
|
-
assert_equal "AA:34 vs. BB:35", e.details
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
it "doesn't raise when fingerprints are equal" do
|
42
|
-
assert Samlr::Fingerprint.new("aa:34").compare!(Samlr::Fingerprint.new("aa:34"))
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
@@ -1,73 +0,0 @@
|
|
1
|
-
require File.expand_path("test/test_helper")
|
2
|
-
|
3
|
-
describe Samlr::LogoutRequest do
|
4
|
-
let(:options) {
|
5
|
-
{
|
6
|
-
:issuer => "https://sp.example.com/saml2",
|
7
|
-
:name_id => "test@test.com"
|
8
|
-
}
|
9
|
-
}
|
10
|
-
|
11
|
-
before do
|
12
|
-
@request = Samlr::LogoutRequest.new(options)
|
13
|
-
end
|
14
|
-
|
15
|
-
describe "#body" do
|
16
|
-
it "should return the generated XML" do
|
17
|
-
document = Nokogiri::XML(@request.body) { |c| c.strict }
|
18
|
-
assert document.at("/samlp:LogoutRequest", Samlr::NS_MAP)
|
19
|
-
end
|
20
|
-
|
21
|
-
it "should delegate the building to the LogoutRequestBuilder" do
|
22
|
-
Samlr::Tools::LogoutRequestBuilder.stub(:build, "hello") do
|
23
|
-
assert_match "hello", @request.body
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
describe "#param" do
|
29
|
-
it "returns the encoded body" do
|
30
|
-
@request.stub(:body, "hello") do
|
31
|
-
assert_equal Samlr::Tools.encode("hello"), @request.param
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
describe "#url" do
|
37
|
-
it "returns a valid URL" do
|
38
|
-
@request.stub(:param, "hello") do
|
39
|
-
assert_equal("https://foo.com/?SAMLRequest=hello&foo=bar", @request.url("https://foo.com/", :foo => "bar"))
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
describe "with optional params" do
|
45
|
-
it "understands name_id_format" do
|
46
|
-
options.merge!(:name_id_format => "some format")
|
47
|
-
request = Samlr::LogoutRequest.new(options)
|
48
|
-
|
49
|
-
assert_match /<saml:NameID Format="some format">/, request.body
|
50
|
-
end
|
51
|
-
|
52
|
-
it "understands [:name_id_options][:format]" do
|
53
|
-
options.merge!(:name_id_options => {:format => "some format"})
|
54
|
-
request = Samlr::LogoutRequest.new(options)
|
55
|
-
|
56
|
-
assert_match /<saml:NameID Format="some format">/, request.body
|
57
|
-
end
|
58
|
-
|
59
|
-
it "understands NameQualifier" do
|
60
|
-
options.merge!(:name_id_options => {:name_qualifier => "Some name qualifier"})
|
61
|
-
request = Samlr::LogoutRequest.new(options)
|
62
|
-
|
63
|
-
assert_match /NameQualifier="Some name qualifier"/, request.body
|
64
|
-
end
|
65
|
-
|
66
|
-
it "understands SPNameQualifier" do
|
67
|
-
options.merge!(:name_id_options => {:spname_qualifier => "Some SPName qualifier"})
|
68
|
-
request = Samlr::LogoutRequest.new(options)
|
69
|
-
|
70
|
-
assert_match /SPNameQualifier="Some SPName qualifier"/, request.body
|
71
|
-
end
|
72
|
-
end
|
73
|
-
end
|
data/test/unit/test_reference.rb
DELETED
@@ -1,32 +0,0 @@
|
|
1
|
-
require File.expand_path("test/test_helper")
|
2
|
-
|
3
|
-
describe Samlr::Reference do
|
4
|
-
before do
|
5
|
-
@response = fixed_saml_response
|
6
|
-
@reference = @response.signature.send(:references).first
|
7
|
-
end
|
8
|
-
|
9
|
-
describe "#uri" do
|
10
|
-
it "should return the normalized URI" do
|
11
|
-
assert_equal "samlr123", @reference.uri
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
describe "#digest_method" do
|
16
|
-
it "should return the digest implementation" do
|
17
|
-
assert_equal OpenSSL::Digest::SHA1, @reference.digest_method
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
describe "#digest_value" do
|
22
|
-
it "should return the verbatim value" do
|
23
|
-
assert_equal "OSVXSTu8W+eGao6muxUHXcKQwZU=", @reference.digest_value
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
describe "namespaces" do
|
28
|
-
it "should return the inclusive namespaces" do
|
29
|
-
assert_equal ["#default", "samlp", "saml", "ds", "xs", "xsi"].sort, @reference.namespaces.sort
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
data/test/unit/test_request.rb
DELETED
@@ -1,34 +0,0 @@
|
|
1
|
-
require File.expand_path("test/test_helper")
|
2
|
-
|
3
|
-
describe Samlr::Request do
|
4
|
-
before { @request = Samlr::Request.new }
|
5
|
-
|
6
|
-
describe "#body" do
|
7
|
-
it "should return the generated XML" do
|
8
|
-
document = Nokogiri::XML(@request.body) { |c| c.strict }
|
9
|
-
assert document.at("/samlp:AuthnRequest", Samlr::NS_MAP)
|
10
|
-
end
|
11
|
-
|
12
|
-
it "should delegate the building to the RequestBuilder" do
|
13
|
-
Samlr::Tools::RequestBuilder.stub(:build, "hello") do
|
14
|
-
assert_match "hello", @request.body
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
describe "#param" do
|
20
|
-
it "returns the encoded body" do
|
21
|
-
@request.stub(:body, "hello") do
|
22
|
-
assert_equal Samlr::Tools.encode("hello"), @request.param
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
describe "#url" do
|
28
|
-
it "returns a valid URL" do
|
29
|
-
@request.stub(:param, "hello") do
|
30
|
-
assert_equal("https://foo.com/?SAMLRequest=hello&foo=bar", @request.url("https://foo.com/", :foo => "bar"))
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
data/test/unit/test_response.rb
DELETED
@@ -1,94 +0,0 @@
|
|
1
|
-
require File.expand_path("test/test_helper")
|
2
|
-
|
3
|
-
describe Samlr::Response do
|
4
|
-
|
5
|
-
subject { fixed_saml_response }
|
6
|
-
|
7
|
-
describe "#name_id" do
|
8
|
-
it "delegates to the assertion" do
|
9
|
-
subject.assertion.stub(:name_id, "george") do
|
10
|
-
assert_equal("george", subject.name_id)
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
describe "#attributes" do
|
16
|
-
it "delegates to the assertion" do
|
17
|
-
subject.assertion.stub(:attributes, { :name => "george" }) do
|
18
|
-
assert_equal({ :name => "george" }, subject.attributes)
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
describe "#location" do
|
24
|
-
it "should return proper assertion location" do
|
25
|
-
assert_equal "//saml:Assertion[@ID='samlr456']", subject.assertion.location
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
describe "XSW attack" do
|
30
|
-
it "should not validate if SAML response is hacked" do
|
31
|
-
document = saml_response_document(:certificate => TEST_CERTIFICATE)
|
32
|
-
|
33
|
-
modified_document = Nokogiri::XML(document)
|
34
|
-
|
35
|
-
original_assertion = modified_document.xpath("/samlp:Response/saml:Assertion", Samlr::NS_MAP).first
|
36
|
-
|
37
|
-
response_signature = modified_document.xpath("/samlp:Response/ds:Signature", Samlr::NS_MAP).first
|
38
|
-
|
39
|
-
extensions = Nokogiri::XML::Node.new "Extensions", modified_document
|
40
|
-
extensions << original_assertion.to_xml(:save_with => Nokogiri::XML::Node::SaveOptions::AS_XML)
|
41
|
-
response_signature.add_next_sibling(extensions)
|
42
|
-
response_signature.remove()
|
43
|
-
|
44
|
-
modified_document.xpath("/samlp:Response/samlp:Extensions/saml:Assertion/ds:Signature", Samlr::NS_MAP).remove
|
45
|
-
modified_document.xpath("/samlp:Response/saml:Assertion/saml:Subject/saml:NameID", Samlr::NS_MAP).first.content="evil@example.org"
|
46
|
-
modified_document.xpath("/samlp:Response/saml:Assertion", Samlr::NS_MAP).first["ID"] = "evil_id"
|
47
|
-
|
48
|
-
response = Samlr::Response.new(modified_document.to_xml(:save_with => Nokogiri::XML::Node::SaveOptions::AS_XML), {:certificate => TEST_CERTIFICATE.x509})
|
49
|
-
assert_raises(Samlr::FormatError) { response.verify! }
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
describe "::parse" do
|
54
|
-
before { @document = saml_response_document(:certificate => TEST_CERTIFICATE) }
|
55
|
-
|
56
|
-
describe "when given a raw XML response" do
|
57
|
-
it "constructs and XML document" do
|
58
|
-
assert_equal Nokogiri::XML::Document, Samlr::Response.parse(@document).class
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
describe "when given a Base64 encoded response" do
|
63
|
-
subject { Base64.encode64(@document) }
|
64
|
-
|
65
|
-
it "constructs and XML document" do
|
66
|
-
assert_equal Nokogiri::XML::Document, Samlr::Response.parse(subject).class
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
describe "when given an invalid string" do
|
71
|
-
it "fails" do
|
72
|
-
assert_raises(Samlr::FormatError) { Samlr::Response.parse("hello") }
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
describe "when given a malformed XML response" do
|
77
|
-
subject { saml_response_document(:certificate => TEST_CERTIFICATE).gsub("Assertion", "AyCaramba") }
|
78
|
-
after { Samlr.validation_mode = :reject }
|
79
|
-
|
80
|
-
describe "and Samlr.validation_mode == :log" do
|
81
|
-
before { Samlr.validation_mode = :log }
|
82
|
-
it "does not raise" do
|
83
|
-
assert Samlr::Response.parse(subject)
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
|
-
describe "and Samlr.validation_mode != :log" do
|
88
|
-
it "raises" do
|
89
|
-
assert_raises(Samlr::FormatError) { Samlr::Response.parse(subject) }
|
90
|
-
end
|
91
|
-
end
|
92
|
-
end
|
93
|
-
end
|
94
|
-
end
|
@@ -1,126 +0,0 @@
|
|
1
|
-
require File.expand_path("test/test_helper")
|
2
|
-
|
3
|
-
# The tests in here are integraton level tests. They pass various mutations of a response
|
4
|
-
# document to the stack and asserts behavior.
|
5
|
-
describe Samlr do
|
6
|
-
|
7
|
-
describe "a valid response" do
|
8
|
-
subject { saml_response(:certificate => TEST_CERTIFICATE) }
|
9
|
-
|
10
|
-
it "verifies" do
|
11
|
-
assert subject.verify!
|
12
|
-
assert_equal "someone@example.org", subject.name_id
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
describe "an invalid fingerprint" do
|
17
|
-
subject { saml_response(:certificate => TEST_CERTIFICATE, :fingerprint => "hello") }
|
18
|
-
it "fails" do
|
19
|
-
assert_raises(Samlr::FingerprintError) { subject.verify! }
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
describe "an unsatisfied before condition" do
|
24
|
-
subject { saml_response(:certificate => TEST_CERTIFICATE, :not_before => Samlr::Tools::Timestamp.stamp(Time.now + 60)) }
|
25
|
-
|
26
|
-
it "fails" do
|
27
|
-
assert_raises(Samlr::ConditionsError) { subject.verify! }
|
28
|
-
end
|
29
|
-
|
30
|
-
describe "when jitter is in effect" do
|
31
|
-
after { Samlr.jitter = nil }
|
32
|
-
|
33
|
-
it "passes" do
|
34
|
-
Samlr.jitter = 500
|
35
|
-
assert subject.verify!
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
describe "an unsatisfied after condition" do
|
41
|
-
subject { saml_response(:certificate => TEST_CERTIFICATE, :not_on_or_after => Samlr::Tools::Timestamp.stamp(Time.now - 60)) }
|
42
|
-
|
43
|
-
it "fails" do
|
44
|
-
assert_raises(Samlr::ConditionsError) { subject.verify! }
|
45
|
-
end
|
46
|
-
|
47
|
-
describe "when jitter is in effect" do
|
48
|
-
after { Samlr.jitter = nil }
|
49
|
-
|
50
|
-
it "passes" do
|
51
|
-
Samlr.jitter = 500
|
52
|
-
assert subject.verify!
|
53
|
-
end
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
describe "when there are no attributes" do
|
58
|
-
subject { saml_response(:certificate => TEST_CERTIFICATE, :attributes => {}) }
|
59
|
-
|
60
|
-
it "returns an empty hash" do
|
61
|
-
assert_equal({}, subject.attributes)
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
describe "when there are no signatures" do
|
66
|
-
subject { saml_response(:certificate => TEST_CERTIFICATE, :sign_assertion => false, :sign_response => false) }
|
67
|
-
|
68
|
-
it "fails" do
|
69
|
-
assert_raises(Samlr::SignatureError) { subject.verify! }
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
describe "when there is no keyinfo" do
|
74
|
-
subject { saml_response(:certificate => TEST_CERTIFICATE, :skip_response_keyinfo => true, :skip_assertion_keyinfo => true) }
|
75
|
-
|
76
|
-
it "fails" do
|
77
|
-
assert_raises(Samlr::SignatureError) { subject.verify! }
|
78
|
-
end
|
79
|
-
|
80
|
-
describe "when a matching external cert is provided" do
|
81
|
-
it "passes" do
|
82
|
-
subject.options[:certificate] = TEST_CERTIFICATE.x509
|
83
|
-
assert subject.verify!
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
|
-
describe "when a non-matching external cert is provided" do
|
88
|
-
it "fails" do
|
89
|
-
subject.options[:certificate] = Samlr::Tools::CertificateBuilder.new.x509
|
90
|
-
assert_raises(Samlr::FingerprintError) { subject.verify! }
|
91
|
-
end
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
describe "when there's no assertion" do
|
96
|
-
subject { saml_response(:certificate => TEST_CERTIFICATE, :sign_assertion => false, :skip_assertion => true) }
|
97
|
-
|
98
|
-
it "fails" do
|
99
|
-
assert_raises(Samlr::FormatError) { subject.verify! }
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
|
-
describe "duplicate element ids" do
|
104
|
-
subject { saml_response(:certificate => TEST_CERTIFICATE, :response_id => "abcdef", :assertion_id => "abcdef") }
|
105
|
-
|
106
|
-
it "fails" do
|
107
|
-
assert_raises(Samlr::FormatError) { subject.verify! }
|
108
|
-
end
|
109
|
-
end
|
110
|
-
|
111
|
-
describe "when only the response signature is missing a certificate" do
|
112
|
-
subject { saml_response(:certificate => TEST_CERTIFICATE, :skip_response_keyinfo => true) }
|
113
|
-
|
114
|
-
it "verifies" do
|
115
|
-
assert subject.verify!
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
describe "when only the assertion signature is missing a certificate" do
|
120
|
-
subject { saml_response(:certificate => TEST_CERTIFICATE, :skip_assertion_keyinfo => true) }
|
121
|
-
|
122
|
-
it "verifies" do
|
123
|
-
assert subject.verify!
|
124
|
-
end
|
125
|
-
end
|
126
|
-
end
|
data/test/unit/test_signature.rb
DELETED
@@ -1,54 +0,0 @@
|
|
1
|
-
require File.expand_path("test/test_helper")
|
2
|
-
require "openssl"
|
3
|
-
|
4
|
-
describe Samlr::Signature do
|
5
|
-
before do
|
6
|
-
@response = fixed_saml_response
|
7
|
-
@signature = @response.signature
|
8
|
-
end
|
9
|
-
|
10
|
-
describe "#signature_algorithm" do
|
11
|
-
it "should defer to Samlr::Tools::algorithm" do
|
12
|
-
Samlr::Tools.stub(:algorithm, "hello") do
|
13
|
-
assert_match "hello", @signature.send(:signature_method)
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
describe "#references" do
|
19
|
-
it "should extract the reference to the signed document" do
|
20
|
-
assert_equal @response.document.children.first, @response.document.at(".//*[@ID='#{@signature.send(:references).first.uri}']")
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
describe "#certificate!" do
|
25
|
-
it "should extract the certificate" do
|
26
|
-
assert_equal TEST_CERTIFICATE.to_certificate, @signature.send(:certificate!)
|
27
|
-
end
|
28
|
-
|
29
|
-
describe "when there is no X509 certificate" do
|
30
|
-
it "should raise a signature error" do
|
31
|
-
@signature.stub(:certificate_node, nil) do
|
32
|
-
assert_raises(Samlr::SignatureError) { @signature.send(:certificate!) }
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
describe "#verify_digests!" do
|
39
|
-
describe "when there are duplicate element ids" do
|
40
|
-
before do
|
41
|
-
@signature.document.at("/samlp:Response/saml:Assertion")["ID"] = @signature.document.root["ID"]
|
42
|
-
end
|
43
|
-
|
44
|
-
it "should raise" do
|
45
|
-
begin
|
46
|
-
@signature.send(:verify_digests!)
|
47
|
-
flunk("Excepted to raise due to duplicate elements")
|
48
|
-
rescue Samlr::SignatureError => e
|
49
|
-
assert_equal "Reference validation error: Invalid element references", e.message
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|
54
|
-
end
|