ruby-saml 1.8.0 → 1.13.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 +4 -4
  2. data/.github/workflows/test.yml +25 -0
  3. data/{changelog.md → CHANGELOG.md} +66 -1
  4. data/README.md +365 -209
  5. data/UPGRADING.md +149 -0
  6. data/lib/onelogin/ruby-saml/attribute_service.rb +1 -1
  7. data/lib/onelogin/ruby-saml/attributes.rb +24 -1
  8. data/lib/onelogin/ruby-saml/authrequest.rb +25 -9
  9. data/lib/onelogin/ruby-saml/idp_metadata_parser.rb +285 -184
  10. data/lib/onelogin/ruby-saml/logging.rb +4 -1
  11. data/lib/onelogin/ruby-saml/logoutrequest.rb +25 -10
  12. data/lib/onelogin/ruby-saml/logoutresponse.rb +33 -17
  13. data/lib/onelogin/ruby-saml/metadata.rb +62 -17
  14. data/lib/onelogin/ruby-saml/response.rb +89 -45
  15. data/lib/onelogin/ruby-saml/saml_message.rb +17 -8
  16. data/lib/onelogin/ruby-saml/setting_error.rb +6 -0
  17. data/lib/onelogin/ruby-saml/settings.rb +124 -43
  18. data/lib/onelogin/ruby-saml/slo_logoutrequest.rb +38 -11
  19. data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +42 -19
  20. data/lib/onelogin/ruby-saml/utils.rb +94 -12
  21. data/lib/onelogin/ruby-saml/version.rb +1 -1
  22. data/lib/xml_security.rb +44 -19
  23. data/ruby-saml.gemspec +16 -8
  24. metadata +44 -282
  25. data/.travis.yml +0 -26
  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 -1586
  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_without_x509certificate.xml.base64 +0 -1
  153. data/test/saml_message_test.rb +0 -56
  154. data/test/settings_test.rb +0 -301
  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
@@ -16,8 +16,8 @@ module OneLogin
16
16
  class SamlMessage
17
17
  include REXML
18
18
 
19
- ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion"
20
- PROTOCOL = "urn:oasis:names:tc:SAML:2.0:protocol"
19
+ ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion".freeze
20
+ PROTOCOL = "urn:oasis:names:tc:SAML:2.0:protocol".freeze
21
21
 
