ruby-saml 0.9.1 → 0.9.2

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.

Potentially problematic release.


This version of ruby-saml might be problematic. Click here for more details.

@@ -1,3 +1,7 @@
1
+ require "xml_security"
2
+ require "onelogin/ruby-saml/attribute_service"
3
+ require "onelogin/ruby-saml/utils"
4
+
1
5
  module OneLogin
2
6
  module RubySaml
3
7
  class Settings
@@ -5,7 +9,10 @@ module OneLogin
5
9
  config = DEFAULTS.merge(overrides)
6
10
  config.each do |k,v|
7
11
  acc = "#{k.to_s}=".to_sym
8
- self.send(acc, v) if self.respond_to? acc
12
+ if self.respond_to? acc
13
+ value = v.is_a?(Hash) ? v.dup : v
14
+ self.send(acc, value)
15
+ end
9
16
  end
10
17
  @attribute_consuming_service = AttributeService.new
11
18
  end
@@ -16,6 +23,7 @@ module OneLogin
16
23
  attr_accessor :idp_slo_target_url
17
24
  attr_accessor :idp_cert
18
25
  attr_accessor :idp_cert_fingerprint
26
+ attr_accessor :idp_cert_fingerprint_algorithm
19
27
  # SP Data
20
28
  attr_accessor :issuer
21
29
  attr_accessor :assertion_consumer_service_url
@@ -97,20 +105,22 @@ module OneLogin
97
105
  private
98
106
 
99
107
  DEFAULTS = {
100
- :assertion_consumer_service_binding => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
101
- :single_logout_service_binding => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect",
108
+ :assertion_consumer_service_binding => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST".freeze,
109
+ :single_logout_service_binding => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect".freeze,
110
+ :idp_cert_fingerprint_algorithm => XMLSecurity::Document::SHA1,
102
111
  :compress_request => true,
103
112
  :compress_response => true,
104
113
  :security => {
105
114
  :authn_requests_signed => false,
106
115
  :logout_requests_signed => false,
107
- :logout_responses_signed => false,
116
+ :logout_responses_signed => false,
117
+ :metadata_signed => false,
108
118
  :embed_sign => false,
109
119
  :digest_method => XMLSecurity::Document::SHA1,
110
- :signature_method => XMLSecurity::Document::SHA1
111
- },
120
+ :signature_method => XMLSecurity::Document::RSA_SHA1
121
+ }.freeze,
112
122
  :double_quote_xml_attribute_values => false,
113
- }
123
+ }.freeze
114
124
  end
115
125
  end
116
126
  end
@@ -2,6 +2,8 @@ require 'zlib'
2
2
  require 'time'
3
3
  require 'nokogiri'
4
4
 
5
+ require "onelogin/ruby-saml/saml_message"
6
+
5
7
  # Only supports SAML 2.0
6
8
  module OneLogin
7
9
  module RubySaml
@@ -1,6 +1,7 @@
1
1
  require "uuid"
2
2
 
3
3
  require "onelogin/ruby-saml/logging"
4
+ require "onelogin/ruby-saml/saml_message"
4
5
 
5
6
  module OneLogin
6
7
  module RubySaml
@@ -25,7 +26,10 @@ module OneLogin
25
26
  end
26
27
 
27
28
  def create_params(settings, request_id = nil, logout_message = nil, params = {})
28
- params = {} if params.nil?
29
+ # The method expects :RelayState but sometimes we get 'RelayState' instead.
30
+ # Based on the HashWithIndifferentAccess value in Rails we could experience
31
+ # conflicts so this line will solve them.
32
+ relay_state = params[:RelayState] || params['RelayState']
29
33
 
30
34
  response_doc = create_logout_response_xml_doc(settings, request_id, logout_message)
31
35
  response_doc.context[:attribute_quote] = :quote if settings.double_quote_xml_attribute_values
@@ -40,10 +44,10 @@ module OneLogin
40
44
  response_params = {"SAMLResponse" => base64_response}
41
45
 
42
46
  if settings.security[:logout_responses_signed] && !settings.security[:embed_sign] && settings.private_key
