ruby-saml 1.9.0 → 1.14.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 (159) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/test.yml +25 -0
  3. data/{changelog.md → CHANGELOG.md} +64 -1
  4. data/README.md +394 -211
  5. data/UPGRADING.md +149 -0
  6. data/lib/onelogin/ruby-saml/attributes.rb +24 -1
  7. data/lib/onelogin/ruby-saml/authrequest.rb +26 -10
  8. data/lib/onelogin/ruby-saml/idp_metadata_parser.rb +285 -184
  9. data/lib/onelogin/ruby-saml/logging.rb +3 -3
  10. data/lib/onelogin/ruby-saml/logoutrequest.rb +26 -11
  11. data/lib/onelogin/ruby-saml/logoutresponse.rb +27 -11
  12. data/lib/onelogin/ruby-saml/metadata.rb +62 -17
  13. data/lib/onelogin/ruby-saml/response.rb +86 -37
  14. data/lib/onelogin/ruby-saml/saml_message.rb +14 -5
  15. data/lib/onelogin/ruby-saml/setting_error.rb +6 -0
  16. data/lib/onelogin/ruby-saml/settings.rb +117 -41
  17. data/lib/onelogin/ruby-saml/slo_logoutrequest.rb +33 -31
  18. data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +43 -20
  19. data/lib/onelogin/ruby-saml/utils.rb +101 -9
  20. data/lib/onelogin/ruby-saml/version.rb +1 -1
  21. data/lib/xml_security.rb +39 -13
  22. data/ruby-saml.gemspec +21 -8
  23. metadata +43 -284
  24. data/.travis.yml +0 -32
  25. data/test/certificates/certificate1 +0 -12
  26. data/test/certificates/certificate_without_head_foot +0 -1
  27. data/test/certificates/formatted_certificate +0 -14
  28. data/test/certificates/formatted_chained_certificate +0 -42
  29. data/test/certificates/formatted_private_key +0 -12
  30. data/test/certificates/formatted_rsa_private_key +0 -12
  31. data/test/certificates/invalid_certificate1 +0 -1
  32. data/test/certificates/invalid_certificate2 +0 -1
  33. data/test/certificates/invalid_certificate3 +0 -12
  34. data/test/certificates/invalid_chained_certificate1 +0 -1
  35. data/test/certificates/invalid_private_key1 +0 -1
  36. data/test/certificates/invalid_private_key2 +0 -1
  37. data/test/certificates/invalid_private_key3 +0 -10
  38. data/test/certificates/invalid_rsa_private_key1 +0 -1
  39. data/test/certificates/invalid_rsa_private_key2 +0 -1
  40. data/test/certificates/invalid_rsa_private_key3 +0 -10
  41. data/test/certificates/ruby-saml-2.crt +0 -15
  42. data/test/certificates/ruby-saml.crt +0 -14
  43. data/test/certificates/ruby-saml.key +0 -15
  44. data/test/idp_metadata_parser_test.rb +0 -579
  45. data/test/logging_test.rb +0 -62
  46. data/test/logout_requests/invalid_slo_request.xml +0 -6
  47. data/test/logout_requests/slo_request.xml +0 -4
  48. data/test/logout_requests/slo_request.xml.base64 +0 -1
  49. data/test/logout_requests/slo_request_deflated.xml.base64 +0 -1
  50. data/test/logout_requests/slo_request_with_name_id_format.xml +0 -4
  51. data/test/logout_requests/slo_request_with_session_index.xml +0 -5
  52. data/test/logout_responses/logoutresponse_fixtures.rb +0 -67
  53. data/test/logoutrequest_test.rb +0 -226
  54. data/test/logoutresponse_test.rb +0 -402
  55. data/test/metadata/idp_descriptor.xml +0 -26
  56. data/test/metadata/idp_descriptor_2.xml +0 -56
  57. data/test/metadata/idp_descriptor_3.xml +0 -14
  58. data/test/metadata/idp_descriptor_4.xml +0 -72
  59. data/test/metadata/idp_metadata_different_sign_and_encrypt_cert.xml +0 -72
  60. data/test/metadata/idp_metadata_multi_certs.xml +0 -75
  61. data/test/metadata/idp_metadata_multi_signing_certs.xml +0 -52
  62. data/test/metadata/idp_metadata_same_sign_and_encrypt_cert.xml +0 -71
  63. data/test/metadata/idp_multiple_descriptors.xml +0 -53
  64. data/test/metadata/no_idp_descriptor.xml +0 -21
  65. data/test/metadata_test.rb +0 -331
  66. data/test/request_test.rb +0 -323
  67. data/test/response_test.rb +0 -1619
  68. data/test/responses/adfs_response_sha1.xml +0 -46
  69. data/test/responses/adfs_response_sha256.xml +0 -46
  70. data/test/responses/adfs_response_sha384.xml +0 -46
  71. data/test/responses/adfs_response_sha512.xml +0 -46
  72. data/test/responses/adfs_response_xmlns.xml +0 -45
  73. data/test/responses/attackxee.xml +0 -13
  74. data/test/responses/invalids/duplicated_attributes.xml.base64 +0 -1
  75. data/test/responses/invalids/empty_destination.xml.base64 +0 -1
  76. data/test/responses/invalids/empty_nameid.xml.base64 +0 -1
  77. data/test/responses/invalids/encrypted_new_attack.xml.base64 +0 -1
  78. data/test/responses/invalids/invalid_audience.xml.base64 +0 -1
  79. data/test/responses/invalids/invalid_issuer_assertion.xml.base64 +0 -1
  80. data/test/responses/invalids/invalid_issuer_message.xml.base64 +0 -1
  81. data/test/responses/invalids/invalid_signature_position.xml.base64 +0 -1
  82. data/test/responses/invalids/invalid_subjectconfirmation_inresponse.xml.base64 +0 -1
  83. data/test/responses/invalids/invalid_subjectconfirmation_nb.xml.base64 +0 -1
  84. data/test/responses/invalids/invalid_subjectconfirmation_noa.xml.base64 +0 -1
  85. data/test/responses/invalids/invalid_subjectconfirmation_recipient.xml.base64 +0 -1
  86. data/test/responses/invalids/multiple_assertions.xml.base64 +0 -2
  87. data/test/responses/invalids/multiple_signed.xml.base64 +0 -1
  88. data/test/responses/invalids/no_authnstatement.xml.base64 +0 -1
  89. data/test/responses/invalids/no_conditions.xml.base64 +0 -1
  90. data/test/responses/invalids/no_id.xml.base64 +0 -1
  91. data/test/responses/invalids/no_issuer_assertion.xml.base64 +0 -1
  92. data/test/responses/invalids/no_issuer_response.xml.base64 +0 -1
  93. data/test/responses/invalids/no_nameid.xml.base64 +0 -1
  94. data/test/responses/invalids/no_saml2.xml.base64 +0 -1
  95. data/test/responses/invalids/no_signature.xml.base64 +0 -1
  96. data/test/responses/invalids/no_status.xml.base64 +0 -1
  97. data/test/responses/invalids/no_status_code.xml.base64 +0 -1
  98. data/test/responses/invalids/no_subjectconfirmation_data.xml.base64 +0 -1
  99. data/test/responses/invalids/no_subjectconfirmation_method.xml.base64 +0 -1
  100. data/test/responses/invalids/response_invalid_signed_element.xml.base64 +0 -1
  101. data/test/responses/invalids/response_with_concealed_signed_assertion.xml +0 -51
  102. data/test/responses/invalids/response_with_doubled_signed_assertion.xml +0 -49
  103. data/test/responses/invalids/signature_wrapping_attack.xml.base64 +0 -1
  104. data/test/responses/invalids/status_code_responder.xml.base64 +0 -1
  105. data/test/responses/invalids/status_code_responer_and_msg.xml.base64 +0 -1
  106. data/test/responses/invalids/wrong_spnamequalifier.xml.base64 +0 -1
  107. data/test/responses/no_signature_ns.xml +0 -48
  108. data/test/responses/open_saml_response.xml +0 -56
  109. data/test/responses/response_assertion_wrapped.xml.base64 +0 -93
  110. data/test/responses/response_audience_self_closed_tag.xml.base64 +0 -1
  111. data/test/responses/response_double_status_code.xml.base64 +0 -1
  112. data/test/responses/response_encrypted_attrs.xml.base64 +0 -1
  113. data/test/responses/response_encrypted_nameid.xml.base64 +0 -1
  114. data/test/responses/response_eval.xml +0 -7
  115. data/test/responses/response_no_cert_and_encrypted_attrs.xml +0 -29
  116. data/test/responses/response_node_text_attack.xml.base64 +0 -1
  117. data/test/responses/response_node_text_attack2.xml.base64 +0 -1
  118. data/test/responses/response_node_text_attack3.xml.base64 +0 -1
  119. data/test/responses/response_unsigned_xml_base64 +0 -1
  120. data/test/responses/response_with_ampersands.xml +0 -139
  121. data/test/responses/response_with_ampersands.xml.base64 +0 -93
  122. data/test/responses/response_with_ds_namespace_at_the_root.xml.base64 +0 -1
  123. data/test/responses/response_with_multiple_attribute_statements.xml +0 -72
  124. data/test/responses/response_with_multiple_attribute_values.xml +0 -67
  125. data/test/responses/response_with_retrieval_method.xml +0 -26
  126. data/test/responses/response_with_saml2_namespace.xml.base64 +0 -102
  127. data/test/responses/response_with_signed_assertion.xml.base64 +0 -66
  128. data/test/responses/response_with_signed_assertion_2.xml.base64 +0 -1
  129. data/test/responses/response_with_signed_assertion_3.xml +0 -30
  130. data/test/responses/response_with_signed_message_and_assertion.xml +0 -34
  131. data/test/responses/response_with_undefined_recipient.xml.base64 +0 -1
  132. data/test/responses/response_without_attributes.xml.base64 +0 -79
  133. data/test/responses/response_without_reference_uri.xml.base64 +0 -1
  134. data/test/responses/response_wrapped.xml.base64 +0 -150
  135. data/test/responses/signed_message_encrypted_signed_assertion.xml.base64 +0 -1
  136. data/test/responses/signed_message_encrypted_unsigned_assertion.xml.base64 +0 -1
  137. data/test/responses/signed_nameid_in_atts.xml +0 -47
  138. data/test/responses/signed_unqual_nameid_in_atts.xml +0 -47
  139. data/test/responses/simple_saml_php.xml +0 -71
  140. data/test/responses/starfield_response.xml.base64 +0 -1
  141. data/test/responses/test_sign.xml +0 -43
  142. data/test/responses/unsigned_encrypted_adfs.xml +0 -23
  143. data/test/responses/unsigned_message_aes128_encrypted_signed_assertion.xml.base64 +0 -1
  144. data/test/responses/unsigned_message_aes192_encrypted_signed_assertion.xml.base64 +0 -1
  145. data/test/responses/unsigned_message_aes256_encrypted_signed_assertion.xml.base64 +0 -1
  146. data/test/responses/unsigned_message_des192_encrypted_signed_assertion.xml.base64 +0 -1
  147. data/test/responses/unsigned_message_encrypted_assertion_without_saml_namespace.xml.base64 +0 -1
  148. data/test/responses/unsigned_message_encrypted_signed_assertion.xml.base64 +0 -1
  149. data/test/responses/unsigned_message_encrypted_unsigned_assertion.xml.base64 +0 -1
  150. data/test/responses/valid_response.xml.base64 +0 -1
  151. data/test/responses/valid_response_with_formatted_x509certificate.xml.base64 +0 -1
  152. data/test/responses/valid_response_without_x509certificate.xml.base64 +0 -1
  153. data/test/saml_message_test.rb +0 -56
  154. data/test/settings_test.rb +0 -329
  155. data/test/slo_logoutrequest_test.rb +0 -448
  156. data/test/slo_logoutresponse_test.rb +0 -199
  157. data/test/test_helper.rb +0 -327
  158. data/test/utils_test.rb +0 -254
  159. data/test/xml_security_test.rb +0 -421
