ruby-saml 1.12.2 → 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.

Potentially problematic release.


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

@@ -70,8 +70,8 @@ module OneLogin
70
70
  base64_request = encode(request)
71
71
  request_params = {"SAMLRequest" => base64_request}
72
72
 
73
- if settings.security[:logout_requests_signed] && !settings.security[:embed_sign] && settings.private_key
74
- params['SigAlg'] = settings.security[:signature_method]
73
+ if settings.idp_slo_service_binding == Utils::BINDINGS[:redirect] && settings.security[:logout_requests_signed] && settings.private_key
74
+ params['SigAlg'] = settings.security[:signature_method]
75
75
  url_string = OneLogin::RubySaml::Utils.build_query(
76
76
  :type => 'SAMLRequest',
77
77
  :data => base64_request,
@@ -138,7 +138,7 @@ module OneLogin
138
138
 
139
139
  def sign_document(document, settings)
140
140
  # embed signature
141
- if settings.security[:logout_requests_signed] && settings.private_key && settings.certificate && settings.security[:embed_sign]
141
+ if settings.idp_slo_service_binding == Utils::BINDINGS[:post] && settings.security[:logout_requests_signed] && settings.private_key && settings.certificate
142
142
  private_key = settings.get_sp_key
143
143
  cert = settings.get_sp_cert
144
144
  document.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method])
@@ -43,7 +43,7 @@ module OneLogin
43
43
  end
44
44
 
45
45
  @options = options
46
- @response = decode_raw_saml(response)
46
+ @response = decode_raw_saml(response, settings)
47
47
  @document = XMLSecurity::SignedDocument.new(@response)
48
48
  end
49
49
 
@@ -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,