43
- params['SigAlg'] = XMLSecurity::Document::SHA1
47
+ params['SigAlg'] = settings.security[:signature_method]
44
48
  url_string = "SAMLResponse=#{CGI.escape(base64_response)}"
45
- url_string += "&RelayState=#{CGI.escape(params['RelayState'])}" if params['RelayState']
46
- url_string += "&SigAlg=#{CGI.escape(params['SigAlg'])}"
49
+ url_string << "&RelayState=#{CGI.escape(relay_state)}" if relay_state
50
+ url_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}"
47
51
  private_key = settings.get_sp_key()
48
52
  signature = private_key.sign(XMLSecurity::BaseDocument.new.algorithm(settings.security[:signature_method]).new, url_string)
49
53
  params['Signature'] = encode(signature)
@@ -86,7 +90,7 @@ module OneLogin
86
90
  issuer.text = settings.issuer
87
91
  end
88
92
 
89
- # embebed sign
93
+ # embed signature
90
94
  if settings.security[:logout_responses_signed] && settings.private_key && settings.certificate && settings.security[:embed_sign]
91
95
  private_key = settings.get_sp_key()
92
96
  cert = settings.get_sp_cert()
@@ -1,5 +1,5 @@
1
1
  module OneLogin
2
2
  module RubySaml
3
- VERSION = '0.9.1'
3
+ VERSION = '0.9.2'
4
4
  end
5
5
  end
@@ -1,16 +1 @@
1
- require 'onelogin/ruby-saml/logging'
2
- require 'onelogin/ruby-saml/saml_message'
3
- require 'onelogin/ruby-saml/authrequest'
4
- require 'onelogin/ruby-saml/logoutrequest'
5
- require 'onelogin/ruby-saml/logoutresponse'
6
- require 'onelogin/ruby-saml/attributes'
7
- require 'onelogin/ruby-saml/slo_logoutrequest'
8
- require 'onelogin/ruby-saml/slo_logoutresponse'
9
- require 'onelogin/ruby-saml/response'
10
- require 'onelogin/ruby-saml/settings'
11
- require 'onelogin/ruby-saml/attribute_service'
12
- require 'onelogin/ruby-saml/validation_error'
13
- require 'onelogin/ruby-saml/metadata'
14
- require 'onelogin/ruby-saml/idp_metadata_parser'
15
- require 'onelogin/ruby-saml/utils'
16
- require 'onelogin/ruby-saml/version'
1
+ require 'onelogin/ruby-saml'
@@ -56,9 +56,10 @@ module XMLSecurity
56
56
  algorithm = element
57
57
  if algorithm.is_a?(REXML::Element)
58
58
  algorithm = element.attribute("Algorithm").value
59
- algorithm = algorithm && algorithm =~ /sha(.*?)$/i && $1.to_i
60
59
  end
61
60
 
61
+ algorithm = algorithm && algorithm =~ /(rsa-)?sha(.*?)$/i && $2.to_i
62
+
62
63
  case algorithm
63
64
  when 256 then OpenSSL::Digest::SHA256
64
65
  when 384 then OpenSSL::Digest::SHA384
@@ -71,15 +72,25 @@ module XMLSecurity
71
72
  end
72
73
 
73
74
  class Document < BaseDocument
74
- SHA1 = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"
75
- SHA256 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
76
- SHA384 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384"
77
- SHA512 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512"
75
+ RSA_SHA1 = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"
76
+ RSA_SHA256 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
77
+ RSA_SHA384 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384"
78
+ RSA_SHA512 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512"
79
+ SHA1 = "http://www.w3.org/2000/09/xmldsig#sha1"
80
+ SHA256 = "http://www.w3.org/2001/04/xmldsig-more#sha256"
81
+ SHA384 = "http://www.w3.org/2001/04/xmldsig-more#sha384"
82
+ SHA512 = "http://www.w3.org/2001/04/xmldsig-more#sha512"
78
83
  ENVELOPED_SIG = "http://www.w3.org/2000/09/xmldsig#enveloped-signature"
79
- INC_PREFIX_LIST = "#default samlp saml ds xs xsi"
84
+ INC_PREFIX_LIST = "#default samlp saml ds xs xsi md"
80
85
 
