ruby-saml 1.12.1 → 1.13.0

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

Potentially problematic release.


This version of ruby-saml might be problematic. Click here for more details.

@@ -21,21 +21,50 @@ module OneLogin
21
21
  #
22
22
  def generate(settings, pretty_print=false, valid_until=nil, cache_duration=nil)
23
23
  meta_doc = XMLSecurity::Document.new
24
+ add_xml_declaration(meta_doc)
25
+ root = add_root_element(meta_doc, settings, valid_until, cache_duration)
26
+ sp_sso = add_sp_sso_element(root, settings)
27
+ add_sp_certificates(sp_sso, settings)
28
+ add_sp_service_elements(sp_sso, settings)
29
+ add_extras(root, settings)
30
+ embed_signature(meta_doc, settings)
31
+ output_xml(meta_doc, pretty_print)
32
+ end
33
+
34
+ protected
35
+
36
+ def add_xml_declaration(meta_doc)
37
+ meta_doc << REXML::XMLDecl.new('1.0', 'UTF-8')
38
+ end
39
+
40
+ def add_root_element(meta_doc, settings, valid_until, cache_duration)
24
41
  namespaces = {
25
42
  "xmlns:md" => "urn:oasis:names:tc:SAML:2.0:metadata"
26
43
  }
44
+
27
45
  if settings.attribute_consuming_service.configured?
28
46
  namespaces["xmlns:saml"] = "urn:oasis:names:tc:SAML:2.0:assertion"
29
47
  end
30
- root = meta_doc.add_element "md:EntityDescriptor", namespaces
31
- sp_sso = root.add_element "md:SPSSODescriptor", {
48
+
49
+ root = meta_doc.add_element("md:EntityDescriptor", namespaces)
50
+ root.attributes["ID"] = OneLogin::RubySaml::Utils.uuid
51
+ root.attributes["entityID"] = settings.sp_entity_id if settings.sp_entity_id
52
+ root.attributes["validUntil"] = valid_until.strftime('%Y-%m-%dT%H:%M:%S%z') if valid_until
53
+ root.attributes["cacheDuration"] = "PT" + cache_duration.to_s + "S" if cache_duration
54
+ root
55
+ end
56
+
57
+ def add_sp_sso_element(root, settings)
58
+ root.add_element "md:SPSSODescriptor", {
32
59
  "protocolSupportEnumeration" => "urn:oasis:names:tc:SAML:2.0:protocol",
33
60
  "AuthnRequestsSigned" => settings.security[:authn_requests_signed],
34
61
  "WantAssertionsSigned" => settings.security[:want_assertions_signed],
35
62
  }
63
+ end
36
64
 
37
- # Add KeyDescriptor if messages will be signed / encrypted
38
- # with SP certificate, and new SP certificate if any
65
+ # Add KeyDescriptor if messages will be signed / encrypted
66
+ # with SP certificate, and new SP certificate if any
67
+ def add_sp_certificates(sp_sso, settings)
39
68
  cert = settings.get_sp_cert
40
69
  cert_new = settings.get_sp_cert_new
41
70
 
@@ -58,16 +87,10 @@ module OneLogin
58
87
  end
59
88
  end
60
89
 
61
- root.attributes["ID"] = OneLogin::RubySaml::Utils.uuid
62
- if settings.sp_entity_id
63
- root.attributes["entityID"] = settings.sp_entity_id
64
- end
65
- if valid_until
66
- root.attributes["validUntil"] = valid_until.strftime('%Y-%m-%dT%H:%M:%S%z')
67
- end
68
- if cache_duration
69
- root.attributes["cacheDuration"] = "PT" + cache_duration.to_s + "S"
70
- end
90
+ sp_sso
91
+ end
92
+
93
+ def add_sp_service_elements(sp_sso, settings)
71
94
  if settings.single_logout_service_url
72
95
  sp_sso.add_element "md:SingleLogoutService", {
73
96
  "Binding" => settings.single_logout_service_binding,
@@ -75,10 +98,12 @@ module OneLogin
75
98
  "ResponseLocation" => settings.single_logout_service_url
76
99
  }
77
100
  end
101
+
78
102
  if settings.name_identifier_format
79
103
  nameid = sp_sso.add_element "md:NameIDFormat"
80
104
  nameid.text = settings.name_identifier_format
81
105
  end
106
+
82
107
  if settings.assertion_consumer_service_url
83
108
  sp_sso.add_element "md:AssertionConsumerService", {
84
109
  "Binding" => settings.assertion_consumer_service_binding,
@@ -117,15 +142,27 @@ module OneLogin
117
142
  # <md:RoleDescriptor xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:query="urn:oasis:names:tc:SAML:metadata:ext:query" xsi:type="query:AttributeQueryDescriptorType" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"/>
118
143
  # <md:XACMLAuthzDecisionQueryDescriptor WantAssertionsSigned="false" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"/>
119
144
 
120
- meta_doc << REXML::XMLDecl.new("1.0", "UTF-8")
145
+ sp_sso
146
+ end
121
147
 
122
- # embed signature
123
- if settings.security[:metadata_signed] && settings.private_key && settings.certificate
124
- private_key = settings.get_sp_key
125
- meta_doc.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method])
126
- end
148
+ # can be overridden in subclass
149
+ def add_extras(root, _settings)
150
+ root
151
+ end
152
+
153
+ def embed_signature(meta_doc, settings)
154
+ return unless settings.security[:metadata_signed]
155
+
156
+ private_key = settings.get_sp_key
157
+ cert = settings.get_sp_cert
158
+ return unless private_key && cert
159
+
160
+ meta_doc.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method])
161
+ end
162
+
163
+ def output_xml(meta_doc, pretty_print)
164
+ ret = ''
127
165
 
