ruby-saml 1.9.0 → 1.15.0

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 (160) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/test.yml +43 -0
  3. data/{changelog.md → CHANGELOG.md} +72 -1
  4. data/LICENSE +2 -1
  5. data/README.md +439 -212
  6. data/UPGRADING.md +149 -0
  7. data/lib/onelogin/ruby-saml/attributes.rb +24 -1
  8. data/lib/onelogin/ruby-saml/authrequest.rb +27 -11
  9. data/lib/onelogin/ruby-saml/idp_metadata_parser.rb +285 -184
  10. data/lib/onelogin/ruby-saml/logging.rb +3 -3
  11. data/lib/onelogin/ruby-saml/logoutrequest.rb +27 -12
  12. data/lib/onelogin/ruby-saml/logoutresponse.rb +27 -11
  13. data/lib/onelogin/ruby-saml/metadata.rb +62 -17
  14. data/lib/onelogin/ruby-saml/response.rb +87 -38
  15. data/lib/onelogin/ruby-saml/saml_message.rb +16 -8
  16. data/lib/onelogin/ruby-saml/setting_error.rb +6 -0
  17. data/lib/onelogin/ruby-saml/settings.rb +123 -52
  18. data/lib/onelogin/ruby-saml/slo_logoutrequest.rb +33 -31
  19. data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +44 -21
  20. data/lib/onelogin/ruby-saml/utils.rb +101 -9
  21. data/lib/onelogin/ruby-saml/version.rb +1 -1
  22. data/lib/xml_security.rb +41 -15
  23. data/ruby-saml.gemspec +49 -13
  24. metadata +71 -308
  25. data/.travis.yml +0 -32
  26. data/test/certificates/certificate1 +0 -12
  27. data/test/certificates/certificate_without_head_foot +0 -1
  28. data/test/certificates/formatted_certificate +0 -14
  29. data/test/certificates/formatted_chained_certificate +0 -42
  30. data/test/certificates/formatted_private_key +0 -12
  31. data/test/certificates/formatted_rsa_private_key +0 -12
  32. data/test/certificates/invalid_certificate1 +0 -1
  33. data/test/certificates/invalid_certificate2 +0 -1
  34. data/test/certificates/invalid_certificate3 +0 -12
  35. data/test/certificates/invalid_chained_certificate1 +0 -1
  36. data/test/certificates/invalid_private_key1 +0 -1
  37. data/test/certificates/invalid_private_key2 +0 -1
  38. data/test/certificates/invalid_private_key3 +0 -10
  39. data/test/certificates/invalid_rsa_private_key1 +0 -1
  40. data/test/certificates/invalid_rsa_private_key2 +0 -1
  41. data/test/certificates/invalid_rsa_private_key3 +0 -10
  42. data/test/certificates/ruby-saml-2.crt +0 -15
  43. data/test/certificates/ruby-saml.crt +0 -14
  44. data/test/certificates/ruby-saml.key +0 -15
  45. data/test/idp_metadata_parser_test.rb +0 -579
  46. data/test/logging_test.rb +0 -62
  47. data/test/logout_requests/invalid_slo_request.xml +0 -6
  48. data/test/logout_requests/slo_request.xml +0 -4
  49. data/test/logout_requests/slo_request.xml.base64 +0 -1
  50. data/test/logout_requests/slo_request_deflated.xml.base64 +0 -1
  51. data/test/logout_requests/slo_request_with_name_id_format.xml +0 -4
  52. data/test/logout_requests/slo_request_with_session_index.xml +0 -5
  53. data/test/logout_responses/logoutresponse_fixtures.rb +0 -67
  54. data/test/logoutrequest_test.rb +0 -226
  55. data/test/logoutresponse_test.rb +0 -402
  56. data/test/metadata/idp_descriptor.xml +0 -26
  57. data/test/metadata/idp_descriptor_2.xml +0 -56
  58. data/test/metadata/idp_descriptor_3.xml +0 -14
  59. data/test/metadata/idp_descriptor_4.xml +0 -72
  60. data/test/metadata/idp_metadata_different_sign_and_encrypt_cert.xml +0 -72
  61. data/test/metadata/idp_metadata_multi_certs.xml +0 -75
  62. data/test/metadata/idp_metadata_multi_signing_certs.xml +0 -52
  63. data/test/metadata/idp_metadata_same_sign_and_encrypt_cert.xml +0 -71
  64. data/test/metadata/idp_multiple_descriptors.xml +0 -53
  65. data/test/metadata/no_idp_descriptor.xml +0 -21
  66. data/test/metadata_test.rb +0 -331
  67. data/test/request_test.rb +0 -323
  68. data/test/response_test.rb +0 -1619
  69. data/test/responses/adfs_response_sha1.xml +0 -46
  70. data/test/responses/adfs_response_sha256.xml +0 -46
  71. data/test/responses/adfs_response_sha384.xml +0 -46
  72. data/test/responses/adfs_response_sha512.xml +0 -46
  73. data/test/responses/adfs_response_xmlns.xml +0 -45
  74. data/test/responses/attackxee.xml +0 -13
  75. data/test/responses/invalids/duplicated_attributes.xml.base64 +0 -1
  76. data/test/responses/invalids/empty_destination.xml.base64 +0 -1
  77. data/test/responses/invalids/empty_nameid.xml.base64 +0 -1
  78. data/test/responses/invalids/encrypted_new_attack.xml.base64 +0 -1
  79. data/test/responses/invalids/invalid_audience.xml.base64 +0 -1
  80. data/test/responses/invalids/invalid_issuer_assertion.xml.base64 +0 -1
  81. data/test/responses/invalids/invalid_issuer_message.xml.base64 +0 -1
  82. data/test/responses/invalids/invalid_signature_position.xml.base64 +0 -1
  83. data/test/responses/invalids/invalid_subjectconfirmation_inresponse.xml.base64 +0 -1
  84. data/test/responses/invalids/invalid_subjectconfirmation_nb.xml.base64 +0 -1
  85. data/test/responses/invalids/invalid_subjectconfirmation_noa.xml.base64 +0 -1
  86. data/test/responses/invalids/invalid_subjectconfirmation_recipient.xml.base64 +0 -1
  87. data/test/responses/invalids/multiple_assertions.xml.base64 +0 -2
  88. data/test/responses/invalids/multiple_signed.xml.base64 +0 -1
  89. data/test/responses/invalids/no_authnstatement.xml.base64 +0 -1
  90. data/test/responses/invalids/no_conditions.xml.base64 +0 -1
  91. data/test/responses/invalids/no_id.xml.base64 +0 -1
  92. data/test/responses/invalids/no_issuer_assertion.xml.base64 +0 -1
  93. data/test/responses/invalids/no_issuer_response.xml.base64 +0 -1
  94. data/test/responses/invalids/no_nameid.xml.base64 +0 -1
  95. data/test/responses/invalids/no_saml2.xml.base64 +0 -1
  96. data/test/responses/invalids/no_signature.xml.base64 +0 -1
  97. data/test/responses/invalids/no_status.xml.base64 +0 -1
  98. data/test/responses/invalids/no_status_code.xml.base64 +0 -1
  99. data/test/responses/invalids/no_subjectconfirmation_data.xml.base64 +0 -1
  100. data/test/responses/invalids/no_subjectconfirmation_method.xml.base64 +0 -1
  101. data/test/responses/invalids/response_invalid_signed_element.xml.base64 +0 -1
  102. data/test/responses/invalids/response_with_concealed_signed_assertion.xml +0 -51
  103. data/test/responses/invalids/response_with_doubled_signed_assertion.xml +0 -49
  104. data/test/responses/invalids/signature_wrapping_attack.xml.base64 +0 -1
  105. data/test/responses/invalids/status_code_responder.xml.base64 +0 -1
  106. data/test/responses/invalids/status_code_responer_and_msg.xml.base64 +0 -1
  107. data/test/responses/invalids/wrong_spnamequalifier.xml.base64 +0 -1
  108. data/test/responses/no_signature_ns.xml +0 -48
  109. data/test/responses/open_saml_response.xml +0 -56
  110. data/test/responses/response_assertion_wrapped.xml.base64 +0 -93
  111. data/test/responses/response_audience_self_closed_tag.xml.base64 +0 -1
  112. data/test/responses/response_double_status_code.xml.base64 +0 -1
  113. data/test/responses/response_encrypted_attrs.xml.base64 +0 -1
  114. data/test/responses/response_encrypted_nameid.xml.base64 +0 -1
  115. data/test/responses/response_eval.xml +0 -7
  116. data/test/responses/response_no_cert_and_encrypted_attrs.xml +0 -29
  117. data/test/responses/response_node_text_attack.xml.base64 +0 -1
  118. data/test/responses/response_node_text_attack2.xml.base64 +0 -1
  119. data/test/responses/response_node_text_attack3.xml.base64 +0 -1
  120. data/test/responses/response_unsigned_xml_base64 +0 -1
  121. data/test/responses/response_with_ampersands.xml +0 -139
  122. data/test/responses/response_with_ampersands.xml.base64 +0 -93
  123. data/test/responses/response_with_ds_namespace_at_the_root.xml.base64 +0 -1
  124. data/test/responses/response_with_multiple_attribute_statements.xml +0 -72
  125. data/test/responses/response_with_multiple_attribute_values.xml +0 -67
  126. data/test/responses/response_with_retrieval_method.xml +0 -26
  127. data/test/responses/response_with_saml2_namespace.xml.base64 +0 -102
  128. data/test/responses/response_with_signed_assertion.xml.base64 +0 -66
  129. data/test/responses/response_with_signed_assertion_2.xml.base64 +0 -1
  130. data/test/responses/response_with_signed_assertion_3.xml +0 -30
  131. data/test/responses/response_with_signed_message_and_assertion.xml +0 -34
  132. data/test/responses/response_with_undefined_recipient.xml.base64 +0 -1
  133. data/test/responses/response_without_attributes.xml.base64 +0 -79
  134. data/test/responses/response_without_reference_uri.xml.base64 +0 -1
  135. data/test/responses/response_wrapped.xml.base64 +0 -150
  136. data/test/responses/signed_message_encrypted_signed_assertion.xml.base64 +0 -1
  137. data/test/responses/signed_message_encrypted_unsigned_assertion.xml.base64 +0 -1
  138. data/test/responses/signed_nameid_in_atts.xml +0 -47
  139. data/test/responses/signed_unqual_nameid_in_atts.xml +0 -47
  140. data/test/responses/simple_saml_php.xml +0 -71
  141. data/test/responses/starfield_response.xml.base64 +0 -1
  142. data/test/responses/test_sign.xml +0 -43
  143. data/test/responses/unsigned_encrypted_adfs.xml +0 -23
  144. data/test/responses/unsigned_message_aes128_encrypted_signed_assertion.xml.base64 +0 -1
  145. data/test/responses/unsigned_message_aes192_encrypted_signed_assertion.xml.base64 +0 -1
  146. data/test/responses/unsigned_message_aes256_encrypted_signed_assertion.xml.base64 +0 -1
  147. data/test/responses/unsigned_message_des192_encrypted_signed_assertion.xml.base64 +0 -1
  148. data/test/responses/unsigned_message_encrypted_assertion_without_saml_namespace.xml.base64 +0 -1
  149. data/test/responses/unsigned_message_encrypted_signed_assertion.xml.base64 +0 -1
  150. data/test/responses/unsigned_message_encrypted_unsigned_assertion.xml.base64 +0 -1
  151. data/test/responses/valid_response.xml.base64 +0 -1
  152. data/test/responses/valid_response_with_formatted_x509certificate.xml.base64 +0 -1
  153. data/test/responses/valid_response_without_x509certificate.xml.base64 +0 -1
  154. data/test/saml_message_test.rb +0 -56
  155. data/test/settings_test.rb +0 -329
  156. data/test/slo_logoutrequest_test.rb +0 -448
  157. data/test/slo_logoutresponse_test.rb +0 -199
  158. data/test/test_helper.rb +0 -327
  159. data/test/utils_test.rb +0 -254
  160. data/test/xml_security_test.rb +0 -421