81
86
  attr_accessor :uuid
82
87
 
88
+ def uuid
89
+ @uuid ||= begin
90
+ document.root.nil? ? nil : document.root.attributes['ID']
91
+ end
92
+ end
93
+
83
94
  #<Signature>
84
95
  #<SignedInfo>
85
96
  #<CanonicalizationMethod />
@@ -95,9 +106,8 @@ module XMLSecurity
95
106
  #<KeyInfo />
96
107
  #<Object />
97
108
  #</Signature>
98
- def sign_document(private_key, certificate, signature_method = SHA1, digest_method = SHA1)
109
+ def sign_document(private_key, certificate, signature_method = RSA_SHA1, digest_method = SHA1)
99
110
  noko = Nokogiri.parse(self.to_s)
100
- canon_doc = noko.canonicalize(canon_algorithm(C14N))
101
111
 
102
112
  signature_element = REXML::Element.new("ds:Signature").add_namespace('ds', DSIG)
103
113
  signed_info_element = signature_element.add_element("ds:SignedInfo")
@@ -110,16 +120,19 @@ module XMLSecurity
110
120
  # Add Transforms
111
121
  transforms_element = reference_element.add_element("ds:Transforms")
112
122
  transforms_element.add_element("ds:Transform", {"Algorithm" => ENVELOPED_SIG})
113
- transforms_element.add_element("ds:Transform", {"Algorithm" => C14N})
114
- transforms_element.add_element("ds:InclusiveNamespaces", {"xmlns" => C14N, "PrefixList" => INC_PREFIX_LIST})
123
+ c14element = transforms_element.add_element("ds:Transform", {"Algorithm" => C14N})
124
+ c14element.add_element("ec:InclusiveNamespaces", {"xmlns:ec" => C14N, "PrefixList" => INC_PREFIX_LIST})
115
125
 
116
126
  digest_method_element = reference_element.add_element("ds:DigestMethod", {"Algorithm" => digest_method})
127
+ inclusive_namespaces = INC_PREFIX_LIST.split(" ")
128
+ canon_doc = noko.canonicalize(canon_algorithm(C14N), inclusive_namespaces)
117
129
  reference_element.add_element("ds:DigestValue").text = compute_digest(canon_doc, algorithm(digest_method_element))
118
130
 
119
131
  # add SignatureValue
120
132
  noko_sig_element = Nokogiri.parse(signature_element.to_s)
121
133
  noko_signed_info_element = noko_sig_element.at_xpath('//ds:Signature/ds:SignedInfo', 'ds' => DSIG)
122
134
  canon_string = noko_signed_info_element.canonicalize(canon_algorithm(C14N))
135
+
123
136
  signature = compute_signature(private_key, algorithm(signature_method).new, canon_string)
124
137
  signature_element.add_element("ds:SignatureValue").text = signature
125
138
 
@@ -137,7 +150,11 @@ module XMLSecurity
137
150
  if issuer_element
138
151
  self.root.insert_after issuer_element, signature_element
139
152
  else
140
- self.root.add_element(signature_element)
153
+ if sp_sso_descriptor = self.elements["/md:EntityDescriptor"]
154
+ self.root.insert_before sp_sso_descriptor, signature_element
155
+ else
156
+ self.root.add_element(signature_element)
157
+ end
141
158
  end
142
159
  end
143
160
 
@@ -165,7 +182,7 @@ module XMLSecurity
165
182
  extract_signed_element_id
166
183
  end
167
184
 
168
- def validate_document(idp_cert_fingerprint, soft = true)
185
+ def validate_document(idp_cert_fingerprint, soft = true, options = {})
169
186
  # get cert from response
170
187
  cert_element = REXML::XPath.first(self, "//ds:X509Certificate", { "ds"=>DSIG })
171
188
  unless cert_element
@@ -175,13 +192,18 @@ module XMLSecurity
175
192
  raise OneLogin::RubySaml::ValidationError.new("Certificate element missing in response (ds:X509Certificate)")
176
193
  end
177
194
  end