22
22
  BASE64_FORMAT = %r(\A([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?\Z)
23
23
  @@mutex = Mutex.new
@@ -62,7 +62,7 @@ module OneLogin
62
62
  # @param document [REXML::Document] The message that will be validated
63
63
  # @param soft [Boolean] soft Enable or Disable the soft mode (In order to raise exceptions when the message is invalid or not)
64
64
  # @return [Boolean] True if the XML is valid, otherwise False, if soft=True
65
- # @raise [ValidationError] if soft == false and validation fails
65
+ # @raise [ValidationError] if soft == false and validation fails
66
66
  #
67
67
  def valid_saml?(document, soft = true)
68
68
  begin
@@ -74,9 +74,9 @@ module OneLogin
74
74
  raise ValidationError.new("XML load failed: #{error.message}")
75
75
  end
76
76
 
77
- SamlMessage.schema.validate(xml).map do |error|
77
+ SamlMessage.schema.validate(xml).map do |schema_error|
78
78
  return false if soft
79
- raise ValidationError.new("#{error.message}\n\n#{xml.to_s}")
79
+ raise ValidationError.new("#{schema_error.message}\n\n#{xml.to_s}")
80
80
  end
81
81
  end
82
82
 
@@ -86,9 +86,14 @@ module OneLogin
86
86
  # @param saml [String] The deflated and encoded SAML Message
87
87
  # @return [String] The plain SAML Message
88
88
  #
89
- def decode_raw_saml(saml)
89
+ def decode_raw_saml(saml, settings = nil)
90
90
  return saml unless base64_encoded?(saml)
91
91
 
92
+ settings = OneLogin::RubySaml::Settings.new if settings.nil?
93
+ if saml.bytesize > settings.message_max_bytesize
94
+ raise ValidationError.new("Encoded SAML Message exceeds " + settings.message_max_bytesize.to_s + " bytes, so was rejected")
95
+ end
96
+
92
97
  decoded = decode(saml)
93
98
  begin
94
99
  inflate(decoded)
@@ -105,7 +110,7 @@ module OneLogin
105
110
  def encode_raw_saml(saml, settings)
106
111
  saml = deflate(saml) if settings.compress_request
107
112
 
108
- CGI.escape(Base64.encode64(saml))
113
+ CGI.escape(encode(saml))
109
114
  end
110
115
 
111
116
  # Base 64 decode method
@@ -121,7 +126,11 @@ module OneLogin
121
126
  # @return [String] The encoded string
122
127
  #
123
128
  def encode(string)
124
- Base64.encode64(string).gsub(/\n/, "")
129
+ if Base64.respond_to?('strict_encode64')
130
+ Base64.strict_encode64(string)
131
+ else
132
+ Base64.encode64(string).gsub(/\n/, "")
133
+ end
125
134
  end
126
135
 
127
136
  # 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
@@ -9,8 +10,15 @@ module OneLogin
9
10
  # SAML2 Toolkit Settings
10
11
  #
11
12
  class Settings
12
- def initialize(overrides = {})
13
- config = DEFAULTS.merge(overrides)
13
+ def initialize(overrides = {}, keep_security_attributes = false)
14
+ if keep_security_attributes
15
+ security_attributes = overrides.delete(:security) || {}
16
+ config = DEFAULTS.merge(overrides)
17
+ config[:security] = DEFAULTS[:security].merge(security_attributes)
18
+ else
19
+ config = DEFAULTS.merge(overrides)
20
+ end
21
+
14
22
  config.each do |k,v|
15
23
  acc = "#{k.to_s}=".to_sym
16
24
  if respond_to? acc
@@ -23,27 +31,32 @@ module OneLogin
23
31
 
24
32
  # IdP Data
25
33
  attr_accessor :idp_entity_id
26
- attr_accessor :idp_sso_target_url
27
- 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
28
37
  attr_accessor :idp_cert
29
38
  attr_accessor :idp_cert_fingerprint
30
39
  attr_accessor :idp_cert_fingerprint_algorithm
31
40
  attr_accessor :idp_cert_multi
32
41
  attr_accessor :idp_attribute_names
33
42
  attr_accessor :idp_name_qualifier
43
+ attr_accessor :valid_until
34
44
  # SP Data
35
- attr_accessor :issuer
45
+ attr_writer :sp_entity_id
36
46
  attr_accessor :assertion_consumer_service_url
37
- attr_accessor :assertion_consumer_service_binding
47
+ attr_reader :assertion_consumer_service_binding
48
+ attr_writer :single_logout_service_url
38
49
  attr_accessor :sp_name_qualifier
39
50
  attr_accessor :name_identifier_format
40
51
  attr_accessor :name_identifier_value
52
+ attr_accessor :name_identifier_value_requested
41
53
  attr_accessor :sessionindex
42
54
  attr_accessor :compress_request
43
55
  attr_accessor :compress_response
44
56
  attr_accessor :double_quote_xml_attribute_values
57
+ attr_accessor :message_max_bytesize
45
58
  attr_accessor :passive
46
- attr_accessor :protocol_binding
59
+ attr_reader :protocol_binding
47
60
  attr_accessor :attributes_index
48
61
  attr_accessor :force_authn
49
62
  attr_accessor :certificate
@@ -56,52 +69,99 @@ module OneLogin
56
69
  # Work-flow
57
70
  attr_accessor :security
58
71
  attr_accessor :soft
59
- # Compability
72
+ # Deprecated
60
73
  attr_accessor :assertion_consumer_logout_service_url
61
- 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
62
78
 
63
- # @return [String] Single Logout Service URL.
79
+ # @return [String] IdP Single Sign On Service URL
64
80
  #
65
- def single_logout_service_url
66
- val = nil
67
- if @single_logout_service_url.nil?
68
- if @assertion_consumer_logout_service_url
69
- val = @assertion_consumer_logout_service_url
70
- end
71
- else
72
- val = @single_logout_service_url
73
- end
74
- 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)
75
128
  end
76
129
 
77
- # Setter for the Single Logout Service URL.
78
- # @param url [String].
130
+ # Setter for SP Assertion Consumer Service Binding
131
+ # @param value [String, Symbol].
79
132
  #
80
- def single_logout_service_url=(url)
81
- @single_logout_service_url = url
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
82
141
  end
83
142
 
84
143
  # @return [String] Single Logout Service Binding.
85
144
  #
86
145
  def single_logout_service_binding
87
- val = nil
88
- if @single_logout_service_binding.nil?
89
- if @assertion_consumer_logout_service_binding
90
- val = @assertion_consumer_logout_service_binding
91
- end
92
- else
93
- val = @single_logout_service_binding
94
- end
95
- val
146
+ @single_logout_service_binding || @assertion_consumer_logout_service_binding
96
147
  end
97
148
 
98
149
  # Setter for Single Logout Service Binding.
99
150
  #
100
151
  # (Currently we only support "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect")
101
- # @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]
102
162
  #
103
- def single_logout_service_binding=(url)
104
- @single_logout_service_binding = url
163
+ def assertion_consumer_logout_service_binding=(value)
164
+ @assertion_consumer_logout_service_binding = get_binding(value)
105
165
  end
106
166
 
107
167
  # Calculates the fingerprint of the IdP x509 certificate.
@@ -158,7 +218,15 @@ module OneLogin
158
218
  return nil if certificate.nil? || certificate.empty?
159
219
 
160
220
  formatted_cert = OneLogin::RubySaml::Utils.format_cert(certificate)
161
- 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
162
230
  end
163
231
 
164
232
  # @return [OpenSSL::X509::Certificate|nil] Build the New SP certificate from the settings (previously format it)
@@ -181,13 +249,25 @@ module OneLogin
181
249
 
182
250
  private
183
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
+
184
262
  DEFAULTS = {
185
- :assertion_consumer_service_binding => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST".freeze,
186
- :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],
187
265
  :idp_cert_fingerprint_algorithm => XMLSecurity::Document::SHA1,
188
266
  :compress_request => true,
189
267
  :compress_response => true,
268
+ :message_max_bytesize => 250000,
190
269
  :soft => true,
270
+ :double_quote_xml_attribute_values => false,
191
271
  :security => {
192
272
  :authn_requests_signed => false,
193
273
  :logout_requests_signed => false,
@@ -196,11 +276,12 @@ module OneLogin
196
276
  :want_assertions_encrypted => false,
197
277
  :want_name_id => false,
198
278
  :metadata_signed => false,
199
- :embed_sign => false,
279
+ :embed_sign => false, # Deprecated
200
280
  :digest_method => XMLSecurity::Document::SHA1,
201
- :signature_method => XMLSecurity::Document::RSA_SHA1
202
- }.freeze,
203
- :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
+ }.freeze
204
285
  }.freeze
