ruby-saml 1.11.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 (160) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +25 -0
  3. data/{changelog.md → CHANGELOG.md} +44 -1
  4. data/README.md +333 -217
  5. data/UPGRADING.md +149 -0
  6. data/lib/onelogin/ruby-saml/attributes.rb +24 -1
  7. data/lib/onelogin/ruby-saml/authrequest.rb +11 -7
  8. data/lib/onelogin/ruby-saml/idp_metadata_parser.rb +154 -83
  9. data/lib/onelogin/ruby-saml/logoutrequest.rb +12 -6
  10. data/lib/onelogin/ruby-saml/logoutresponse.rb +5 -1
  11. data/lib/onelogin/ruby-saml/metadata.rb +62 -17
  12. data/lib/onelogin/ruby-saml/response.rb +51 -31
  13. data/lib/onelogin/ruby-saml/saml_message.rb +8 -3
  14. data/lib/onelogin/ruby-saml/setting_error.rb +6 -0
  15. data/lib/onelogin/ruby-saml/settings.rb +89 -49
  16. data/lib/onelogin/ruby-saml/slo_logoutrequest.rb +16 -4
  17. data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +31 -17
  18. data/lib/onelogin/ruby-saml/utils.rb +63 -2
  19. data/lib/onelogin/ruby-saml/version.rb +1 -1
  20. data/lib/xml_security.rb +39 -13
  21. data/ruby-saml.gemspec +8 -4
  22. metadata +24 -282
  23. data/.travis.yml +0 -46
  24. data/test/certificates/certificate.der +0 -0
  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 -594
  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 -86
  53. data/test/logoutrequest_test.rb +0 -260
  54. data/test/logoutresponse_test.rb +0 -427
  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 -59
  64. data/test/metadata/idp_multiple_descriptors_2.xml +0 -59
  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 -340
  68. data/test/response_test.rb +0 -1629
  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 -338
  156. data/test/slo_logoutrequest_test.rb +0 -467
  157. data/test/slo_logoutresponse_test.rb +0 -233
  158. data/test/test_helper.rb +0 -333
  159. data/test/utils_test.rb +0 -259
  160. data/test/xml_security_test.rb +0 -421
@@ -0,0 +1,6 @@
1
+ module OneLogin
2
+ module RubySaml
3
+ class SettingError < StandardError
4
+ end
5
+ end
6
+ end
@@ -31,8 +31,9 @@ module OneLogin
31
31
 
32
32
  # IdP Data
33
33
  attr_accessor :idp_entity_id
34
- attr_accessor :idp_sso_target_url
35
- 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
36
37
  attr_accessor :idp_cert
37
38
  attr_accessor :idp_cert_fingerprint
38
39
  attr_accessor :idp_cert_fingerprint_algorithm
@@ -41,8 +42,10 @@ module OneLogin
41
42
  attr_accessor :idp_name_qualifier
42
43
  attr_accessor :valid_until
43
44
  # SP Data
45
+ attr_writer :sp_entity_id
44
46
  attr_accessor :assertion_consumer_service_url
45
- attr_accessor :assertion_consumer_service_binding
47
+ attr_reader :assertion_consumer_service_binding
48
+ attr_writer :single_logout_service_url
46
49
  attr_accessor :sp_name_qualifier
47
50
  attr_accessor :name_identifier_format
48
51
  attr_accessor :name_identifier_value
@@ -51,8 +54,9 @@ module OneLogin
51
54
  attr_accessor :compress_request
52
55
  attr_accessor :compress_response
53
56
  attr_accessor :double_quote_xml_attribute_values
57
+ attr_accessor :message_max_bytesize
54
58
  attr_accessor :passive
55
- attr_accessor :protocol_binding
59
+ attr_reader :protocol_binding
56
60
  attr_accessor :attributes_index
57
61
  attr_accessor :force_authn
58
62
  attr_accessor :certificate
@@ -65,74 +69,99 @@ module OneLogin
65
69
  # Work-flow