178
- base64_cert = cert_element.text
179
- cert_text = Base64.decode64(base64_cert)
180
- cert = OpenSSL::X509::Certificate.new(cert_text)
195
+ base64_cert = cert_element.text
196
+ cert_text = Base64.decode64(base64_cert)
197
+ cert = OpenSSL::X509::Certificate.new(cert_text)
181
198
 
182
- # check cert matches registered idp cert
183
- fingerprint = Digest::SHA1.hexdigest(cert.to_der)
199
+ if options[:fingerprint_alg]
200
+ fingerprint_alg = XMLSecurity::BaseDocument.new.algorithm(options[:fingerprint_alg]).new
201
+ else
202
+ fingerprint_alg = OpenSSL::Digest::SHA1.new
203
+ end
204
+ fingerprint = fingerprint_alg.hexdigest(cert.to_der)
184
205
 
206
+ # check cert matches registered idp cert
185
207
  if fingerprint != idp_cert_fingerprint.gsub(/[^a-zA-Z0-9]/,"").downcase
186
208
  @errors << "Fingerprint mismatch"
187
209
  return soft ? false : (raise OneLogin::RubySaml::ValidationError.new("Fingerprint mismatch"))
@@ -26,12 +26,11 @@ Gem::Specification.new do |s|
26
26
  s.test_files = `git ls-files test/*`.split("\n")
27
27
 
28
28
  s.add_runtime_dependency('uuid', '~> 2.3')
29
- if RUBY_VERSION < '1.9'
30
- # 1.8.7
31
- s.add_runtime_dependency('nokogiri', '~> 1.5.10')
32
- else
33
- s.add_runtime_dependency('nokogiri', '~> 1.6.0')
34
- end
29
+
30
+ # Because runtime dependencies are determined at build time, we cannot make
31
+ # Nokogiri's version dependent on the Ruby version, even though we would
32
+ # have liked to constrain Ruby 1.8.7 to install only the 1.5.x versions.
33
+ s.add_runtime_dependency('nokogiri', '>= 1.5.10')
35
34
 
36
35
  s.add_development_dependency('minitest', '~> 5.5')
37
36
  s.add_development_dependency('mocha', '~> 0.14')
@@ -1,6 +1,6 @@
1
1
  require File.expand_path(File.join(File.dirname(__FILE__), "test_helper"))
2
- require 'net/http'
3
- require 'net/https'
2
+
3
+ require 'onelogin/ruby-saml/idp_metadata_parser'
4
4
 
5
5
  class IdpMetadataParserTest < Minitest::Test
6
6
 
@@ -14,9 +14,11 @@ class IdpMetadataParserTest < Minitest::Test
14
14
 
15
15
  settings = idp_metadata_parser.parse(idp_metadata)
16
16
 
17
+ assert_equal "https://example.hello.com/access/saml/idp.xml", settings.idp_entity_id
17
18
  assert_equal "https://example.hello.com/access/saml/login", settings.idp_sso_target_url
18
19
  assert_equal "F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72", settings.idp_cert_fingerprint
19
20
  assert_equal "https://example.hello.com/access/saml/logout", settings.idp_slo_target_url
21
+ assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", settings.name_identifier_format
20
22
  end
21
23
  end
22
24
 
@@ -37,9 +39,11 @@ class IdpMetadataParserTest < Minitest::Test
37
39
  idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new
38
40
  settings = idp_metadata_parser.parse_remote(@url)
39
41
 
42
+ assert_equal "https://example.hello.com/access/saml/idp.xml", settings.idp_entity_id
40
43
  assert_equal "https://example.hello.com/access/saml/login", settings.idp_sso_target_url
41
44
  assert_equal "F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72", settings.idp_cert_fingerprint
42
45
  assert_equal "https://example.hello.com/access/saml/logout", settings.idp_slo_target_url
46
+ assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", settings.name_identifier_format
43
47
  assert_equal OpenSSL::SSL::VERIFY_PEER, @http.verify_mode
44
48
  end
45
49
 
@@ -1,5 +1,7 @@
1
1
  require File.expand_path(File.join(File.dirname(__FILE__), "test_helper"))
2
2
 
3
+ require 'onelogin/ruby-saml/logoutrequest'
4
+
3
5
  class RequestTest < Minitest::Test
4
6
 
5
7
  describe "Logoutrequest" do