@@ -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
@@ -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
82
121
  end
83
122
 
84
- # Setter for the Single Logout Service URL.
85
- # @param url [String].
123
+ # Setter for SP Protocol Binding
124
+ # @param value [String, Symbol].
86
125
  #
87
- def single_logout_service_url=(url)
88
- @single_logout_service_url = url
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)
135
+ end
136
+
137
+ # @return [String] Single Logout Service URL.
138
+ #
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.
@@ -165,7 +218,15 @@ module OneLogin
165
218
  return nil if certificate.nil? || certificate.empty?
166
219
 
167
220
  formatted_cert = OneLogin::RubySaml::Utils.format_cert(certificate)
168
- OpenSSL::X509::Certificate.new(formatted_cert)
221
+ cert = OpenSSL::X509::Certificate.new(formatted_cert)
222
+
223
+ if security[:check_sp_cert_expiration]
224
+ if OneLogin::RubySaml::Utils.is_cert_expired(cert)
225
+ raise OneLogin::RubySaml::ValidationError.new("The SP certificate expired.")
226
+ end
227
+ end
228
+
229
+ cert
169
230
  end
170
231
 
171
232
  # @return [OpenSSL::X509::Certificate|nil] Build the New SP certificate from the settings (previously format it)
@@ -188,13 +249,25 @@ module OneLogin
188
249
 