128
- ret = ""
129
166
  # pretty print the XML so IdP administrators can easily see what the SP supports
130
167
  if pretty_print
131
168
  meta_doc.write(ret, 1)
@@ -133,7 +170,7 @@ module OneLogin
133
170
  ret = meta_doc.to_s
134
171
  end
135
172
 
136
- return ret
173
+ ret
137
174
  end
138
175
  end
139
176
  end
@@ -63,7 +63,7 @@ module OneLogin
63
63
  end
64
64
  end
65
65
 
66
- @response = decode_raw_saml(response)
66
+ @response = decode_raw_saml(response, settings)
67
67
  @document = XMLSecurity::SignedDocument.new(@response, @errors)
68
68
 
69
69
  if assertion_encrypted?
@@ -227,11 +227,10 @@ module OneLogin
227
227
  statuses = nodes.collect do |inner_node|
228
228
  inner_node.attributes["Value"]
229
229
  end
230
- extra_code = statuses.join(" | ")
231
- if extra_code
232
- code = "#{code} | #{extra_code}"
233
- end
230
+
231
+ code = [code, statuses].flatten.join(" | ")
234
232
  end
233
+
235
234
  code
236
235
  end
237
236
  end
@@ -338,9 +337,9 @@ module OneLogin
338
337
  end
339
338
 
340
339
  # returns the allowed clock drift on timing validation
341
- # @return [Integer]
340
+ # @return [Float]
342
341
  def allowed_clock_drift
343
- return options[:allowed_clock_drift].to_f
342
+ options[:allowed_clock_drift].to_f.abs + Float::EPSILON
344
343
  end
345
344
 
346
345
  # Checks if the SAML Response contains or not an EncryptedAssertion element
@@ -377,7 +376,6 @@ module OneLogin
377
376
  return false unless validate_response_state
378
377
 