@@ -4,7 +4,6 @@ require 'base64'
4
4
  require 'nokogiri'
5
5
  require 'rexml/document'
6
6
  require 'rexml/xpath'
7
- require 'thread'
8
7
  require "onelogin/ruby-saml/error_handling"
9
8
 
10
9
  # Only supports SAML 2.0
@@ -16,8 +15,8 @@ module OneLogin
16
15
  class SamlMessage
17
16
  include REXML
18
17
 
19
- ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion"
20
- PROTOCOL = "urn:oasis:names:tc:SAML:2.0:protocol"
18
+ ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion".freeze
19
+ PROTOCOL = "urn:oasis:names:tc:SAML:2.0:protocol".freeze
21
20
 
22
21
  BASE64_FORMAT = %r(\A([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?\Z)
23
22
  @@mutex = Mutex.new
@@ -69,14 +68,14 @@ module OneLogin
69
68
  xml = Nokogiri::XML(document.to_s) do |config|
70
69
  config.options = XMLSecurity::BaseDocument::NOKOGIRI_OPTIONS
71
70
  end
72
- rescue Exception => error
71
+ rescue StandardError => error
73
72
  return false if soft
74
73
  raise ValidationError.new("XML load failed: #{error.message}")
75
74
  end
76
75
 
77
76
  SamlMessage.schema.validate(xml).map do |schema_error|
78
77
  return false if soft
79
- raise ValidationError.new("#{schema_error.message}\n\n#{xml.to_s}")
78
+ raise ValidationError.new("#{schema_error.message}\n\n#{xml}")
80
79
  end
81
80
  end
82
81
 
@@ -86,9 +85,14 @@ module OneLogin
86
85
  # @param saml [String] The deflated and encoded SAML Message
87
86
  # @return [String] The plain SAML Message
88
87
  #
89
- def decode_raw_saml(saml)
88
+ def decode_raw_saml(saml, settings = nil)
90
89
  return saml unless base64_encoded?(saml)
91
90
 
91
+ settings = OneLogin::RubySaml::Settings.new if settings.nil?
92
+ if saml.bytesize > settings.message_max_bytesize
93
+ raise ValidationError.new("Encoded SAML Message exceeds " + settings.message_max_bytesize.to_s + " bytes, so was rejected")
94
+ end
95
+
92
96
  decoded = decode(saml)
93
97
  begin
94
98
  inflate(decoded)
@@ -105,7 +109,7 @@ module OneLogin
105
109
  def encode_raw_saml(saml, settings)
106
110
  saml = deflate(saml) if settings.compress_request
107
111
 
108
- CGI.escape(Base64.encode64(saml))
112
+ CGI.escape(encode(saml))
109
113
  end
110
114
 
111
115
  # Base 64 decode method
@@ -121,7 +125,11 @@ module OneLogin
121
125
  # @return [String] The encoded string
122
126
  #
123
127
  def encode(string)
124
- Base64.encode64(string).gsub(/\n/, "")
128
+ if Base64.respond_to?('strict_encode64')
129
+ Base64.strict_encode64(string)
130
+ else
131
+ Base64.encode64(string).gsub(/\n/, "")
132
+ end
125
133
  end
126
134
 
127
135
  # Check if a string is base64 encoded
@@ -0,0 +1,6 @@
1
+ module OneLogin
2
+ module RubySaml
3
+ class SettingError < StandardError
4
+ end
5
+ end
6
+ end
@@ -1,6 +1,7 @@
1
1
  require "xml_security"
2
2
  require "onelogin/ruby-saml/attribute_service"
3
3
  require "onelogin/ruby-saml/utils"
4
+ require "onelogin/ruby-saml/validation_error"
4
5
 
5
6
  # Only supports SAML 2.0
6
7
  module OneLogin
@@ -19,7 +20,7 @@ module OneLogin
19
20
  end
20
21
 
21
22
  config.each do |k,v|
22
- acc = "#{k.to_s}=".to_sym
23
+ acc = "#{k}=".to_sym
23
24
  if respond_to? acc
24
25
  value = v.is_a?(Hash) ? v.dup : v
25
26
  send(acc, value)
@@ -30,27 +31,32 @@ module OneLogin
30
31
 
31
32
  # IdP Data
32
33
  attr_accessor :idp_entity_id
33
- attr_accessor :idp_sso_target_url
34
- attr_accessor :idp_slo_target_url
34
+ attr_writer :idp_sso_service_url
35
+ attr_writer :idp_slo_service_url
36
+ attr_accessor :idp_slo_response_service_url
35
37
  attr_accessor :idp_cert
36
38
  attr_accessor :idp_cert_fingerprint
37
39
  attr_accessor :idp_cert_fingerprint_algorithm
38
40
  attr_accessor :idp_cert_multi
39
41
  attr_accessor :idp_attribute_names
40
42
  attr_accessor :idp_name_qualifier
43
+ attr_accessor :valid_until
41
44
  # SP Data
42
- attr_accessor :issuer
45
+ attr_writer :sp_entity_id
43
46
  attr_accessor :assertion_consumer_service_url
44
- attr_accessor :assertion_consumer_service_binding
47
+ attr_reader :assertion_consumer_service_binding
48
+ attr_writer :single_logout_service_url
45
49
  attr_accessor :sp_name_qualifier
46
50
  attr_accessor :name_identifier_format
47
51
  attr_accessor :name_identifier_value
52
+ attr_accessor :name_identifier_value_requested
48
53
  attr_accessor :sessionindex
49
54
  attr_accessor :compress_request
50
55
  attr_accessor :compress_response
51
56
  attr_accessor :double_quote_xml_attribute_values
57
+ attr_accessor :message_max_bytesize
52
58
  attr_accessor :passive
53
- attr_accessor :protocol_binding
59
+ attr_reader :protocol_binding
54
60
  attr_accessor :attributes_index
55
61
  attr_accessor :force_authn
56
62
  attr_accessor :certificate
@@ -63,52 +69,99 @@ module OneLogin
63
69
  # Work-flow
64
70
  attr_accessor :security
65
71
  attr_accessor :soft
66
- # Compability
72
+ # Deprecated
67
73
  attr_accessor :assertion_consumer_logout_service_url
68
- attr_accessor :assertion_consumer_logout_service_binding
74
+ attr_reader :assertion_consumer_logout_service_binding
75
+ attr_accessor :issuer
76
+ attr_accessor :idp_sso_target_url
77
+ attr_accessor :idp_slo_target_url
69
78
 
70
- # @return [String] Single Logout Service URL.
79
+ # @return [String] IdP Single Sign On Service URL
71
80
  #
72
- def single_logout_service_url
73
- val = nil
74
- if @single_logout_service_url.nil?
75
- if @assertion_consumer_logout_service_url
76
- val = @assertion_consumer_logout_service_url
77
- end
78
- else
79
- val = @single_logout_service_url
80
- end
81
- val
81
+ def idp_sso_service_url
82
+ @idp_sso_service_url || @idp_sso_target_url
83
+ end
84
+
85
+ # @return [String] IdP Single Logout Service URL
86
+ #
87
+ def idp_slo_service_url
88
+ @idp_slo_service_url || @idp_slo_target_url
89
+ end
90
+
91
+ # @return [String] IdP Single Sign On Service Binding
92
+ #
93
+ def idp_sso_service_binding
94
+ @idp_sso_service_binding || idp_binding_from_embed_sign
95
+ end
96
+
97
+ # Setter for IdP Single Sign On Service Binding
98
+ # @param value [String, Symbol].
99
+ #
100
+ def idp_sso_service_binding=(value)
101
+ @idp_sso_service_binding = get_binding(value)
102
+ end
103
+
104
+ # @return [String] IdP Single Logout Service Binding
105
+ #
106
+ def idp_slo_service_binding
107
+ @idp_slo_service_binding || idp_binding_from_embed_sign
108
+ end
109
+
110
+ # Setter for IdP Single Logout Service Binding
111
+ # @param value [String, Symbol].
112
+ #
113
+ def idp_slo_service_binding=(value)
114
+ @idp_slo_service_binding = get_binding(value)
115
+ end
116
+
117
+ # @return [String] SP Entity ID
118
+ #
119
+ def sp_entity_id
120
+ @sp_entity_id || @issuer
121
+ end
122
+
123
+ # Setter for SP Protocol Binding
124
+ # @param value [String, Symbol].
125
+ #
126
+ def protocol_binding=(value)
127
+ @protocol_binding = get_binding(value)
128
+ end
129
+
130
+ # Setter for SP Assertion Consumer Service Binding
131
+ # @param value [String, Symbol].
132
+ #
133
+ def assertion_consumer_service_binding=(value)
134
+ @assertion_consumer_service_binding = get_binding(value)
82
135
  end
83
136
 
84
- # Setter for the Single Logout Service URL.
85
- # @param url [String].
137
+ # @return [String] Single Logout Service URL.
86
138
  #
87
- def single_logout_service_url=(url)
88
- @single_logout_service_url = url
139
+ def single_logout_service_url
140
+ @single_logout_service_url || @assertion_consumer_logout_service_url
89
141
  end
90
142
 
91
143
  # @return [String] Single Logout Service Binding.
92
144
  #
93
145
  def single_logout_service_binding
94
- val = nil
95
- if @single_logout_service_binding.nil?
96
- if @assertion_consumer_logout_service_binding
97
- val = @assertion_consumer_logout_service_binding
98
- end
99
- else
100
- val = @single_logout_service_binding
101
- end
102
- val
146
+ @single_logout_service_binding || @assertion_consumer_logout_service_binding
103
147
  end
104
148
 
105
149
  # Setter for Single Logout Service Binding.
106
150
  #
107
151
  # (Currently we only support "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect")
108
- # @param url [String]
152
+ # @param value [String, Symbol]
153
+ #
154
+ def single_logout_service_binding=(value)
155
+ @single_logout_service_binding = get_binding(value)
156
+ end
157
+
158
+ # @deprecated Setter for legacy Single Logout Service Binding parameter.
159
+ #
160
+ # (Currently we only support "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect")
161
+ # @param value [String, Symbol]
109
162
  #
110
- def single_logout_service_binding=(url)
111
- @single_logout_service_binding = url
163
+ def assertion_consumer_logout_service_binding=(value)
164
+ @assertion_consumer_logout_service_binding = get_binding(value)
112
165
  end
113
166
 
114
167
  # Calculates the fingerprint of the IdP x509 certificate.
@@ -142,17 +195,13 @@ module OneLogin
142
195
 
143
196
  certs = {:signing => [], :encryption => [] }
144
197
 
145
- if idp_cert_multi.key?(:signing) and not idp_cert_multi[:signing].empty?
146
- idp_cert_multi[:signing].each do |idp_cert|
147
- formatted_cert = OneLogin::RubySaml::Utils.format_cert(idp_cert)
148
- certs[:signing].push(OpenSSL::X509::Certificate.new(formatted_cert))
149
- end
150
- end
198
+ [:signing, :encryption].each do |type|
199
+ certs_for_type = idp_cert_multi[type] || idp_cert_multi[type.to_s]
200
+ next if !certs_for_type || certs_for_type.empty?
151
201
 
152
- if idp_cert_multi.key?(:encryption) and not idp_cert_multi[:encryption].empty?
153
- idp_cert_multi[:encryption].each do |idp_cert|
202
+ certs_for_type.each do |idp_cert|
154
203
  formatted_cert = OneLogin::RubySaml::Utils.format_cert(idp_cert)
155
- certs[:encryption].push(OpenSSL::X509::Certificate.new(formatted_cert))
204
+ certs[type].push(OpenSSL::X509::Certificate.new(formatted_cert))
156
205
  end
157
206
  end
158
207
 
@@ -165,7 +214,15 @@ module OneLogin
165
214
  return nil if certificate.nil? || certificate.empty?
166
215
 
167
216
  formatted_cert = OneLogin::RubySaml::Utils.format_cert(certificate)
168
- OpenSSL::X509::Certificate.new(formatted_cert)
217
+ cert = OpenSSL::X509::Certificate.new(formatted_cert)
218
+
219
+ if security[:check_sp_cert_expiration]
220
+ if OneLogin::RubySaml::Utils.is_cert_expired(cert)
221
+ raise OneLogin::RubySaml::ValidationError.new("The SP certificate expired.")
222
+ end
223
+ end
224
+
225
+ cert
169
226
  end
170
227
 
171
228
  # @return [OpenSSL::X509::Certificate|nil] Build the New SP certificate from the settings (previously format it)
@@ -186,15 +243,26 @@ module OneLogin
186
243
  OpenSSL::PKey::RSA.new(formatted_private_key)
187
244
  end
188
245
 
189
- private
246
+
247
+ def idp_binding_from_embed_sign
248
+ security[:embed_sign] ? Utils::BINDINGS[:post] : Utils::BINDINGS[:redirect]
249
+ end
250
+
251
+ def get_binding(value)
252
+ return unless value
253
+
254
+ Utils::BINDINGS[value.to_sym] || value
255
+ end
190
256
 
191
257
  DEFAULTS = {
192
- :assertion_consumer_service_binding => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST".freeze,
193
- :single_logout_service_binding => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect".freeze,
258
+ :assertion_consumer_service_binding => Utils::BINDINGS[:post],
259
+ :single_logout_service_binding => Utils::BINDINGS[:redirect],
194
260
  :idp_cert_fingerprint_algorithm => XMLSecurity::Document::SHA1,
195
261
  :compress_request => true,
196
262
  :compress_response => true,
263
+ :message_max_bytesize => 250000,
197
264
  :soft => true,
265
+ :double_quote_xml_attribute_values => false,
198
266
  :security => {
199
267
  :authn_requests_signed => false,
200
268
  :logout_requests_signed => false,
@@ -203,11 +271,14 @@ module OneLogin
203
271
  :want_assertions_encrypted => false,
204
272
  :want_name_id => false,
205
273
  :metadata_signed => false,
206
- :embed_sign => false,
274
+ :embed_sign => false, # Deprecated
207
275
  :digest_method => XMLSecurity::Document::SHA1,
208
- :signature_method => XMLSecurity::Document::RSA_SHA1
209
- }.freeze,
210
- :double_quote_xml_attribute_values => false,
276
+ :signature_method => XMLSecurity::Document::RSA_SHA1,
277
+ :check_idp_cert_expiration => false,
278
+ :check_sp_cert_expiration => false,
279
+ :strict_audience_validation => false,
280
+ :lowercase_url_encoding => false
281
+ }.freeze
211
282
  }.freeze
212
283
  end
213
284
  end
@@ -43,10 +43,14 @@ module OneLogin
43
43
  end
44
44
  end
45
45
 
46
- @request = decode_raw_saml(request)
46
+ @request = decode_raw_saml(request, settings)
47
47
  @document = REXML::Document.new(@request)
48
48
  end
49
49
 
50
+ def request_id
51
+ id(document)
52
+ end
53
+
50
54
  # Validates the Logout Request with the default values (soft = true)
51
55
  # @param collect_errors [Boolean] Stop validation when first error appears or keep validating.
52
56
  # @return [Boolean] TRUE if the Logout Request is valid
@@ -126,6 +130,12 @@ module OneLogin
126
130
 
127
131
  private
128
132
 
133
+ # returns the allowed clock drift on timing validation
134
+ # @return [Float]
135
+ def allowed_clock_drift
136
+ options[:allowed_clock_drift].to_f.abs + Float::EPSILON
137
+ end
138
+
129
139
  # Hard aux function to validate the Logout Request
130
140
  # @param collect_errors [Boolean] Stop validation when first error appears or keep validating. (if soft=true)
131
141
  # @return [Boolean] TRUE if the Logout Request is valid
@@ -176,15 +186,17 @@ module OneLogin
176
186
  true
177
187
  end
178
188
 
179
- # Validates the time. (If the logout request was initialized with the :allowed_clock_drift option, the timing validations are relaxed by the allowed_clock_drift value)
189
+ # Validates the time. (If the logout request was initialized with the :allowed_clock_drift
190
+ # option, the timing validations are relaxed by the allowed_clock_drift value)
180
191
  # If fails, the error is added to the errors array
181
192
  # @return [Boolean] True if satisfies the conditions, otherwise False if soft=True
182
193
  # @raise [ValidationError] if soft == false and validation fails
183
194
  #
184
195
  def validate_not_on_or_after
185
196
  now = Time.now.utc
186
- if not_on_or_after && now >= (not_on_or_after + (options[:allowed_clock_drift] || 0))
187
- return append_error("Current time is on or after NotOnOrAfter (#{now} >= #{not_on_or_after})")
197
+
198
+ if not_on_or_after && now >= (not_on_or_after + allowed_clock_drift)
199
+ return append_error("Current time is on or after NotOnOrAfter (#{now} >= #{not_on_or_after}#{" + #{allowed_clock_drift.ceil}s" if allowed_clock_drift > 0})")
188
200
  end
189
201
 
190
202
  true
@@ -236,32 +248,8 @@ module OneLogin
236
248
  return true unless options.has_key? :get_params
237
249
  return true unless options[:get_params].has_key? 'Signature'
238
250
 
239
- # SAML specifies that the signature should be derived from a concatenation
240
- # of URI-encoded values _as sent by the IDP_:
241
- #
242
- # > Further, note that URL-encoding is not canonical; that is, there are multiple legal encodings for a given
243
- # > value. The relying party MUST therefore perform the verification step using the original URL-encoded
244
- # > values it received on the query string. It is not sufficient to re-encode the parameters after they have been
245
- # > processed by software because the resulting encoding may not match the signer's encoding.
246
- #
247
- # <http://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf>
248
- #
249
- # If we don't have the original parts (for backward compatibility) required to correctly verify the signature,
250
- # then fabricate them by re-encoding the parsed URI parameters, and hope that we're lucky enough to use
251
- # the exact same URI-encoding as the IDP. (This is not the case if the IDP is ADFS!)
252
- options[:raw_get_params] ||= {}
253
- if options[:raw_get_params]['SAMLRequest'].nil? && !options[:get_params]['SAMLRequest'].nil?
254
- options[:raw_get_params]['SAMLRequest'] = CGI.escape(options[:get_params]['SAMLRequest'])
255
- end
256
- if options[:raw_get_params]['RelayState'].nil? && !options[:get_params]['RelayState'].nil?
257
- options[:raw_get_params]['RelayState'] = CGI.escape(options[:get_params]['RelayState'])
258
- end
259
- if options[:raw_get_params]['SigAlg'].nil? && !options[:get_params]['SigAlg'].nil?
260
- options[:raw_get_params]['SigAlg'] = CGI.escape(options[:get_params]['SigAlg'])
261
- end
251
+ options[:raw_get_params] = OneLogin::RubySaml::Utils.prepare_raw_get_params(options[:raw_get_params], options[:get_params], settings.security[:lowercase_url_encoding])
262
252
 
263
- # If we only received the raw version of SigAlg,
264
- # then parse it back into the decoded params hash for convenience.
265
253
  if options[:get_params]['SigAlg'].nil? && !options[:raw_get_params]['SigAlg'].nil?
266
254
  options[:get_params]['SigAlg'] = CGI.unescape(options[:raw_get_params]['SigAlg'])
267
255
  end
@@ -280,13 +268,19 @@ module OneLogin
280
268
  :raw_sig_alg => options[:raw_get_params]['SigAlg']
281
269
  )
282
270
 
271
+ expired = false
283
272
  if idp_certs.nil? || idp_certs[:signing].empty?
284
273
  valid = OneLogin::RubySaml::Utils.verify_signature(
285
- :cert => settings.get_idp_cert,
274
+ :cert => idp_cert,
286
275
  :sig_alg => options[:get_params]['SigAlg'],
287
276
  :signature => options[:get_params]['Signature'],
288
277
  :query_string => query_string
289
278
  )
279
+ if valid && settings.security[:check_idp_cert_expiration]
280
+ if OneLogin::RubySaml::Utils.is_cert_expired(idp_cert)
281
+ expired = true
282
+ end
283
+ end
290
284
  else
291
285
  valid = false
292
286
  idp_certs[:signing].each do |signing_idp_cert|
@@ -297,18 +291,26 @@ module OneLogin
297
291
  :query_string => query_string
298
292
  )