66
70
  attr_accessor :security
67
71
  attr_accessor :soft
68
- # Compability
72
+ # Deprecated
69
73
  attr_accessor :assertion_consumer_logout_service_url
70
- attr_accessor :assertion_consumer_logout_service_binding
74
+ attr_reader :assertion_consumer_logout_service_binding
71
75
  attr_accessor :issuer
76
+ attr_accessor :idp_sso_target_url
77
+ attr_accessor :idp_slo_target_url
78
+
79
+ # @return [String] IdP Single Sign On Service URL
80
+ #
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
72
116
 
73
117
  # @return [String] SP Entity ID
74
118
  #
75
119
  def sp_entity_id
76
- val = nil
77
- if @sp_entity_id.nil?
78
- if @issuer
79
- val = @issuer
80
- end
81
- else
82
- val = @sp_entity_id
83
- end
84
- val
120
+ @sp_entity_id || @issuer
85
121
  end
86
122
 
87
- # Setter for SP Entity ID.
88
- # @param val [String].
123
+ # Setter for SP Protocol Binding
124
+ # @param value [String, Symbol].
89
125
  #
90
- def sp_entity_id=(val)
91
- @sp_entity_id = val
126
+ def protocol_binding=(value)
127
+ @protocol_binding = get_binding(value)
92
128
  end
93
129
 
94
- # @return [String] Single Logout Service URL.
130
+ # Setter for SP Assertion Consumer Service Binding
131
+ # @param value [String, Symbol].
95
132
  #
96
- def single_logout_service_url
97
- val = nil
98
- if @single_logout_service_url.nil?
99
- if @assertion_consumer_logout_service_url
100
- val = @assertion_consumer_logout_service_url
101
- end
102
- else
103
- val = @single_logout_service_url
104
- end
105
- val
133
+ def assertion_consumer_service_binding=(value)
134
+ @assertion_consumer_service_binding = get_binding(value)
106
135
  end
107
136
 
108
- # Setter for the Single Logout Service URL.
109
- # @param url [String].
137
+ # @return [String] Single Logout Service URL.
110
138
  #
111
- def single_logout_service_url=(url)
112
- @single_logout_service_url = url
139
+ def single_logout_service_url
140
+ @single_logout_service_url || @assertion_consumer_logout_service_url
113
141
  end
114
142
 
115
143
  # @return [String] Single Logout Service Binding.
116
144
  #
117
145
  def single_logout_service_binding
118
- val = nil
119
- if @single_logout_service_binding.nil?
120
- if @assertion_consumer_logout_service_binding
121
- val = @assertion_consumer_logout_service_binding
122
- end
123
- else
124
- val = @single_logout_service_binding
125
- end
126
- val
146
+ @single_logout_service_binding || @assertion_consumer_logout_service_binding
127
147
  end
128
148
 
129
149
  # Setter for Single Logout Service Binding.
130
150
  #
131
151
  # (Currently we only support "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect")
132
- # @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]
133
162
  #
134
- def single_logout_service_binding=(url)
135
- @single_logout_service_binding = url
163
+ def assertion_consumer_logout_service_binding=(value)
164
+ @assertion_consumer_logout_service_binding = get_binding(value)
136
165
  end
137
166
 
138
167
  # Calculates the fingerprint of the IdP x509 certificate.
@@ -220,12 +249,23 @@ module OneLogin
220
249
 
221
250
  private
222
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
+
223
262
  DEFAULTS = {
224
- :assertion_consumer_service_binding => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST".freeze,
225
- :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],
226
265
  :idp_cert_fingerprint_algorithm => XMLSecurity::Document::SHA1,
227
266
  :compress_request => true,
228
267
  :compress_response => true,
268
+ :message_max_bytesize => 250000,
229
269
  :soft => true,
230
270
  :double_quote_xml_attribute_values => false,
