saml_tools 0.0.1
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 +15 -0
- data/LICENSE +24 -0
- data/README.rdoc +65 -0
- data/Rakefile +28 -0
- data/lib/saml_tool.rb +23 -0
- data/lib/saml_tool/certificate.rb +27 -0
- data/lib/saml_tool/decoder.rb +35 -0
- data/lib/saml_tool/encoder.rb +31 -0
- data/lib/saml_tool/erb_builder.rb +33 -0
- data/lib/saml_tool/reader.rb +40 -0
- data/lib/saml_tool/redirect.rb +45 -0
- data/lib/saml_tool/response_reader.rb +148 -0
- data/lib/saml_tool/rsa_key.rb +13 -0
- data/lib/saml_tool/saml.rb +30 -0
- data/lib/saml_tool/settings.rb +24 -0
- data/lib/saml_tool/validator.rb +40 -0
- data/lib/saml_tool/version.rb +8 -0
- data/lib/saml_tools.rb +1 -0
- data/lib/schema/localised-saml-schema-assertion-2.0.xsd +292 -0
- data/lib/schema/localised-saml-schema-protocol-2.0.xsd +309 -0
- data/lib/schema/localised-xenc-schema.xsd +151 -0
- data/lib/schema/xmldsig-core-schema.xsd +318 -0
- data/test/files/TEST_FILES.rdoc +22 -0
- data/test/files/cacert.pem +21 -0
- data/test/files/open_saml_response.xml +56 -0
- data/test/files/request.saml.erb +28 -0
- data/test/files/response.xml +94 -0
- data/test/files/response_template.xml +63 -0
- data/test/files/usercert.p12 +0 -0
- data/test/files/userkey.pem +18 -0
- data/test/files/valid_saml_request.xml +13 -0
- data/test/test_helper.rb +51 -0
- data/test/units/saml_tool/certificate_test.rb +30 -0
- data/test/units/saml_tool/decoder_test.rb +36 -0
- data/test/units/saml_tool/encoder_test.rb +38 -0
- data/test/units/saml_tool/erb_builder_test.rb +50 -0
- data/test/units/saml_tool/reader_test.rb +104 -0
- data/test/units/saml_tool/redirect_test.rb +70 -0
- data/test/units/saml_tool/response_reader_test.rb +144 -0
- data/test/units/saml_tool/rsa_key_test.rb +21 -0
- data/test/units/saml_tool/saml_test.rb +21 -0
- data/test/units/saml_tool/settings_test.rb +36 -0
- data/test/units/saml_tool/validator_test.rb +16 -0
- metadata +168 -0
@@ -0,0 +1,38 @@
|
|
1
|
+
require_relative '../../test_helper'
|
2
|
+
|
3
|
+
module SamlTool
|
4
|
+
class EncoderTest < Minitest::Test
|
5
|
+
|
6
|
+
def test_class_encode
|
7
|
+
encoded_saml = Encoder.encode(saml)
|
8
|
+
inflated = inflate Base64.decode64(encoded_saml)
|
9
|
+
assert_equal saml, inflated
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_encode
|
13
|
+
encoded_saml = Encoder.new(saml).encode
|
14
|
+
inflated = inflate Base64.decode64(encoded_saml)
|
15
|
+
assert_equal saml, inflated
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_base64
|
19
|
+
encoded_saml = Encoder.new(saml).base64
|
20
|
+
assert_equal Base64.encode64(saml), encoded_saml
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_zlib
|
24
|
+
encoded_saml = Encoder.new(saml).zlib
|
25
|
+
inflated = inflate encoded_saml
|
26
|
+
assert_equal saml, inflated
|
27
|
+
end
|
28
|
+
|
29
|
+
def inflate(encoded)
|
30
|
+
zstream = Zlib::Inflate.new(-Zlib::MAX_WBITS) # I have no idea why we're using minus Zlib::MAX_WBITS. Zlib documentation suggests just Zlib::MAX_WBITS should work, but it doesn't
|
31
|
+
inflated = zstream.inflate(encoded)
|
32
|
+
zstream.finish
|
33
|
+
zstream.close
|
34
|
+
return inflated
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require_relative '../../test_helper'
|
2
|
+
|
3
|
+
module SamlTool
|
4
|
+
class ErbBuilderTest < Minitest::Test
|
5
|
+
|
6
|
+
def test_build
|
7
|
+
saml = ErbBuilder.build(
|
8
|
+
template: '<foo><%= settings %></foo>',
|
9
|
+
settings: 'bar'
|
10
|
+
)
|
11
|
+
assert_equal '<foo>bar</foo>', saml
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_erb
|
15
|
+
saml = ErbBuilder.new(
|
16
|
+
template: '<foo><%= settings %></foo>',
|
17
|
+
settings: 'bar'
|
18
|
+
)
|
19
|
+
assert_equal '<foo>bar</foo>', saml.to_s
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_erb_can_create_valid_saml
|
23
|
+
saml = ErbBuilder.new(
|
24
|
+
template: request_saml_erb,
|
25
|
+
settings: settings
|
26
|
+
)
|
27
|
+
assert_equal true, Validator.new(saml.to_s).valid?
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
def settings
|
32
|
+
Hashie::Mash.new(
|
33
|
+
id: ('_' + SecureRandom.uuid),
|
34
|
+
issue_instance: Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ"),
|
35
|
+
assertion_consumer_service_url: 'http://localhost:3000/demo',
|
36
|
+
issuer: 'http://localhost:3000',
|
37
|
+
idp_sso_target_url: 'http://localhost:3000/saml/auth',
|
38
|
+
idp_cert_fingerprint: '9E:65:2E:03:06:8D:80:F2:86:C7:6C:77:A1:D9:14:97:0A:4D:F4:4D',
|
39
|
+
name_identifier_format: 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress',
|
40
|
+
# Optional for most SAML IdPs
|
41
|
+
authn_context: "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport"
|
42
|
+
|
43
|
+
)
|
44
|
+
end
|
45
|
+
|
46
|
+
def thing
|
47
|
+
'bar'
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require_relative '../../test_helper'
|
2
|
+
|
3
|
+
module SamlTool
|
4
|
+
class ReaderTest < Minitest::Test
|
5
|
+
|
6
|
+
def test_reader
|
7
|
+
config = Settings.new(
|
8
|
+
foo: xpath_to_return_foo_text
|
9
|
+
)
|
10
|
+
@reader = Reader.new(nested_saml, config)
|
11
|
+
assert_equal 'bar', @reader.foo
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_reader_with_string_hash_config
|
15
|
+
reader = Reader.new(
|
16
|
+
nested_saml,
|
17
|
+
'foo' => xpath_to_return_foo_text
|
18
|
+
)
|
19
|
+
assert_equal 'bar', reader.foo
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_reader_can_get_attribute
|
23
|
+
reader = Reader.new(
|
24
|
+
nested_saml,
|
25
|
+
'foo' => xpath_to_return_foo_attribute
|
26
|
+
)
|
27
|
+
assert_equal 'that', reader.foo
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_reader_with_name_space
|
31
|
+
reader = Reader.new(
|
32
|
+
response_xml,
|
33
|
+
{foo: '//ds:X509Certificate/text()'},
|
34
|
+
{ds: 'http://www.w3.org/2000/09/xmldsig#'}
|
35
|
+
)
|
36
|
+
assert_equal 'MIIC6D', reader.foo[0...6]
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_value_remembers_source
|
40
|
+
saml = SamlTool::SAML(nested_saml)
|
41
|
+
source = saml.xpath(xpath_to_return_foo_text)
|
42
|
+
test_reader
|
43
|
+
assert_equal source.class, @reader.foo.source.class
|
44
|
+
assert_equal source.to_s, @reader.foo.source.to_s
|
45
|
+
assert_equal @reader.foo, @reader.foo.source.to_s
|
46
|
+
end
|
47
|
+
|
48
|
+
# If nokogiri is passed a namespace of {} it assumes an explicit entry of no namespaces.
|
49
|
+
# Whereas it sees nil namespaces as meaning namesspaces should be ignored.
|
50
|
+
# So nil should be the default behaviour, and can be overridden with {} as required.
|
51
|
+
# This reflects the normal Nokogiri behaviour that is more likely to be the expected
|
52
|
+
# behaviour.
|
53
|
+
def test_default_namespaces
|
54
|
+
reader = Reader.new(nested_saml)
|
55
|
+
assert_equal nil, reader.namespaces
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_to_hash
|
59
|
+
reader = Reader.new(
|
60
|
+
nested_saml,
|
61
|
+
{
|
62
|
+
foo: xpath_to_return_foo_text,
|
63
|
+
this: xpath_to_return_foo_attribute
|
64
|
+
}
|
65
|
+
)
|
66
|
+
expected = {
|
67
|
+
foo: 'bar',
|
68
|
+
this: 'that'
|
69
|
+
}
|
70
|
+
assert_equal expected, reader.to_hash
|
71
|
+
end
|
72
|
+
|
73
|
+
class FooReader < Reader
|
74
|
+
def initialize(saml)
|
75
|
+
super(
|
76
|
+
saml,
|
77
|
+
{
|
78
|
+
foo: 'level_one/foo[1]/text()',
|
79
|
+
this: 'level_one/foo[1]/@this'
|
80
|
+
}
|
81
|
+
)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def test_reader_via_inherited_class
|
86
|
+
reader = FooReader.new(nested_saml)
|
87
|
+
assert_equal 'bar', reader.foo
|
88
|
+
assert_equal 'that', reader.this
|
89
|
+
end
|
90
|
+
|
91
|
+
def nested_saml
|
92
|
+
'<level_one><foo this="that">bar</foo></level_one>'
|
93
|
+
end
|
94
|
+
|
95
|
+
def xpath_to_return_foo_text
|
96
|
+
'level_one/foo[1]/text()'
|
97
|
+
end
|
98
|
+
|
99
|
+
def xpath_to_return_foo_attribute
|
100
|
+
'level_one/foo[1]/@this'
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require_relative '../../test_helper'
|
2
|
+
|
3
|
+
module SamlTool
|
4
|
+
class RedirectTest < Minitest::Test
|
5
|
+
|
6
|
+
def test_uri
|
7
|
+
redirect = Redirect.uri(
|
8
|
+
to: url,
|
9
|
+
data: {
|
10
|
+
foo: 'bar'
|
11
|
+
}
|
12
|
+
)
|
13
|
+
assert_equal "#{url}?foo=bar", redirect
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_to_s
|
17
|
+
redirect = Redirect.new(
|
18
|
+
to: url,
|
19
|
+
data: {
|
20
|
+
foo: 'bar'
|
21
|
+
}
|
22
|
+
)
|
23
|
+
assert_equal "#{url}?foo=bar", redirect.to_s
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_to_s_with_multiple_data
|
27
|
+
redirect = Redirect.new(
|
28
|
+
to: url,
|
29
|
+
data: {
|
30
|
+
foo: 'bar',
|
31
|
+
this: 'that'
|
32
|
+
}
|
33
|
+
)
|
34
|
+
assert_equal "#{url}?foo=bar&this=that", redirect.to_s
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_to_s_with_existing_parameters
|
38
|
+
redirect = Redirect.new(
|
39
|
+
to: url + '?foo=bar',
|
40
|
+
data: {
|
41
|
+
this: 'that'
|
42
|
+
}
|
43
|
+
)
|
44
|
+
assert_equal "#{url}?foo=bar&this=that", redirect.to_s
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_to_s_with_data_string
|
48
|
+
redirect = Redirect.new(
|
49
|
+
to: url,
|
50
|
+
data: 'foo=bar'
|
51
|
+
)
|
52
|
+
assert_equal "#{url}?foo=bar", redirect.to_s
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_to_s_escapes_data
|
56
|
+
redirect = Redirect.new(
|
57
|
+
to: url,
|
58
|
+
data: {
|
59
|
+
foo: '<bar>'
|
60
|
+
}
|
61
|
+
)
|
62
|
+
assert_equal "#{url}?foo=%3Cbar%3E", redirect.to_s
|
63
|
+
end
|
64
|
+
|
65
|
+
def url
|
66
|
+
'http://example.com/saml/auth'
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
require_relative '../../test_helper'
|
2
|
+
|
3
|
+
module SamlTool
|
4
|
+
class ResponseReaderTest < Minitest::Test
|
5
|
+
|
6
|
+
def test_saml
|
7
|
+
assert_kind_of SAML::Document, response_document.saml
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_signatureless
|
11
|
+
assert_kind_of SAML::Document, response_document.signatureless
|
12
|
+
expected = response_document.saml.clone
|
13
|
+
expected.xpath('//ds:Signature', { 'ds' => dsig }).remove
|
14
|
+
assert_equal expected.to_s, response_document.signatureless.to_s
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_signatureless_does_not_impact_saml
|
18
|
+
response_document.signatureless
|
19
|
+
assert response_document.saml.to_s != response_document.signatureless.to_s, 'Changes made in forming signatureless should not also happen to saml'
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_base64_cert
|
23
|
+
base64_cert = response_document_saml.xpath('//ds:X509Certificate/text()', { 'ds' => dsig })
|
24
|
+
assert_equal base64_cert.to_s, response_document.base64_cert
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_certificate
|
28
|
+
assert_kind_of OpenSSL::X509::Certificate, response_document.certificate
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_fingerprint
|
32
|
+
expected = Digest::SHA1.hexdigest(response_document.certificate.to_der)
|
33
|
+
assert_equal expected, response_document.fingerprint
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_canonicalization_method
|
37
|
+
expected = response_document_saml.xpath('//ds:CanonicalizationMethod/@Algorithm', { 'ds' => dsig })
|
38
|
+
assert_equal expected.to_s, response_document.canonicalization_method
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_canonicalization_algorithm
|
42
|
+
expected = Nokogiri::XML::XML_C14N_1_0
|
43
|
+
assert_equal expected, response_document.canonicalization_algorithm
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_reference_uri
|
47
|
+
expected = response_document_saml.xpath('//ds:Reference/@URI', { 'ds' => dsig })
|
48
|
+
assert_equal expected.to_s, response_document.reference_uri
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_inclusive_namespaces
|
52
|
+
assert_equal "", response_document.inclusive_namespaces
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_inclusive_namespaces_when_they_exist_in_saml
|
56
|
+
document = ResponseReader.new(open_saml_request)
|
57
|
+
assert_equal 'xs', document.inclusive_namespaces
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_hashed_element
|
61
|
+
remove_signature_from_assertion
|
62
|
+
assert_equal assertion.to_s, response_document.hashed_element.to_s
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_canonicalized_hashed_element
|
66
|
+
remove_signature_from_assertion
|
67
|
+
expected = assertion.canonicalize(Nokogiri::XML::XML_C14N_1_0, [])
|
68
|
+
assert_equal expected, response_document.canonicalized_hashed_element
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_digest_algorithm
|
72
|
+
assert_equal 'http://www.w3.org/2000/09/xmldsig#sha1', response_document.digest_algorithm
|
73
|
+
end
|
74
|
+
|
75
|
+
def test_digest_algorithm_class
|
76
|
+
assert_equal OpenSSL::Digest::SHA1, response_document.digest_algorithm_class
|
77
|
+
end
|
78
|
+
|
79
|
+
def test_digest_hash
|
80
|
+
expected = OpenSSL::Digest::SHA1.digest(response_document.canonicalized_hashed_element)
|
81
|
+
assert_equal expected, response_document.digest_hash
|
82
|
+
end
|
83
|
+
|
84
|
+
def test_digest_hash_matches_digest_value
|
85
|
+
assert_equal response_document.digest_hash, response_document.decoded_digest_value
|
86
|
+
end
|
87
|
+
|
88
|
+
def test_digests_match?
|
89
|
+
assert_equal true, response_document.digests_match?
|
90
|
+
end
|
91
|
+
|
92
|
+
def test_signature
|
93
|
+
signature_value = response_document_saml.xpath('//ds:SignatureValue', { 'ds' => dsig }).text
|
94
|
+
assert_equal Base64.decode64(signature_value), response_document.signature
|
95
|
+
end
|
96
|
+
|
97
|
+
def test_signature_algorithm_class
|
98
|
+
assert_equal OpenSSL::Digest::SHA1, response_document.signature_algorithm_class
|
99
|
+
end
|
100
|
+
|
101
|
+
def test_canonicalized_signed_info
|
102
|
+
expected = response_document.signed_info.source.first.canonicalize(Nokogiri::XML::XML_C14N_1_0, [])
|
103
|
+
assert_equal expected, response_document.canonicalized_signed_info
|
104
|
+
end
|
105
|
+
|
106
|
+
def test_signature_verified
|
107
|
+
assert_equal true, response_document.signature_verified?
|
108
|
+
end
|
109
|
+
|
110
|
+
def test_structurally_valid
|
111
|
+
assert Validator.new(response_xml).valid?, 'response.xml needs to be valid SAML'
|
112
|
+
assert_equal true, response_document.structurally_valid?
|
113
|
+
end
|
114
|
+
|
115
|
+
def test_valid
|
116
|
+
assert_equal true, response_document.valid?
|
117
|
+
end
|
118
|
+
|
119
|
+
def response_document
|
120
|
+
@response_document ||= ResponseReader.new(response_xml)
|
121
|
+
end
|
122
|
+
|
123
|
+
def assertion
|
124
|
+
@assertion ||= response_document_saml.at_xpath('//saml:Assertion')
|
125
|
+
end
|
126
|
+
|
127
|
+
def remove_signature_from_assertion
|
128
|
+
assertion.xpath('//ds:Signature', { 'ds' => dsig }).remove
|
129
|
+
end
|
130
|
+
|
131
|
+
def response_document_saml
|
132
|
+
@response_document_saml ||= SamlTool::SAML(response_xml)
|
133
|
+
end
|
134
|
+
|
135
|
+
def c14m
|
136
|
+
'http://www.w3.org/2001/10/xml-exc-c14n#'
|
137
|
+
end
|
138
|
+
|
139
|
+
def dsig
|
140
|
+
'http://www.w3.org/2000/09/xmldsig#'
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require_relative '../../test_helper'
|
2
|
+
|
3
|
+
module SamlTool
|
4
|
+
class RsaKeyTest < Minitest::Test
|
5
|
+
|
6
|
+
def test_modulous
|
7
|
+
expected = Base64.encode64(open_ssl_rsa_key.n.to_s(2))
|
8
|
+
assert_equal expected, rsa_key.modulus
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_exponent
|
12
|
+
expected = Base64.encode64(open_ssl_rsa_key.e.to_s(2))
|
13
|
+
assert_equal expected, rsa_key.exponent
|
14
|
+
end
|
15
|
+
|
16
|
+
def rsa_key
|
17
|
+
@rsa_key ||= RsaKey.new(open_ssl_rsa_key)
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module SamlTool
|
4
|
+
class SamlTest < Minitest::Test
|
5
|
+
|
6
|
+
def test_document
|
7
|
+
document = SamlTool::SAML(valid_xml)
|
8
|
+
assert_kind_of Nokogiri::XML::Document, document
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_parse
|
12
|
+
document = SamlTool::SAML.parse valid_xml
|
13
|
+
assert_kind_of Nokogiri::XML::Document, document
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_document_parse
|
17
|
+
document = SamlTool::SAML::Document.parse valid_xml
|
18
|
+
assert_kind_of Nokogiri::XML::Document, document
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|