@@ -88,7 +90,7 @@ class RequestTest < Minitest::Test
88
90
  end
89
91
  end
90
92
 
91
- describe "when the settings indicate to sign (embebed) the logout request" do
93
+ describe "when the settings indicate to sign (embedded) logout request" do
92
94
  it "created a signed logout request" do
93
95
  settings = OneLogin::RubySaml::Settings.new
94
96
  settings.idp_slo_target_url = "http://example.com?field=value"
@@ -104,6 +106,8 @@ class RequestTest < Minitest::Test
104
106
 
105
107
  inflated = decode_saml_request_payload(unauth_url)
106
108
  assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], inflated
109
+ assert_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1'/>], inflated
110
+ assert_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2000/09/xmldsig#sha1'/>], inflated
107
111
  end
108
112
 
109
113
  it "create a signed logout request with 256 digest and signature methods" do
@@ -114,7 +118,7 @@ class RequestTest < Minitest::Test
114
118
  # sign the logout request
115
119
  settings.security[:logout_requests_signed] = true
116
120
  settings.security[:embed_sign] = true
117
- settings.security[:signature_method] = XMLSecurity::Document::SHA256
121
+ settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA256
118
122
  settings.security[:digest_method] = XMLSecurity::Document::SHA512
119
123
  settings.certificate = ruby_saml_cert_text
120
124
  settings.private_key = ruby_saml_key_text
@@ -123,34 +127,58 @@ class RequestTest < Minitest::Test
123
127
  request_xml = Base64.decode64(params["SAMLRequest"])
124
128
 
125
129
  assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], request_xml
126
- request_xml =~ /<ds:SignatureMethod Algorithm='http:\/\/www.w3.org\/2001\/04\/xmldsig-more#rsa-sha256'\/>/
127
- request_xml =~ /<ds:DigestMethod Algorithm='http:\/\/www.w3.org\/2001\/04\/xmldsig-more#rsa-sha512'\/>/
130
+ assert_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'/>], request_xml
131
+ assert_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2001/04/xmldsig-more#sha512'/>], request_xml
128
132
  end
129
133
  end
130
134
 
131
- describe "when the settings indicate to sign the logout request" do
132
- it "create a signature parameter" do
133
- settings = OneLogin::RubySaml::Settings.new
134
- settings.compress_request = false
135
- settings.idp_slo_target_url = "http://example.com?field=value"
136
- settings.name_identifier_value = "f00f00"
137
- settings.security[:logout_requests_signed] = true
138
- settings.security[:embed_sign] = false
139
- settings.security[:signature_method] = XMLSecurity::Document::SHA1
140
- settings.certificate = ruby_saml_cert_text
141
- settings.private_key = ruby_saml_key_text
135
+ describe "#create_params when the settings indicate to sign the logout request" do
136
+ def setup
137
+ @settings = OneLogin::RubySaml::Settings.new
138
+ @settings.compress_request = false
139
+ @settings.idp_sso_target_url = "http://example.com?field=value"
140
+ @settings.name_identifier_value = "f00f00"
141
+ @settings.security[:logout_requests_signed] = true
142
+ @settings.security[:embed_sign] = false
143
+ @settings.certificate = ruby_saml_cert_text
144
+ @settings.private_key = ruby_saml_key_text
145
+ @cert = OpenSSL::X509::Certificate.new(ruby_saml_cert_text)
146
+ end
142
147
 
143
- params = OneLogin::RubySaml::Logoutrequest.new.create_params(settings)
148
+ it "create a signature parameter with RSA_SHA1 and validate it" do
149
+ settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1
150
+
151
+ params = OneLogin::RubySaml::Logoutrequest.new.create_params(@settings, :RelayState => 'http://example.com')
152
+ assert params['SAMLRequest']
153
+ assert params[:RelayState]
144
154
  assert params['Signature']
145
- assert params['SigAlg'] == XMLSecurity::Document::SHA1
155
+ assert_equal params['SigAlg'], XMLSecurity::Document::RSA_SHA1
146
156
 
