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.
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