299
293
  if valid
294
+ if settings.security[:check_idp_cert_expiration]
295
+ if OneLogin::RubySaml::Utils.is_cert_expired(signing_idp_cert)
296
+ expired = true
297
+ end
298
+ end
300
299
  break
301
300
  end
302
301
  end
303
302
  end
304
303
 
304
+ if expired
305
+ error_msg = "IdP x509 certificate expired"
306
+ return append_error(error_msg)
307
+ end
305
308
  unless valid
306
309
  return append_error("Invalid Signature on Logout Request")
307
310
  end
308
311
 
309
312
  true
310
313
  end
311
-
312
314
  end
313
315
  end
314
316
  end
@@ -2,6 +2,7 @@ require "onelogin/ruby-saml/logging"
2
2
 
3
3
  require "onelogin/ruby-saml/saml_message"
4
4
  require "onelogin/ruby-saml/utils"
5
+ require "onelogin/ruby-saml/setting_error"
5
6
 
6
7
  # Only supports SAML 2.0
7
8
  module OneLogin
@@ -12,7 +13,7 @@ module OneLogin
12
13
  class SloLogoutresponse < SamlMessage
13
14
 
14
15
  # Logout Response ID
15
- attr_reader :uuid
16
+ attr_accessor :uuid
16
17
 
