ruby-saml 1.12.2 → 1.13.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,