147
- # signature_method only affects the embedeed signature
148
- settings.security[:signature_method] = XMLSecurity::Document::SHA256
149
- params = OneLogin::RubySaml::Logoutrequest.new.create_params(settings)
157
+ query_string = "SAMLRequest=#{CGI.escape(params['SAMLRequest'])}"
158
+ query_string << "&RelayState=#{CGI.escape(params[:RelayState])}"
159
+ query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}"
160
+
161
+ signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg'])
162
+ assert_equal signature_algorithm, OpenSSL::Digest::SHA1
163
+ assert @cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string)
164
+ end
165
+
166
+ it "create a signature parameter with RSA_SHA256 and validate it" do
167
+ @settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA256
168
+
169
+ params = OneLogin::RubySaml::Logoutrequest.new.create_params(@settings, :RelayState => 'http://example.com')
150
170
  assert params['Signature']
151
- assert params['SigAlg'] == XMLSecurity::Document::SHA1
171
+ assert_equal params['SigAlg'], XMLSecurity::Document::RSA_SHA256
172
+
173
+ query_string = "SAMLRequest=#{CGI.escape(params['SAMLRequest'])}"
174
+ query_string << "&RelayState=#{CGI.escape(params[:RelayState])}"
175
+ query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}"
176
+
177
+ signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg'])
178
+ assert_equal signature_algorithm, OpenSSL::Digest::SHA256
179
+ assert @cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string)
152
180
  end
153
- end
154
181
 
182
+ end
155
183
  end
156
184
  end
@@ -1,5 +1,6 @@
1
1
  require File.expand_path(File.join(File.dirname(__FILE__), "test_helper"))
2
- require 'rexml/document'
2
+
3
+ require 'onelogin/ruby-saml/logoutresponse'
3
4
  require 'responses/logoutresponse_fixtures'
4
5
 
5
6
  class RubySamlTest < Minitest::Test
@@ -1,88 +1,133 @@
1
1
  require File.expand_path(File.join(File.dirname(__FILE__), "test_helper"))
2
2
 
3
+ require 'onelogin/ruby-saml/metadata'
4
+
3
5
  class MetadataTest < Minitest::Test
4
6
 
5
7
  describe 'Metadata' do
6
- def setup
7
- @settings = OneLogin::RubySaml::Settings.new
8
- @settings.issuer = "https://example.com"
9
- @settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
10
- @settings.assertion_consumer_service_url = "https://foo.example/saml/consume"
11
- @settings.security[:authn_requests_signed] = false
8
+ let(:settings) { OneLogin::RubySaml::Settings.new }
9
+ let(:xml_text) { OneLogin::RubySaml::Metadata.new.generate(settings, false) }
10
+ let(:xml_doc) { REXML::Document.new(xml_text) }
11
+ let(:spsso_descriptor) { REXML::XPath.first(xml_doc, "//md:SPSSODescriptor") }
12
+ let(:acs) { REXML::XPath.first(xml_doc, "//md:AssertionConsumerService") }
13
+
14
+ before do
15
+ settings.issuer = "https://example.com"
16
+ settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
17
+ settings.assertion_consumer_service_url = "https://foo.example/saml/consume"
12
18
  end
13
19
 
14
- it "generates Service Provider Metadata with X509Certificate" do
15
- @settings.security[:authn_requests_signed] = true
16
- @settings.certificate = ruby_saml_cert_text
20
+ it "generates Pretty Print Service Provider Metadata" do
21
+ xml_text = OneLogin::RubySaml::Metadata.new.generate(settings, true)
22
+ # assert correct xml declaration
23
+ start = "<?xml version='1.0' encoding='UTF-8'?>\n<md:EntityDescriptor"
24
+ assert_equal xml_text[0..start.length-1],start
17
25
 
18
- xml_text = OneLogin::RubySaml::Metadata.new.generate(@settings)
26
+ assert_equal "https://example.com", REXML::XPath.first(xml_doc, "//md:EntityDescriptor").attribute("entityID").value
19
27
 
20
- # assert xml_text can be parsed into an xml doc
21
- xml_doc = REXML::Document.new(xml_text)
28
+ assert_equal "urn:oasis:names:tc:SAML:2.0:protocol", spsso_descriptor.attribute("protocolSupportEnumeration").value
29
+ assert_equal "false", spsso_descriptor.attribute("AuthnRequestsSigned").value
30
+ assert_equal "false", spsso_descriptor.attribute("WantAssertionsSigned").value
22
31
 