17
18
  # Initializes the Logout Response. A SloLogoutresponse Object that is an extension of the SamlMessage class.
18
19
  # Asigns an ID, a random uuid.
@@ -21,23 +22,30 @@ module OneLogin
21
22
  @uuid = OneLogin::RubySaml::Utils.uuid
22
23
  end
23
24
 
25
+ def response_id
26
+ @uuid
27
+ end
28
+
24
29
  # Creates the Logout Response string.
25
30
  # @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
26
31
  # @param request_id [String] The ID of the LogoutRequest sent by this SP to the IdP. That ID will be placed as the InResponseTo in the logout response
27
32
  # @param logout_message [String] The Message to be placed as StatusMessage in the logout response
28
33
  # @param params [Hash] Some extra parameters to be added in the GET for example the RelayState
34
+ # @param logout_status_code [String] The StatusCode to be placed as StatusMessage in the logout response
29
35
  # @return [String] Logout Request string that includes the SAMLRequest
30
36
  #
31
- def create(settings, request_id = nil, logout_message = nil, params = {})
32
- params = create_params(settings, request_id, logout_message, params)
33
- params_prefix = (settings.idp_slo_target_url =~ /\?/) ? '&' : '?'
37
+ def create(settings, request_id = nil, logout_message = nil, params = {}, logout_status_code = nil)
38
+ params = create_params(settings, request_id, logout_message, params, logout_status_code)
39
+ params_prefix = (settings.idp_slo_service_url =~ /\?/) ? '&' : '?'
40
+ url = settings.idp_slo_response_service_url || settings.idp_slo_service_url
34
41
  saml_response = CGI.escape(params.delete("SAMLResponse"))