379
378
  validations = [
380
- :validate_response_state,
381
379
  :validate_version,
382
380
  :validate_id,
383
381
  :validate_success_status,
@@ -694,13 +692,13 @@ module OneLogin
694
692
 
695
693
  now = Time.now.utc
696
694
 
697
- if not_before && (now_with_drift = now + allowed_clock_drift) < not_before
698
- error_msg = "Current time is earlier than NotBefore condition (#{now_with_drift} < #{not_before})"
695
+ if not_before && now < (not_before - allowed_clock_drift)
696
+ error_msg = "Current time is earlier than NotBefore condition (#{now} < #{not_before}#{" - #{allowed_clock_drift.ceil}s" if allowed_clock_drift > 0})"
699
697
  return append_error(error_msg)
700
698
  end
701
699
 
702
- if not_on_or_after && now >= (not_on_or_after_with_drift = not_on_or_after + allowed_clock_drift)
703
- error_msg = "Current time is on or after NotOnOrAfter condition (#{now} >= #{not_on_or_after_with_drift})"
700
+ if not_on_or_after && now >= (not_on_or_after + allowed_clock_drift)
701
+ error_msg = "Current time is on or after NotOnOrAfter condition (#{now} >= #{not_on_or_after}#{" + #{allowed_clock_drift.ceil}s" if allowed_clock_drift > 0})"
704
702
  return append_error(error_msg)
705
703
  end
706
704
 
@@ -742,7 +740,7 @@ module OneLogin
742
740
  return true if session_expires_at.nil?
743
741
 
744
742
  now = Time.now.utc
745
- unless (session_expires_at + allowed_clock_drift) > now
743
+ unless now < (session_expires_at + allowed_clock_drift)
746
744
  error_msg = "The attributes have expired, based on the SessionNotOnOrAfter of the AuthnStatement of this Response"
747
745
  return append_error(error_msg)
748
746
  end
@@ -780,8 +778,8 @@ module OneLogin
780
778
 
781
779
  attrs = confirmation_data_node.attributes
782
780
  next if (attrs.include? "InResponseTo" and attrs['InResponseTo'] != in_response_to) ||
783
- (attrs.include? "NotOnOrAfter" and (parse_time(confirmation_data_node, "NotOnOrAfter") + allowed_clock_drift) <= now) ||
784
- (attrs.include? "NotBefore" and parse_time(confirmation_data_node, "NotBefore") > (now + allowed_clock_drift)) ||
781
+ (attrs.include? "NotBefore" and now < (parse_time(confirmation_data_node, "NotBefore") - allowed_clock_drift)) ||
782
+ (attrs.include? "NotOnOrAfter" and now >= (parse_time(confirmation_data_node, "NotOnOrAfter") + allowed_clock_drift)) ||
785
783
  (attrs.include? "Recipient" and !options[:skip_recipient_check] and settings and attrs['Recipient'] != settings.assertion_consumer_service_url)
786
784
 
787
785
  valid_subject_confirmation = true
@@ -16,14 +16,12 @@ 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
24
24
 
25
- MAX_BYTE_SIZE = 250000
26
-
27
25
  # @return [Nokogiri::XML::Schema] Gets the schema object of the SAML 2.0 Protocol schema
28
26
  #
29
27
  def self.schema
@@ -88,11 +86,12 @@ module OneLogin
88
86
  # @param saml [String] The deflated and encoded SAML Message
89
87
  # @return [String] The plain SAML Message
90
88
  #
91
- def decode_raw_saml(saml)
89
+ def decode_raw_saml(saml, settings = nil)
92
90
  return saml unless base64_encoded?(saml)
93
91
 
94
- if saml.bytesize > MAX_BYTE_SIZE
95
- raise ValidationError.new("Encoded SAML Message exceeds " + MAX_BYTE_SIZE.to_s + " bytes, so was rejected")
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")
96
95
  end
97
96
 
98
97
  decoded = decode(saml)
@@ -31,9 +31,8 @@ module OneLogin
31
31
 
32
32
  # IdP Data
33
33
  attr_accessor :idp_entity_id
34
-
35
- attr_accessor :idp_sso_service_url
36
- attr_accessor :idp_slo_service_url
34
+ attr_writer :idp_sso_service_url
35
+ attr_writer :idp_slo_service_url
37
36
  attr_accessor :idp_slo_response_service_url
38
37
  attr_accessor :idp_cert
39
38
  attr_accessor :idp_cert_fingerprint
@@ -43,8 +42,10 @@ module OneLogin
43
42
  attr_accessor :idp_name_qualifier
44
43
  attr_accessor :valid_until
45
44
  # SP Data
45
+ attr_writer :sp_entity_id
46
46
  attr_accessor :assertion_consumer_service_url
47
- attr_accessor :assertion_consumer_service_binding
47
+ attr_reader :assertion_consumer_service_binding
48
+ attr_writer :single_logout_service_url
48
49
  attr_accessor :sp_name_qualifier
49
50
  attr_accessor :name_identifier_format
50
51
  attr_accessor :name_identifier_value
@@ -53,8 +54,9 @@ module OneLogin
53
54
  attr_accessor :compress_request
54
55
  attr_accessor :compress_response
55
56
  attr_accessor :double_quote_xml_attribute_values
57
+ attr_accessor :message_max_bytesize
56
58
  attr_accessor :passive
57
- attr_accessor :protocol_binding
59
+ attr_reader :protocol_binding
58
60
  attr_accessor :attributes_index
59
61
  attr_accessor :force_authn
60
62
  attr_accessor :certificate
@@ -67,9 +69,9 @@ module OneLogin
67
69
  # Work-flow
68
70
  attr_accessor :security
69
71
  attr_accessor :soft
70
- # Compability
72
+ # Deprecated
71
73
  attr_accessor :assertion_consumer_logout_service_url
72
- attr_accessor :assertion_consumer_logout_service_binding
74
+ attr_reader :assertion_consumer_logout_service_binding
73
75
  attr_accessor :issuer
74
76
  attr_accessor :idp_sso_target_url
75
77
  attr_accessor :idp_slo_target_url
@@ -77,94 +79,89 @@ module OneLogin
77
79
  # @return [String] IdP Single Sign On Service URL
78
80
  #
79
81
  def idp_sso_service_url
80
- val = nil
81
- if @idp_sso_service_url.nil?
82
- if @idp_sso_target_url
83
- val = @idp_sso_target_url
84
- end
85
- else
86
- val = @idp_sso_service_url
87
- end
88
- val
82
+ @idp_sso_service_url || @idp_sso_target_url
89
83
  end
90
84
 
91
85
  # @return [String] IdP Single Logout Service URL
92
86
  #
93
87
  def idp_slo_service_url
94
- val = nil
95
- if @idp_slo_service_url.nil?
96
- if @idp_slo_target_url
97
- val = @idp_slo_target_url
98
- end
99
- else
100
- val = @idp_slo_service_url
101
- end
102
- val
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)
103
115
  end
