saml_tools 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +15 -0
  2. data/LICENSE +24 -0
  3. data/README.rdoc +65 -0
  4. data/Rakefile +28 -0
  5. data/lib/saml_tool.rb +23 -0
  6. data/lib/saml_tool/certificate.rb +27 -0
  7. data/lib/saml_tool/decoder.rb +35 -0
  8. data/lib/saml_tool/encoder.rb +31 -0
  9. data/lib/saml_tool/erb_builder.rb +33 -0
  10. data/lib/saml_tool/reader.rb +40 -0
  11. data/lib/saml_tool/redirect.rb +45 -0
  12. data/lib/saml_tool/response_reader.rb +148 -0
  13. data/lib/saml_tool/rsa_key.rb +13 -0
  14. data/lib/saml_tool/saml.rb +30 -0
  15. data/lib/saml_tool/settings.rb +24 -0
  16. data/lib/saml_tool/validator.rb +40 -0
  17. data/lib/saml_tool/version.rb +8 -0
  18. data/lib/saml_tools.rb +1 -0
  19. data/lib/schema/localised-saml-schema-assertion-2.0.xsd +292 -0
  20. data/lib/schema/localised-saml-schema-protocol-2.0.xsd +309 -0
  21. data/lib/schema/localised-xenc-schema.xsd +151 -0
  22. data/lib/schema/xmldsig-core-schema.xsd +318 -0
  23. data/test/files/TEST_FILES.rdoc +22 -0
  24. data/test/files/cacert.pem +21 -0
  25. data/test/files/open_saml_response.xml +56 -0
  26. data/test/files/request.saml.erb +28 -0
  27. data/test/files/response.xml +94 -0
  28. data/test/files/response_template.xml +63 -0
  29. data/test/files/usercert.p12 +0 -0
  30. data/test/files/userkey.pem +18 -0
  31. data/test/files/valid_saml_request.xml +13 -0
  32. data/test/test_helper.rb +51 -0
  33. data/test/units/saml_tool/certificate_test.rb +30 -0
  34. data/test/units/saml_tool/decoder_test.rb +36 -0
  35. data/test/units/saml_tool/encoder_test.rb +38 -0
  36. data/test/units/saml_tool/erb_builder_test.rb +50 -0
  37. data/test/units/saml_tool/reader_test.rb +104 -0
  38. data/test/units/saml_tool/redirect_test.rb +70 -0
  39. data/test/units/saml_tool/response_reader_test.rb +144 -0
  40. data/test/units/saml_tool/rsa_key_test.rb +21 -0
  41. data/test/units/saml_tool/saml_test.rb +21 -0
  42. data/test/units/saml_tool/settings_test.rb +36 -0
  43. data/test/units/saml_tool/validator_test.rb +16 -0
  44. 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