35
42
  response_params = "#{params_prefix}SAMLResponse=#{saml_response}"
36
43
  params.each_pair do |key, value|
37
- response_params << "&#{key.to_s}=#{CGI.escape(value.to_s)}"
44
+ response_params << "&#{key}=#{CGI.escape(value.to_s)}"
38
45
  end
39
46
 
40
- @logout_url = settings.idp_slo_target_url + response_params
47
+ raise SettingError.new "Invalid settings, idp_slo_service_url is not set!" if url.nil? or url.empty?
48
+ @logout_url = url + response_params
41
49
  end
42
50
 
43
51
  # Creates the Get parameters for the logout response.
@@ -45,9 +53,10 @@ module OneLogin
45
53
  # @param request_id [String] The ID of the LogoutRequest sent by this SP to the IdP. That ID will be placed as the InResponseTo in the logout response
46
54
  # @param logout_message [String] The Message to be placed as StatusMessage in the logout response
47
55
  # @param params [Hash] Some extra parameters to be added in the GET for example the RelayState
56
+ # @param logout_status_code [String] The StatusCode to be placed as StatusMessage in the logout response
48
57
  # @return [Hash] Parameters
49
58
  #
50
- def create_params(settings, request_id = nil, logout_message = nil, params = {})
59
+ def create_params(settings, request_id = nil, logout_message = nil, params = {}, logout_status_code = nil)
51
60
  # The method expects :RelayState but sometimes we get 'RelayState' instead.