205
286
  end
206
287
  end
@@ -24,9 +24,9 @@ module OneLogin
24
24
 
25
25
  # Constructs the Logout Request. A Logout Request Object that is an extension of the SamlMessage class.
26
26
  # @param request [String] A UUEncoded Logout Request from the IdP.
27
- # @param options [Hash] :settings to provide the OneLogin::RubySaml::Settings object
27
+ # @param options [Hash] :settings to provide the OneLogin::RubySaml::Settings object
28
28
  # Or :allowed_clock_drift for the logout request validation process to allow a clock drift when checking dates with
29
- # Or :relax_signature_validation to accept signatures if no idp certificate registered on settings
29
+ # Or :relax_signature_validation to accept signatures if no idp certificate registered on settings
30
30
  #
31
31
  # @raise [ArgumentError] If Request is nil
32
32
  #
@@ -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
@@ -192,7 +204,7 @@ module OneLogin
192
204
 
193
205
  # Validates the Logout Request against the specified schema.
194
206
  # @return [Boolean] True if the XML is valid, otherwise False if soft=True
195
- # @raise [ValidationError] if soft == false and validation fails
207
+ # @raise [ValidationError] if soft == false and validation fails
196
208
  #
197
209
  def validate_structure
198
210
  unless valid_saml?(document, soft)
@@ -202,7 +214,7 @@ module OneLogin
202
214
  true
203
215
  end
204
216
 
205
- # Validates that the Logout Request provided in the initialization is not empty,
217
+ # Validates that the Logout Request provided in the initialization is not empty,
206
218
  # @return [Boolean] True if the required info is found, otherwise False if soft=True
207
219
  # @raise [ValidationError] if soft == false and validation fails
208
220
  #
@@ -280,28 +292,43 @@ module OneLogin
280
292
  :raw_sig_alg => options[:raw_get_params]['SigAlg']
281
293
  )
282
294
 
295
+ expired = false
283
296
  if idp_certs.nil? || idp_certs[:signing].empty?
284
297
  valid = OneLogin::RubySaml::Utils.verify_signature(
285
- :cert => settings.get_idp_cert,
298
+ :cert => idp_cert,
286
299
  :sig_alg => options[:get_params]['SigAlg'],
287
300
  :signature => options[:get_params]['Signature'],
288
301
  :query_string => query_string
289
302
  )
303
+ if valid && settings.security[:check_idp_cert_expiration]
304
+ if OneLogin::RubySaml::Utils.is_cert_expired(idp_cert)
305
+ expired = true
306
+ end
307
+ end
290
308
  else
291
309
  valid = false
292
- idp_certs[:signing].each do |idp_cert|
310
+ idp_certs[:signing].each do |signing_idp_cert|
293
311
  valid = OneLogin::RubySaml::Utils.verify_signature(
294
- :cert => idp_cert,
312
+ :cert => signing_idp_cert,
295
313
  :sig_alg => options[:get_params]['SigAlg'],
296
314
  :signature => options[:get_params]['Signature'],
297
315
  :query_string => query_string
298
316
  )
299
317
  if valid
318
+ if settings.security[:check_idp_cert_expiration]
319
+ if OneLogin::RubySaml::Utils.is_cert_expired(signing_idp_cert)
320
+ expired = true
321
+ end
322
+ end
300
323
  break
301
324
  end
302
325
  end
303
326
  end
304
327
 
328
+ if expired
329
+ error_msg = "IdP x509 certificate expired"
330
+ return append_error(error_msg)
331
+ end
305
332
  unless valid
306
333
  return append_error("Invalid Signature on Logout Request")
307
334
  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
@@ -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