104
116
 
105
117
  # @return [String] SP Entity ID
106
118
  #
107
119
  def sp_entity_id
108
- val = nil
109
- if @sp_entity_id.nil?
110
- if @issuer
111
- val = @issuer
112
- end
113
- else
114
- val = @sp_entity_id
115
- end
116
- val
120
+ @sp_entity_id || @issuer
117
121
  end
118
122
 
119
- # Setter for SP Entity ID.
120
- # @param val [String].
123
+ # Setter for SP Protocol Binding
124
+ # @param value [String, Symbol].
121
125
  #
122
- def sp_entity_id=(val)
123
- @sp_entity_id = val
126
+ def protocol_binding=(value)
127
+ @protocol_binding = get_binding(value)
124
128
  end
125
129
 
126
- # @return [String] Single Logout Service URL.
130
+ # Setter for SP Assertion Consumer Service Binding
131
+ # @param value [String, Symbol].
127
132
  #
128
- def single_logout_service_url
129
- val = nil
130
- if @single_logout_service_url.nil?
131
- if @assertion_consumer_logout_service_url
132
- val = @assertion_consumer_logout_service_url
133
- end
134
- else
135
- val = @single_logout_service_url
136
- end
137
- val
133
+ def assertion_consumer_service_binding=(value)
134
+ @assertion_consumer_service_binding = get_binding(value)
138
135
  end
139
136
 
140
- # Setter for the Single Logout Service URL.
141
- # @param url [String].
137
+ # @return [String] Single Logout Service URL.
142
138
  #
143
- def single_logout_service_url=(url)
144
- @single_logout_service_url = url
139
+ def single_logout_service_url
140
+ @single_logout_service_url || @assertion_consumer_logout_service_url
145
141
  end
146
142
 
147
143
  # @return [String] Single Logout Service Binding.