231
271
  :security => {
@@ -236,7 +276,7 @@ module OneLogin
236
276
  :want_assertions_encrypted => false,
237
277
  :want_name_id => false,
238
278
  :metadata_signed => false,
239
- :embed_sign => false,
279
+ :embed_sign => false, # Deprecated
240
280
  :digest_method => XMLSecurity::Document::SHA1,
241
281
  :signature_method => XMLSecurity::Document::RSA_SHA1,
242
282
  :check_idp_cert_expiration => false,
@@ -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
@@ -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,39 +103,44 @@ 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)
100
- document = create_xml_document(settings, request_id, logout_message)
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)
101
111
  sign_document(document, settings)
102
112
  end
103
113
 
104
- def create_xml_document(settings, request_id = nil, logout_message = nil)
114
+ def create_xml_document(settings, request_id = nil, logout_message = nil, status_code = nil)
105
115
  time = Time.now.utc.strftime('%Y-%m-%dT%H:%M:%SZ')
106
116
 
107
117
  response_doc = XMLSecurity::Document.new
108
118
  response_doc.uuid = uuid
109
119
 
120
+ destination = settings.idp_slo_response_service_url || settings.idp_slo_service_url
121
+
122
+
110
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" }
111
124
  root.attributes['ID'] = uuid
112
125
  root.attributes['IssueInstant'] = time
113
126
  root.attributes['Version'] = '2.0'
114
127
  root.attributes['InResponseTo'] = request_id unless request_id.nil?
115
- 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?
116
129
 
117
130
  if settings.sp_entity_id != nil
118
131
  issuer = root.add_element "saml:Issuer"
119
132
  issuer.text = settings.sp_entity_id
120
133
  end
121
134
 
122
- # add success message
135
+ # add status
123
136
  status = root.add_element 'samlp:Status'
124
137
 
125
- # success status code
126
- status_code = status.add_element 'samlp:StatusCode'
127
- 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
128
142
 
129
- # success status message
143
+ # status message
130
144
  logout_message ||= 'Successfully Signed Out'
131
145
  status_message = status.add_element 'samlp:StatusMessage'
132
146
  status_message.text = logout_message
@@ -136,7 +150,7 @@ module OneLogin
136
150
 
137
151
  def sign_document(document, settings)
138
152
  # embed signature
139
- 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
140
154
  private_key = settings.get_sp_key
141
155
  cert = settings.get_sp_cert
142
156
  document.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method])
@@ -13,8 +13,25 @@ module OneLogin
13
13
  class Utils
14
14
  @@uuid_generator = UUID.new if RUBY_VERSION < '1.9'
15
15
 