23
- spsso_descriptor = REXML::XPath.first(xml_doc, "//md:SPSSODescriptor")
24
- assert_equal "true", spsso_descriptor.attribute("AuthnRequestsSigned").value
32
+ assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", REXML::XPath.first(xml_doc, "//md:NameIDFormat").text.strip
25
33
 
26
- cert_node = REXML::XPath.first(xml_doc, "//md:KeyDescriptor/ds:KeyInfo/ds:X509Data/ds:X509Certificate", {
27
- "md" => "urn:oasis:names:tc:SAML:2.0:metadata",
28
- "ds" => "http://www.w3.org/2000/09/xmldsig#"
29
- })
30
- cert_text = cert_node.text
31
- cert = OpenSSL::X509::Certificate.new(Base64.decode64(cert_text))
32
- assert_equal ruby_saml_cert.to_der, cert.to_der
34
+ assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", acs.attribute("Binding").value
35
+ assert_equal "https://foo.example/saml/consume", acs.attribute("Location").value
33
36
  end
34
37
 
35
38
  it "generates Service Provider Metadata" do
36
- settings = OneLogin::RubySaml::Settings.new
37
- settings.issuer = "https://example.com"
38
- settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
39
- settings.assertion_consumer_service_url = "https://foo.example/saml/consume"
40
- settings.security[:authn_requests_signed] = false
41
-
42
- xml_text = OneLogin::RubySaml::Metadata.new.generate(settings)
43
-
44
39
  # assert correct xml declaration
45
- start = "<?xml version='1.0' encoding='UTF-8'?>\n<md:EntityDescriptor"
46
- assert xml_text[0..start.length-1] == start
47
-
48
- # assert xml_text can be parsed into an xml doc
49
- xml_doc = REXML::Document.new(xml_text)
40
+ start = "<?xml version='1.0' encoding='UTF-8'?><md:EntityDescriptor"
41
+ assert_equal xml_text[0..start.length-1], start
50
42
 
51
43
  assert_equal "https://example.com", REXML::XPath.first(xml_doc, "//md:EntityDescriptor").attribute("entityID").value
52
44
 
53
- spsso_descriptor = REXML::XPath.first(xml_doc, "//md:SPSSODescriptor")
54
45
  assert_equal "urn:oasis:names:tc:SAML:2.0:protocol", spsso_descriptor.attribute("protocolSupportEnumeration").value
55
46
  assert_equal "false", spsso_descriptor.attribute("AuthnRequestsSigned").value
56
47
  assert_equal "false", spsso_descriptor.attribute("WantAssertionsSigned").value
57
48
 
58
49
  assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", REXML::XPath.first(xml_doc, "//md:NameIDFormat").text.strip
59
50
 
60
- acs = REXML::XPath.first(xml_doc, "//md:AssertionConsumerService")
61
51
  assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", acs.attribute("Binding").value
62
52
  assert_equal "https://foo.example/saml/consume", acs.attribute("Location").value
63
53
  end
64
54
 
65
- it "generates attribute service if configured" do
66
- settings = OneLogin::RubySaml::Settings.new
67
- settings.issuer = "https://example.com"
68
- settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
69
- settings.assertion_consumer_service_url = "https://foo.example/saml/consume"
70
- settings.attribute_consuming_service.configure do
71
- service_name "Test Service"
72
- add_attribute(:name => "Name", :name_format => "Name Format", :friendly_name => "Friendly Name", :attribute_value => "Attribute Value")
55
+ describe "when auth requests are signed" do
56
+ let(:cert_node) do
57
+ REXML::XPath.first(
58
+ xml_doc,
59
+ "//md:KeyDescriptor/ds:KeyInfo/ds:X509Data/ds:X509Certificate",
60
+ "md" => "urn:oasis:names:tc:SAML:2.0:metadata",
61
+ "ds" => "http://www.w3.org/2000/09/xmldsig#"
62
+ )
63
+ end
64
+ let(:cert) { OpenSSL::X509::Certificate.new(Base64.decode64(cert_node.text)) }
65
+
66
+ before do
67
+ settings.security[:authn_requests_signed] = true
68
+ settings.certificate = ruby_saml_cert_text
73
69
  end