148
144
  #
149
145
  def single_logout_service_binding
150
- val = nil
151
- if @single_logout_service_binding.nil?
152
- if @assertion_consumer_logout_service_binding
153
- val = @assertion_consumer_logout_service_binding
154
- end
155
- else
156
- val = @single_logout_service_binding
157
- end
158
- val
146
+ @single_logout_service_binding || @assertion_consumer_logout_service_binding
159
147
  end
160
148
 
161
149
  # Setter for Single Logout Service Binding.
162
150
  #
163
151
  # (Currently we only support "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect")
164
- # @param url [String]
152
+ # @param value [String, Symbol]
165
153
  #
166
- def single_logout_service_binding=(url)
167
- @single_logout_service_binding = url
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]
162
+ #
163
+ def assertion_consumer_logout_service_binding=(value)
164
+ @assertion_consumer_logout_service_binding = get_binding(value)
168
165
  end
169
166
 
170
167
  # Calculates the fingerprint of the IdP x509 certificate.
@@ -252,12 +249,23 @@ module OneLogin
252
249
 
253
250
  private
254
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
+
255
262
  DEFAULTS = {
256
- :assertion_consumer_service_binding => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST".freeze,
257
- :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],
258
265
  :idp_cert_fingerprint_algorithm => XMLSecurity::Document::SHA1,
259
266
  :compress_request => true,
260
267
  :compress_response => true,
268
+ :message_max_bytesize => 250000,
261
269
  :soft => true,
262
270
  :double_quote_xml_attribute_values => false,
263
271
  :security => {
@@ -268,7 +276,7 @@ module OneLogin
268
276
  :want_assertions_encrypted => false,
269
277
  :want_name_id => false,
270
278
  :metadata_signed => false,
271
- :embed_sign => false,
279
+ :embed_sign => false, # Deprecated
272
280
  :digest_method => XMLSecurity::Document::SHA1,
273
281
  :signature_method => XMLSecurity::Document::RSA_SHA1,
274
282
  :check_idp_cert_expiration => false,
@@ -43,7 +43,7 @@ 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
 
@@ -130,6 +130,12 @@ module OneLogin
130
130
 
131
131
  private
132
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
+
133
139
  # Hard aux function to validate the Logout Request
134
140
  # @param collect_errors [Boolean] Stop validation when first error appears or keep validating. (if soft=true)
135
141
  # @return [Boolean] TRUE if the Logout Request is valid
@@ -180,15 +186,17 @@ module OneLogin
180
186
  true
181
187
  end
182
188
 
183
- # 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)
184
191
  # If fails, the error is added to the errors array
185
192
  # @return [Boolean] True if satisfies the conditions, otherwise False if soft=True
186
193
  # @raise [ValidationError] if soft == false and validation fails
187
194
  #
188
195
  def validate_not_on_or_after
189
196
  now = Time.now.utc
190
- if not_on_or_after && now >= (not_on_or_after + (options[:allowed_clock_drift] || 0))
191
- 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})")
192
200
  end
193
201
 
194
202
  true
@@ -36,15 +36,15 @@ module OneLogin
36
36
  #
37
37
  def create(settings, request_id = nil, logout_message = nil, params = {}, logout_status_code = nil)
38
38
  params = create_params(settings, request_id, logout_message, params, logout_status_code)
39
- params_prefix = (settings.idp_slo_target_url =~ /\?/) ? '&' : '?'
40
- url = settings.idp_slo_response_service_url || settings.idp_slo_target_url
39
+ params_prefix = (settings.idp_slo_service_url =~ /\?/) ? '&' : '?'
40
+ url = settings.idp_slo_response_service_url || settings.idp_slo_service_url
41
41
  saml_response = CGI.escape(params.delete("SAMLResponse"))
42
42
  response_params = "#{params_prefix}SAMLResponse=#{saml_response}"
43
43
  params.each_pair do |key, value|
44
44
  response_params << "&#{key.to_s}=#{CGI.escape(value.to_s)}"
45
45
  end
46
46
 