16
- DSIG = "http://www.w3.org/2000/09/xmldsig#"
17
- XENC = "http://www.w3.org/2001/04/xmlenc#"
16
+ BINDINGS = { :post => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST".freeze,
17
+ :redirect => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect".freeze }.freeze
18
+ DSIG = "http://www.w3.org/2000/09/xmldsig#".freeze
19
+ XENC = "http://www.w3.org/2001/04/xmlenc#".freeze
20
+ DURATION_FORMAT = %r(^
21
+ (-?)P # 1: Duration sign
22
+ (?:
23
+ (?:(\d+)Y)? # 2: Years
24
+ (?:(\d+)M)? # 3: Months
25
+ (?:(\d+)D)? # 4: Days
26
+ (?:T
27
+ (?:(\d+)H)? # 5: Hours
28
+ (?:(\d+)M)? # 6: Minutes
29
+ (?:(\d+(?:[.,]\d+)?)S)? # 7: Seconds
30
+ )?
31
+ |
32
+ (\d+)W # 8: Weeks
33
+ )
34
+ $)x.freeze
18
35
 
19
36
  # Checks if the x509 cert provided is expired
20
37
  #
@@ -28,6 +45,37 @@ module OneLogin
28
45
  return cert.not_after < Time.now
29
46
  end
30
47
 
48
+ # Interprets a ISO8601 duration value relative to a given timestamp.
49
+ #
50
+ # @param duration [String] The duration, as a string.
51
+ # @param timestamp [Integer] The unix timestamp we should apply the
52
+ # duration to. Optional, default to the
53
+ # current time.
54
+ #
55
+ # @return [Integer] The new timestamp, after the duration is applied.
56
+ #
57
+ def self.parse_duration(duration, timestamp=Time.now.utc)
58
+ return nil if RUBY_VERSION < '1.9' # 1.8.7 not supported
59
+
60
+ matches = duration.match(DURATION_FORMAT)
61
+
62
+ if matches.nil?
63
+ raise Exception.new("Invalid ISO 8601 duration")
64
+ end
65
+
66
+ sign = matches[1] == '-' ? -1 : 1
67
+
68
+ durYears, durMonths, durDays, durHours, durMinutes, durSeconds, durWeeks =
69
+ matches[2..8].map { |match| match ? sign * match.tr(',', '.').to_f : 0.0 }
70
+
71
+ initial_datetime = Time.at(timestamp).utc.to_datetime
72
+ final_datetime = initial_datetime.next_year(durYears)
73
+ final_datetime = final_datetime.next_month(durMonths)
74
+ final_datetime = final_datetime.next_day((7*durWeeks) + durDays)
75
+ final_timestamp = final_datetime.to_time.utc.to_i + (durHours * 3600) + (durMinutes * 60) + durSeconds
76
+ return final_timestamp
77
+ end
78
+
31
79
  # Return a properly formatted x509 certificate
32
80
  #
33
81
  # @param cert [String] The original certificate
@@ -253,6 +301,9 @@ module OneLogin
253
301
  when 'http://www.w3.org/2001/04/xmlenc#aes128-cbc' then cipher = OpenSSL::Cipher.new('AES-128-CBC').decrypt
254
302
  when 'http://www.w3.org/2001/04/xmlenc#aes192-cbc' then cipher = OpenSSL::Cipher.new('AES-192-CBC').decrypt
255
303
  when 'http://www.w3.org/2001/04/xmlenc#aes256-cbc' then cipher = OpenSSL::Cipher.new('AES-256-CBC').decrypt
304
+ when 'http://www.w3.org/2009/xmlenc11#aes128-gcm' then auth_cipher = OpenSSL::Cipher::AES.new(128, :GCM).decrypt
305
+ when 'http://www.w3.org/2009/xmlenc11#aes192-gcm' then auth_cipher = OpenSSL::Cipher::AES.new(192, :GCM).decrypt
306
+ when 'http://www.w3.org/2009/xmlenc11#aes256-gcm' then auth_cipher = OpenSSL::Cipher::AES.new(256, :GCM).decrypt
256
307
  when 'http://www.w3.org/2001/04/xmlenc#rsa-1_5' then rsa = symmetric_key
257
308
  when 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p' then oaep = symmetric_key
258
309
  end
@@ -263,6 +314,16 @@ module OneLogin
263
314
  cipher.padding, cipher.key, cipher.iv = 0, symmetric_key, cipher_text[0..iv_len-1]
264
315
  assertion_plaintext = cipher.update(data)
265
316
  assertion_plaintext << cipher.final
317
+ elsif auth_cipher
318
+ iv_len, text_len, tag_len = auth_cipher.iv_len, cipher_text.length, 16
319
+ data = cipher_text[iv_len..text_len-1-tag_len]
320
+ auth_cipher.padding = 0
321
+ auth_cipher.key = symmetric_key
322
+ auth_cipher.iv = cipher_text[0..iv_len-1]
323
+ auth_cipher.auth_data = ''
324
+ auth_cipher.auth_tag = cipher_text[text_len-tag_len..-1]
325
+ assertion_plaintext = auth_cipher.update(data)
326
+ assertion_plaintext << auth_cipher.final
266
327
  elsif rsa
267
328
  rsa.private_decrypt(cipher_text)
268
329
  elsif oaep