189
250
  private
190
251
 
252
+ def idp_binding_from_embed_sign
253
+ security[:embed_sign] ? Utils::BINDINGS[:post] : Utils::BINDINGS[:redirect]
254
+ end
255
+
256
+ def get_binding(value)
257
+ return unless value
258
+
259
+ Utils::BINDINGS[value.to_sym] || value
260
+ end
261
+
191
262
  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,
263
+ :assertion_consumer_service_binding => Utils::BINDINGS[:post],
264
+ :single_logout_service_binding => Utils::BINDINGS[:redirect],
194
265
  :idp_cert_fingerprint_algorithm => XMLSecurity::Document::SHA1,
195
266
  :compress_request => true,
196
267
  :compress_response => true,
268
+ :message_max_bytesize => 250000,
197
269
  :soft => true,
270
+ :double_quote_xml_attribute_values => false,
198
271
  :security => {
199
272
  :authn_requests_signed => false,
200
273
  :logout_requests_signed => false,
@@ -203,11 +276,14 @@ module OneLogin
203
276
  :want_assertions_encrypted => false,
204
277
  :want_name_id => false,
205
278
  :metadata_signed => false,
206
- :embed_sign => false,
279
+ :embed_sign => false, # Deprecated
207
280
  :digest_method => XMLSecurity::Document::SHA1,
208
- :signature_method => XMLSecurity::Document::RSA_SHA1
209
- }.freeze,
210
- :double_quote_xml_attribute_values => false,
281
+ :signature_method => XMLSecurity::Document::RSA_SHA1,
282
+ :check_idp_cert_expiration => false,
283
+ :check_sp_cert_expiration => false,
284
+ :strict_audience_validation => false,
285
+ :lowercase_url_encoding => false
286
+ }.freeze
211
287
  }.freeze
212
288
  end
213
289
  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
44
  response_params << "&#{key.to_s}=#{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