52
61
  # Based on the HashWithIndifferentAccess value in Rails we could experience
53
62
  # conflicts so this line will solve them.
@@ -58,7 +67,7 @@ module OneLogin
58
67
  params.delete('RelayState')
59
68
  end
60
69
 
61
- response_doc = create_logout_response_xml_doc(settings, request_id, logout_message)
70
+ response_doc = create_logout_response_xml_doc(settings, request_id, logout_message, logout_status_code)
62
71
  response_doc.context[:attribute_quote] = :quote if settings.double_quote_xml_attribute_values
63
72
 
64
73
  response = ""
@@ -70,7 +79,7 @@ module OneLogin
70
79
  base64_response = encode(response)
71
80
  response_params = {"SAMLResponse" => base64_response}
72
81
 
73
- if settings.security[:logout_responses_signed] && !settings.security[:embed_sign] && settings.private_key
82
+ if settings.idp_slo_service_binding == Utils::BINDINGS[:redirect] && settings.security[:logout_responses_signed] && settings.private_key
74
83
  params['SigAlg'] = settings.security[:signature_method]
75
84
  url_string = OneLogin::RubySaml::Utils.build_query(
76
85
  :type => 'SAMLResponse',
@@ -94,46 +103,60 @@ module OneLogin
94
103
  # @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
95
104
  # @param request_id [String] The ID of the LogoutRequest sent by this SP to the IdP. That ID will be placed as the InResponseTo in the logout response
96
105
  # @param logout_message [String] The Message to be placed as StatusMessage in the logout response
106
+ # @param logout_status_code [String] The StatusCode to be placed as StatusMessage in the logout response
97
107
  # @return [String] The SAMLResponse String.
98
108
  #
99
- def create_logout_response_xml_doc(settings, request_id = nil, logout_message = nil)
109
+ def create_logout_response_xml_doc(settings, request_id = nil, logout_message = nil, logout_status_code = nil)
110
+ document = create_xml_document(settings, request_id, logout_message, logout_status_code)
111
+ sign_document(document, settings)
112
+ end
113
+
114
+ def create_xml_document(settings, request_id = nil, logout_message = nil, status_code = nil)
100
115
  time = Time.now.utc.strftime('%Y-%m-%dT%H:%M:%SZ')
101
116
 
102
117
  response_doc = XMLSecurity::Document.new
103
118
  response_doc.uuid = uuid
104
119
 
120
+ destination = settings.idp_slo_response_service_url || settings.idp_slo_service_url
121
+
122
+
105
123
  root = response_doc.add_element 'samlp:LogoutResponse', { 'xmlns:samlp' => 'urn:oasis:names:tc:SAML:2.0:protocol', "xmlns:saml" => "urn:oasis:names:tc:SAML:2.0:assertion" }
106
124
  root.attributes['ID'] = uuid
107
125
  root.attributes['IssueInstant'] = time
108
126
  root.attributes['Version'] = '2.0'
109
127
  root.attributes['InResponseTo'] = request_id unless request_id.nil?
110
- root.attributes['Destination'] = settings.idp_slo_target_url unless settings.idp_slo_target_url.nil?
128
+ root.attributes['Destination'] = destination unless destination.nil? or destination.empty?
111
129
 
112
- if settings.issuer != nil
130
+ if settings.sp_entity_id != nil
113
131
  issuer = root.add_element "saml:Issuer"
114
- issuer.text = settings.issuer
132
+ issuer.text = settings.sp_entity_id
115
133
  end
116
134
 
117
- # add success message
135
+ # add status
118
136
  status = root.add_element 'samlp:Status'
119
137
 
120
- # success status code
121
- status_code = status.add_element 'samlp:StatusCode'
122
- status_code.attributes['Value'] = 'urn:oasis:names:tc:SAML:2.0:status:Success'
138
+ # status code
139
+ status_code ||= 'urn:oasis:names:tc:SAML:2.0:status:Success'
140
+ status_code_elem = status.add_element 'samlp:StatusCode'
141
+ status_code_elem.attributes['Value'] = status_code
123
142
 
124
- # success status message
143
+ # status message
125
144
  logout_message ||= 'Successfully Signed Out'
126
145
  status_message = status.add_element 'samlp:StatusMessage'
127
146
  status_message.text = logout_message
128
147
 
148
+ response_doc
149
+ end
150
+
151
+ def sign_document(document, settings)
129
152
  # embed signature
130
- if settings.security[:logout_responses_signed] && settings.private_key && settings.certificate && settings.security[:embed_sign]
153
+ if settings.idp_slo_service_binding == Utils::BINDINGS[:post] && settings.private_key && settings.certificate
131
154
  private_key = settings.get_sp_key
132
155
  cert = settings.get_sp_cert
133
- response_doc.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method])
156
+ document.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method])
134
157
  end
135
158
 
136
- response_doc
159
+ document
137
160
  end
138
161
 
139
162
  end