ruby-saml 0.9.1 → 0.9.2

Sign up to get free protection for your applications and to get access to all the features.

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