74
70
 
75
- xml_text = OneLogin::RubySaml::Metadata.new.generate(settings)
76
- xml_doc = REXML::Document.new(xml_text)
77
- acs = REXML::XPath.first(xml_doc, "//md:AttributeConsumingService")
78
- assert_equal "true", acs.attribute("isDefault").value
79
- assert_equal "1", acs.attribute("index").value
80
- assert_equal REXML::XPath.first(xml_doc, "//md:ServiceName").text.strip, "Test Service"
81
- req_attr = REXML::XPath.first(xml_doc, "//md:RequestedAttribute")
82
- assert_equal "Name", req_attr.attribute("Name").value
83
- assert_equal "Name Format", req_attr.attribute("NameFormat").value
84
- assert_equal "Friendly Name", req_attr.attribute("FriendlyName").value
85
- assert_equal "Attribute Value", REXML::XPath.first(xml_doc, "//md:AttributeValue").text.strip
71
+ it "generates Service Provider Metadata with X509Certificate" do
72
+ assert_equal "true", spsso_descriptor.attribute("AuthnRequestsSigned").value
73
+ assert_equal ruby_saml_cert.to_der, cert.to_der
74
+ end
75
+ end
76
+
77
+ describe "when attribute service is configured" do
78
+ let(:attr_svc) { REXML::XPath.first(xml_doc, "//md:AttributeConsumingService") }
79
+ let(:req_attr) { REXML::XPath.first(xml_doc, "//md:RequestedAttribute") }
80
+
81
+ before do
82
+ settings.attribute_consuming_service.configure do
83
+ service_name "Test Service"
84
+ add_attribute(:name => "Name", :name_format => "Name Format", :friendly_name => "Friendly Name", :attribute_value => "Attribute Value")
85
+ end
86
+ end
87
+
88
+ it "generates attribute service" do
89
+ assert_equal "true", attr_svc.attribute("isDefault").value
90
+ assert_equal "1", attr_svc.attribute("index").value
91
+ assert_equal REXML::XPath.first(xml_doc, "//md:ServiceName").text.strip, "Test Service"
92
+
93
+ assert_equal "Name", req_attr.attribute("Name").value
94
+ assert_equal "Name Format", req_attr.attribute("NameFormat").value
95
+ assert_equal "Friendly Name", req_attr.attribute("FriendlyName").value
96
+ assert_equal "Attribute Value", REXML::XPath.first(xml_doc, "//md:AttributeValue").text.strip
97
+ end
98
+ end
99
+
100
+ describe "when the settings indicate to sign (embedded) metadata" do
101
+ before do
102
+ settings.security[:metadata_signed] = true
103
+ settings.certificate = ruby_saml_cert_text
104
+ settings.private_key = ruby_saml_key_text
105
+ end
106
+
107
+ it "creates a signed metadata" do
108
+ assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>]m, xml_text
109
+ assert_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1'/>], xml_text
110
+ assert_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2000/09/xmldsig#sha1'/>], xml_text
111
+ signed_metadata = XMLSecurity::SignedDocument.new(xml_text)
112
+ assert signed_metadata.validate_document(ruby_saml_cert_fingerprint, false)
113
+ end
114
+
115
+ describe "when digest and signature methods are specified" do
116
+ before do
117
+ settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA256
118
+ settings.security[:digest_method] = XMLSecurity::Document::SHA512
119
+ end
120
+
121
+ it "creates a signed metadata with specified digest and signature methods" do
122
+ assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>]m, xml_text
123
+ assert_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'/>], xml_text
124
+ assert_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2001/04/xmldsig-more#sha512'/>], xml_text
125
+
126
+ signed_metadata_2 = XMLSecurity::SignedDocument.new(xml_text)
127
+
128
+ assert signed_metadata_2.validate_document(ruby_saml_cert_fingerprint, false)
129
+ end
130
+ end
86
131
  end
87
132
  end
88
133
  end