47
- raise SettingError.new "Invalid settings, idp_slo_target_url is not set!" if url.nil? or url.empty?
47
+ raise SettingError.new "Invalid settings, idp_slo_service_url is not set!" if url.nil? or url.empty?
48
48
  @logout_url = url + response_params
49
49
  end
50
50
 
@@ -79,7 +79,7 @@ module OneLogin
79
79
  base64_response = encode(response)
80
80
  response_params = {"SAMLResponse" => base64_response}
81
81
 
82
- 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
83
83
  params['SigAlg'] = settings.security[:signature_method]
84
84
  url_string = OneLogin::RubySaml::Utils.build_query(
85
85
  :type => 'SAMLResponse',
@@ -117,7 +117,8 @@ module OneLogin
117
117
  response_doc = XMLSecurity::Document.new
118
118
  response_doc.uuid = uuid
119
119
 
120
- destination = settings.idp_slo_response_service_url || settings.idp_slo_target_url
120
+ destination = settings.idp_slo_response_service_url || settings.idp_slo_service_url
121
+
121
122
 
122
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" }
123
124
  root.attributes['ID'] = uuid
@@ -149,7 +150,7 @@ module OneLogin
149
150
 
150
151
  def sign_document(document, settings)
151
152
  # embed signature
152
- 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
153
154
  private_key = settings.get_sp_key
154
155
  cert = settings.get_sp_cert
155
156
  document.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method])
@@ -13,9 +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#"
18
- DURATION_FORMAT = %r(^(-?)P(?:(?:(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?)?)|(?:(\d+)W))$)
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
19
35
 
20
36
  # Checks if the x509 cert provided is expired
21
37
  #
@@ -37,31 +53,20 @@ module OneLogin
37
53
  # current time.
38
54
  #
39
55
  # @return [Integer] The new timestamp, after the duration is applied.
40
- #
56
+ #
41
57
  def self.parse_duration(duration, timestamp=Time.now.utc)
58
+ return nil if RUBY_VERSION < '1.9' # 1.8.7 not supported
59
+
42
60
  matches = duration.match(DURATION_FORMAT)
43
-
61
+
44
62
  if matches.nil?
45
63
  raise Exception.new("Invalid ISO 8601 duration")
46
64
  end
47
65
 
48
- durYears = matches[2].to_i
49
- durMonths = matches[3].to_i
50
- durDays = matches[4].to_i
51
- durHours = matches[5].to_i
52
- durMinutes = matches[6].to_i
53
- durSeconds = matches[7].to_f
54
- durWeeks = matches[8].to_i
55
-
56
- if matches[1] == "-"
57
- durYears = -durYears
58
- durMonths = -durMonths
59
- durDays = -durDays
60
- durHours = -durHours
61
- durMinutes = -durMinutes
62
- durSeconds = -durSeconds
63
- durWeeks = -durWeeks
64
- end
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 }
65
70
 
66
71
  initial_datetime = Time.at(timestamp).utc.to_datetime
67
72
  final_datetime = initial_datetime.next_year(durYears)
@@ -1,5 +1,5 @@
1
1
  module OneLogin
2
2
  module RubySaml
3
- VERSION = '1.12.1'
3
+ VERSION = '1.13.0'
4
4
  end
5
5
  end
data/lib/xml_security.rb CHANGED
@@ -159,15 +159,13 @@ module XMLSecurity
159
159
  x509_cert_element.text = Base64.encode64(certificate.to_der).gsub(/\n/, "")
160
160
 
161
161
  # add the signature
162
- issuer_element = self.elements["//saml:Issuer"]
162
+ issuer_element = elements["//saml:Issuer"]
163
163
  if issuer_element
164
- self.root.insert_after issuer_element, signature_element
164
+ root.insert_after(issuer_element, signature_element)
165
+ elsif first_child = root.children[0]
166
+ root.insert_before(first_child, signature_element)
165
167
  else
166
- if sp_sso_descriptor = self.elements["/md:EntityDescriptor"]
167
- self.root.insert_before sp_sso_descriptor, signature_element
168
- else
169
- self.root.add_element(signature_element)
170
- end
168
+ root.add_element(signature_element)
171
169
  end
172
170
  end
173
171