ruby-saml 1.9.0 → 1.10.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d36c7827c8bb0a1ab808a352ecb85bb925aca401
4
- data.tar.gz: e823dc21a31d81901cad0bdcdc2c5f737ed37d42
3
+ metadata.gz: 8c5ca7da83f66d1548ef03b3ffe920eea1a07eff
4
+ data.tar.gz: f27e46ad0136bb5325d55189f30bc8dac861b5d6
5
5
  SHA512:
6
- metadata.gz: d2a444bfe39a2236b37ff979015dd1b7e82f06d06f23abf38251bebdc33b64ca91b76c128811a7636335b00bfa1609ef0a7a450ff99be8613ea6f0d4f2aea22d
7
- data.tar.gz: 974a863b07f77aaef63393a23d825a1af3bfd3acc4e5d8ad00902d5efd556cf7d2062dac80e44f06c5a5cbe42154ffe2632351d897f18bcc7210fa0582b24d7e
6
+ metadata.gz: 49ac1e001e3b676f080d5ca0d5955c18034cd6a7d7371c99840fece91cb5ac55ce2bd68abfb2ae901f9a192589c997db1e77b6050d9569b66b0aa7311c2875f3
7
+ data.tar.gz: b6a30ef8efe897fcf8e69eb638b1214077cfd5027f83ab438164b3436d7396a7a4ee1a89f04f055e96d57c0b589e437c48ef5fd658ac2a136560b6b8620fa040
data/.travis.yml CHANGED
@@ -16,6 +16,8 @@ rvm:
16
16
  gemfile:
17
17
  - Gemfile
18
18
  - gemfiles/nokogiri-1.5.gemfile
19
+ before_install:
20
+ - gem update bundler
19
21
  matrix:
20
22
  exclude:
21
23
  - rvm: 1.8.7
data/README.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Ruby SAML [![Build Status](https://secure.travis-ci.org/onelogin/ruby-saml.svg)](http://travis-ci.org/onelogin/ruby-saml) [![Coverage Status](https://coveralls.io/repos/onelogin/ruby-saml/badge.svg?branch=master%0A)](https://coveralls.io/r/onelogin/ruby-saml?branch=master%0A) [![Gem Version](https://badge.fury.io/rb/ruby-saml.svg)](http://badge.fury.io/rb/ruby-saml)
2
2
 
3
+ # Updating from 1.9.0 to 1.10.0
4
+ Version `1.10.0` improves IdpMetadataParser to allow parse multiple IDPSSODescriptor, Add Subject support on AuthNRequest to allow SPs provide info to the IdP about the user to be authenticated and updates the format_cert method to accept certs with /\x0d/
5
+
6
+ ## Updating from 1.8.0 to 1.9.0
7
+ Version `1.9.0` better supports Ruby 2.4+ and JRuby 9.2.0.0. `Settings` initialization now has a second parameter, `keep_security_settings` (default: false), which saves security settings attributes that are not explicitly overridden, if set to true.
8
+
3
9
  ## Updating from 1.7.X to 1.8.0
4
10
  On Version `1.8.0`, creating AuthRequests/LogoutRequests/LogoutResponses with nil RelayState param will not generate a URL with an empty RelayState parameter anymore. It also changes the invalid audience error message.
5
11
 
@@ -99,8 +105,10 @@ We created a demo project for Rails4 that uses the latest version of this librar
99
105
  * 2.2.x
100
106
  * 2.3.x
101
107
  * 2.4.x
108
+ * 2.5.x
102
109
  * JRuby 1.7.19
103
110
  * JRuby 9.0.0.0
111
+ * JRuby 9.2.0.0
104
112
 
105
113
  ## Adding Features, Pull Requests
106
114
  * Fork the repository
@@ -121,7 +129,7 @@ Using `Gemfile`
121
129
 
122
130
  ```ruby
123
131
  # latest stable
124
- gem 'ruby-saml', '~> 1.8.0'
132
+ gem 'ruby-saml', '~> 1.9.0'
125
133
 
126
134
  # or track master for bleeding-edge
127
135
  gem 'ruby-saml', :github => 'onelogin/ruby-saml'
@@ -170,7 +178,7 @@ To override the default behavior and control the destination of log messages, pr
170
178
  a ruby Logger object to the gem's logging singleton:
171
179
 
172
180
  ```ruby
173
- OneLogin::RubySaml::Logging.logger = Logger.new(File.open('/var/log/ruby-saml.log', 'w'))
181
+ OneLogin::RubySaml::Logging.logger = Logger.new('/var/log/ruby-saml.log')
174
182
  ```
175
183
 
176
184
  ## The Initialization Phase
@@ -184,6 +192,17 @@ def init
184
192
  end
185
193
  ```
186
194
 
195
+ If the SP knows who should be authenticated in the IdP, then can provide that info as follows:
196
+
197
+ ```ruby
198
+ def init
199
+ request = OneLogin::RubySaml::Authrequest.new
200
+ saml_settings.name_identifier_value_requested = "testuser@example.com"
201
+ saml_settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
202
+ redirect_to(request.create(saml_settings))
203
+ end
204
+ ```
205
+
187
206
  Once you've redirected back to the identity provider, it will ensure that the user has been authorized and redirect back to your application for final consumption. This can look something like this (the `authorize_success` and `authorize_failure` methods are specific to your application):
188
207
 
189
208
  ```ruby
@@ -498,6 +517,9 @@ pp(response.attributes.multi(:not_exists))
498
517
  The `saml:AuthnContextClassRef` of the AuthNRequest can be provided by `settings.authn_context`; possible values are described at [SAMLAuthnCxt]. The comparison method can be set using `settings.authn_context_comparison` parameter. Possible values include: 'exact', 'better', 'maximum' and 'minimum' (default value is 'exact').
499
518
  To add a `saml:AuthnContextDeclRef`, define `settings.authn_context_decl_ref`.
500
519
 
520
+ In a SP-initiaited flow, the SP can indicate to the IdP the subject that should be authenticated. This is done by defining the `settings.name_identifier_value_requested` before
521
+ building the authrequest object.
522
+
501
523
 
502
524
  ## Signing
503
525
 
data/changelog.md CHANGED
@@ -1,4 +1,9 @@
1
1
  # RubySaml Changelog
2
+ ### 1.10.0 (Mar 21, 2019)
3
+ * Add Subject support on AuthNRequest to allow SPs provide info to the IdP about the user to be authenticated
4
+ * Improves IdpMetadataParser to allow parse multiple IDPSSODescriptors
5
+ * Improves format_cert method to accept certs with /\x0d/
6
+ * Forces nokogiri >= 1.8.2 when possible
2
7
 
3
8
  ### 1.9.0 (Sept 03, 2018)
4
9
  * [#458](https://github.com/onelogin/ruby-saml/pull/458) Remove ruby 2.4+ warnings
@@ -121,6 +121,18 @@ module OneLogin
121
121
  issuer = root.add_element "saml:Issuer"
122
122
  issuer.text = settings.issuer
123
123
  end
124
+
125
+ if settings.name_identifier_value_requested != nil
126
+ subject = root.add_element "saml:Subject"
127
+
128
+ nameid = subject.add_element "saml:NameID"
129
+ nameid.attributes['Format'] = settings.name_identifier_format if settings.name_identifier_format
130
+ nameid.text = settings.name_identifier_value_requested
131
+
132
+ subject_confirmation = subject.add_element "saml:SubjectConfirmation"
133
+ subject_confirmation.attributes['Method'] = "urn:oasis:names:tc:SAML:2.0:cm:bearer"
134
+ end
135
+
124
136
  if settings.name_identifier_format != nil
125
137
  root.add_element "samlp:NameIDPolicy", {
126
138
  # Might want to make AllowCreate a setting?
@@ -1,6 +1,4 @@
1
1
  require "base64"
2
- require "zlib"
3
- require "cgi"
4
2
  require "net/http"
5
3
  require "net/https"
6
4
  require "rexml/document"
@@ -14,12 +12,23 @@ module OneLogin
14
12
  # Auxiliary class to retrieve and parse the Identity Provider Metadata
15
13
  #
16
14
  class IdpMetadataParser
15
+ module SamlMetadata
16
+ module Vocabulary
17
+ METADATA = "urn:oasis:names:tc:SAML:2.0:metadata"
18
+ DSIG = "http://www.w3.org/2000/09/xmldsig#"
19
+ NAME_FORMAT = "urn:oasis:names:tc:SAML:2.0:attrname-format:*"
20
+ SAML_ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion"
21
+ end
17
22
 
18
- METADATA = "urn:oasis:names:tc:SAML:2.0:metadata"
19
- DSIG = "http://www.w3.org/2000/09/xmldsig#"
20
- NAME_FORMAT = "urn:oasis:names:tc:SAML:2.0:attrname-format:*"
21
- SAML_ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion"
23
+ NAMESPACE = {
24
+ "md" => Vocabulary::METADATA,
25
+ "NameFormat" => Vocabulary::NAME_FORMAT,
26
+ "saml" => Vocabulary::SAML_ASSERTION,
27
+ "ds" => Vocabulary::DSIG
28
+ }
29
+ end
22
30
 
31
+ include SamlMetadata::Vocabulary
23
32
  attr_reader :document
24
33
  attr_reader :response
25
34
  attr_reader :options
@@ -58,8 +67,25 @@ module OneLogin
58
67
  #
59
68
  # @raise [HttpError] Failure to fetch remote IdP metadata
60
69
  def parse_remote_to_hash(url, validate_cert = true, options = {})
70
+ parse_remote_to_array(url, validate_cert, options)[0]
71
+ end
72
+
73
+ # Parse all Identity Provider metadata and return the results as Array
74
+ #
75
+ # @param url [String] Url where the XML of the Identity Provider Metadata is published.
76
+ # @param validate_cert [Boolean] If true and the URL is HTTPs, the cert of the domain is checked.
77
+ #
78
+ # @param options [Hash] options used for parsing the metadata
79
+ # @option options [Array<String>, nil] :sso_binding an ordered list of bindings to detect the single signon URL. The first binding in the list that is included in the metadata will be used.
80
+ # @option options [Array<String>, nil] :slo_binding an ordered list of bindings to detect the single logout URL. The first binding in the list that is included in the metadata will be used.
81
+ # @option options [String, nil] :entity_id when this is given, the entity descriptor for this ID is used. When ommitted, all found IdPs are returned.
82
+ #
83
+ # @return [Array<Hash>]
84
+ #
85
+ # @raise [HttpError] Failure to fetch remote IdP metadata
86
+ def parse_remote_to_array(url, validate_cert = true, options = {})
61
87
  idp_metadata = get_idp_metadata(url, validate_cert)
62
- parse_to_hash(idp_metadata, options)
88
+ parse_to_array(idp_metadata, options)
63
89
  end
64
90
 
65
91
  # Parse the Identity Provider metadata and update the settings with the IdP values
@@ -98,28 +124,29 @@ module OneLogin
98
124
  #
99
125
  # @return [Hash]
100
126
  def parse_to_hash(idp_metadata, options = {})
127
+ parse_to_array(idp_metadata, options)[0]
128
+ end
129
+
130
+ # Parse all Identity Provider metadata and return the results as Array
131
+ #
132
+ # @param idp_metadata [String]
133
+ #
134
+ # @param options [Hash] options used for parsing the metadata and the returned Settings instance
135
+ # @option options [Array<String>, nil] :sso_binding an ordered list of bindings to detect the single signon URL. The first binding in the list that is included in the metadata will be used.
136
+ # @option options [Array<String>, nil] :slo_binding an ordered list of bindings to detect the single logout URL. The first binding in the list that is included in the metadata will be used.
137
+ # @option options [String, nil] :entity_id when this is given, the entity descriptor for this ID is used. When ommitted, all found IdPs are returned.
138
+ #
139
+ # @return [Array<Hash>]
140
+ def parse_to_array(idp_metadata, options = {})
101
141
  @document = REXML::Document.new(idp_metadata)
102
142
  @options = options
103
- @entity_descriptor = nil
104
- @certificates = nil
105
- @fingerprint = nil
106
143
 
107
- if idpsso_descriptor.nil?
144
+ idpsso_descriptors = IdpMetadata::get_idps(@document, options[:entity_id])
145
+ if !idpsso_descriptors.any?
108
146
  raise ArgumentError.new("idp_metadata must contain an IDPSSODescriptor element")
109
147
  end
110
148
 
111
- {
112
- :idp_entity_id => idp_entity_id,
113
- :name_identifier_format => idp_name_id_format,
114
- :idp_sso_target_url => single_signon_service_url(options),
115
- :idp_slo_target_url => single_logout_service_url(options),
116
- :idp_attribute_names => attribute_names,
117
- :idp_cert => nil,
118
- :idp_cert_fingerprint => nil,
119
- :idp_cert_multi => nil
120
- }.tap do |response_hash|
121
- merge_certificates_into(response_hash) unless certificates.nil?
122
- end
149
+ return idpsso_descriptors.map{|id| IdpMetadata.new(id, id.parent.attributes["entityID"]).to_hash(options)}
123
150
  end
124
151
 
125
152
  private
@@ -147,7 +174,7 @@ module OneLogin
147
174
  end
148
175
 
149
176
  get = Net::HTTP::Get.new(uri.request_uri)
150
- response = http.request(get)
177
+ @response = http.request(get)
151
178
  return response.body if response.is_a? Net::HTTPSuccess
152
179
 
153
180
  raise OneLogin::RubySaml::HttpError.new(
@@ -155,130 +182,129 @@ module OneLogin
155
182
  )
156
183
  end
157
184
 
158
- def entity_descriptor
159
- @entity_descriptor ||= REXML::XPath.first(
160
- document,
161
- entity_descriptor_path,
162
- namespace
163
- )
164
- end
165
-
166
- def entity_descriptor_path
167
- path = "//md:EntityDescriptor"
168
- entity_id = options[:entity_id]
169
- return path unless entity_id
170
- path << "[@entityID=\"#{entity_id}\"]"
171
- end
172
-
173
- def idpsso_descriptor
174
- unless entity_descriptor.nil?
175
- return REXML::XPath.first(
176
- entity_descriptor,
177
- "md:IDPSSODescriptor",
178
- namespace
185
+ class IdpMetadata
186
+ def self.get_idps(metadata_document, only_entity_id=nil)
187
+ path = "//md:EntityDescriptor#{only_entity_id && '[@entityID="' + only_entity_id + '"]'}/md:IDPSSODescriptor"
188
+ REXML::XPath.match(
189
+ metadata_document,
190
+ path,
191
+ SamlMetadata::NAMESPACE
179
192
  )
180
193
  end
181
- end
182
-
183
- # @return [String|nil] IdP Entity ID value if exists
184
- #
185
- def idp_entity_id
186
- entity_descriptor.attributes["entityID"]
187
- end
188
194
 
189
- # @return [String|nil] IdP Name ID Format value if exists
190
- #
191
- def idp_name_id_format
192
- node = REXML::XPath.first(
193
- entity_descriptor,
194
- "md:IDPSSODescriptor/md:NameIDFormat",
195
- namespace
196
- )
197
- Utils.element_text(node)
198
- end
195
+ def initialize(idpsso_descriptor, entity_id)
196
+ @idpsso_descriptor = idpsso_descriptor
197
+ @entity_id = entity_id
198
+ end
199
199
 
200
- # @param binding_priority [Array]
201
- # @return [String|nil] SingleSignOnService binding if exists
202
- #
203
- def single_signon_service_binding(binding_priority = nil)
204
- nodes = REXML::XPath.match(
205
- entity_descriptor,
206
- "md:IDPSSODescriptor/md:SingleSignOnService/@Binding",
207
- namespace
208
- )
209
- if binding_priority
210
- values = nodes.map(&:value)
211
- binding_priority.detect{ |binding| values.include? binding }
212
- else
213
- nodes.first.value if nodes.any?
200
+ def to_hash(options = {})
201
+ {
202
+ :idp_entity_id => @entity_id,
203
+ :name_identifier_format => idp_name_id_format,
204
+ :idp_sso_target_url => single_signon_service_url(options),
205
+ :idp_slo_target_url => single_logout_service_url(options),
206
+ :idp_attribute_names => attribute_names,
207
+ :idp_cert => nil,
208
+ :idp_cert_fingerprint => nil,
209
+ :idp_cert_multi => nil
210
+ }.tap do |response_hash|
211
+ merge_certificates_into(response_hash) unless certificates.nil?
212
+ end
214
213
  end
215
- end
216
214
 
217
- # @param options [Hash]
218
- # @return [String|nil] SingleSignOnService endpoint if exists
219
- #
220
- def single_signon_service_url(options = {})
221
- binding = single_signon_service_binding(options[:sso_binding])
222
- unless binding.nil?
215
+ # @return [String|nil] IdP Name ID Format value if exists
216
+ #
217
+ def idp_name_id_format
223
218
  node = REXML::XPath.first(
224
- entity_descriptor,
225
- "md:IDPSSODescriptor/md:SingleSignOnService[@Binding=\"#{binding}\"]/@Location",
226
- namespace
219
+ @idpsso_descriptor,
220
+ "md:NameIDFormat",
221
+ SamlMetadata::NAMESPACE
227
222
  )
228
- return node.value if node
223
+ Utils.element_text(node)
229
224
  end
230
- end
231
225
 
232
- # @param binding_priority [Array]
233
- # @return [String|nil] SingleLogoutService binding if exists
234
- #
235
- def single_logout_service_binding(binding_priority = nil)
236
- nodes = REXML::XPath.match(
237
- entity_descriptor,
238
- "md:IDPSSODescriptor/md:SingleLogoutService/@Binding",
239
- namespace
240
- )
241
- if binding_priority
242
- values = nodes.map(&:value)
243
- binding_priority.detect{ |binding| values.include? binding }
244
- else
245
- nodes.first.value if nodes.any?
226
+ # @param binding_priority [Array]
227
+ # @return [String|nil] SingleSignOnService binding if exists
228
+ #
229
+ def single_signon_service_binding(binding_priority = nil)
230
+ nodes = REXML::XPath.match(
231
+ @idpsso_descriptor,
232
+ "md:SingleSignOnService/@Binding",
233
+ SamlMetadata::NAMESPACE
234
+ )
235
+ if binding_priority
236
+ values = nodes.map(&:value)
237
+ binding_priority.detect{ |binding| values.include? binding }
238
+ else
239
+ nodes.first.value if nodes.any?
240
+ end
246
241
  end
247
- end
248
242
 
249
- # @param options [Hash]
250
- # @return [String|nil] SingleLogoutService endpoint if exists
251
- #
252
- def single_logout_service_url(options = {})
253
- binding = single_logout_service_binding(options[:slo_binding])
254
- unless binding.nil?
243
+ # @param options [Hash]
244
+ # @return [String|nil] SingleSignOnService endpoint if exists
245
+ #
246
+ def single_signon_service_url(options = {})
247
+ binding = single_signon_service_binding(options[:sso_binding])
248
+ return if binding.nil?
249
+
255
250
  node = REXML::XPath.first(
256
- entity_descriptor,
257
- "md:IDPSSODescriptor/md:SingleLogoutService[@Binding=\"#{binding}\"]/@Location",
258
- namespace
251
+ @idpsso_descriptor,
252
+ "md:SingleSignOnService[@Binding=\"#{binding}\"]/@Location",
253
+ SamlMetadata::NAMESPACE
259
254
  )
260
255
  return node.value if node
261
256
  end
262
- end
263
257
 
264
- # @return [String|nil] Unformatted Certificate if exists
265
- #
266
- def certificates
267
- @certificates ||= begin
268
- signing_nodes = REXML::XPath.match(
269
- entity_descriptor,
270
- "md:IDPSSODescriptor/md:KeyDescriptor[not(contains(@use, 'encryption'))]/ds:KeyInfo/ds:X509Data/ds:X509Certificate",
271
- namespace
258
+ # @param binding_priority [Array]
259
+ # @return [String|nil] SingleLogoutService binding if exists
260
+ #
261
+ def single_logout_service_binding(binding_priority = nil)
262
+ nodes = REXML::XPath.match(
263
+ @idpsso_descriptor,
264
+ "md:SingleLogoutService/@Binding",
265
+ SamlMetadata::NAMESPACE
272
266
  )
267
+ if binding_priority
268
+ values = nodes.map(&:value)
269
+ binding_priority.detect{ |binding| values.include? binding }
270
+ else
271
+ nodes.first.value if nodes.any?
272
+ end
273
+ end
274
+
275
+ # @param options [Hash]
276
+ # @return [String|nil] SingleLogoutService endpoint if exists
277
+ #
278
+ def single_logout_service_url(options = {})
279
+ binding = single_logout_service_binding(options[:slo_binding])
280
+ return if binding.nil?
273
281
 
274
- encryption_nodes = REXML::XPath.match(
275
- entity_descriptor,
276
- "md:IDPSSODescriptor/md:KeyDescriptor[not(contains(@use, 'signing'))]/ds:KeyInfo/ds:X509Data/ds:X509Certificate",
277
- namespace
282
+ node = REXML::XPath.first(
283
+ @idpsso_descriptor,
284
+ "md:SingleLogoutService[@Binding=\"#{binding}\"]/@Location",
285
+ SamlMetadata::NAMESPACE
278
286
  )
287
+ return node.value if node
288
+ end
289
+
290
+ # @return [String|nil] Unformatted Certificate if exists
291
+ #
292
+ def certificates
293
+ @certificates ||= begin
294
+ signing_nodes = REXML::XPath.match(
295
+ @idpsso_descriptor,
296
+ "md:KeyDescriptor[not(contains(@use, 'encryption'))]/ds:KeyInfo/ds:X509Data/ds:X509Certificate",
297
+ SamlMetadata::NAMESPACE
298
+ )
299
+
300
+ encryption_nodes = REXML::XPath.match(
301
+ @idpsso_descriptor,
302
+ "md:KeyDescriptor[not(contains(@use, 'signing'))]/ds:KeyInfo/ds:X509Data/ds:X509Certificate",
303
+ SamlMetadata::NAMESPACE
304
+ )
305
+
306
+ return nil if signing_nodes.empty? && encryption_nodes.empty?
279
307
 
280
- certs = nil
281
- unless signing_nodes.empty? && encryption_nodes.empty?
282
308
  certs = {}
283
309
  unless signing_nodes.empty?
284
310
  certs['signing'] = []
@@ -293,71 +319,62 @@ module OneLogin
293
319
  certs['encryption'] << Utils.element_text(cert_node)
294
320
  end
295
321
  end
322
+ certs
296
323
  end
297
- certs
298
324
  end
299
- end
300
325
 
301
- # @return [String|nil] the fingerpint of the X509Certificate if it exists
302
- #
303
- def fingerprint(certificate, fingerprint_algorithm = XMLSecurity::Document::SHA1)
304
- @fingerprint ||= begin
305
- if certificate
326
+ # @return [String|nil] the fingerpint of the X509Certificate if it exists
327
+ #
328
+ def fingerprint(certificate, fingerprint_algorithm = XMLSecurity::Document::SHA1)
329
+ @fingerprint ||= begin
330
+ return unless certificate
331
+
306
332
  cert = OpenSSL::X509::Certificate.new(Base64.decode64(certificate))
307
333
 
308
334
  fingerprint_alg = XMLSecurity::BaseDocument.new.algorithm(fingerprint_algorithm).new
309
335
  fingerprint_alg.hexdigest(cert.to_der).upcase.scan(/../).join(":")
310
336
  end
311
337
  end
312
- end
313
338
 
314
- # @return [Array] the names of all SAML attributes if any exist
315
- #
316
- def attribute_names
317
- nodes = REXML::XPath.match(
318
- entity_descriptor,
319
- "md:IDPSSODescriptor/saml:Attribute/@Name",
320
- namespace
321
- )
322
- nodes.map(&:value)
323
- end
324
-
325
- def namespace
326
- {
327
- "md" => METADATA,
328
- "NameFormat" => NAME_FORMAT,
329
- "saml" => SAML_ASSERTION,
330
- "ds" => DSIG
331
- }
332
- end
339
+ # @return [Array] the names of all SAML attributes if any exist
340
+ #
341
+ def attribute_names
342
+ nodes = REXML::XPath.match(
343
+ @idpsso_descriptor ,
344
+ "saml:Attribute/@Name",
345
+ SamlMetadata::NAMESPACE
346
+ )
347
+ nodes.map(&:value)
348
+ end
333
349
 
334
- def merge_certificates_into(parsed_metadata)
335
- if (certificates.size == 1 &&
350
+ def merge_certificates_into(parsed_metadata)
351
+ if (certificates.size == 1 &&
336
352
  (certificates_has_one('signing') || certificates_has_one('encryption'))) ||
337
353
  (certificates_has_one('signing') && certificates_has_one('encryption') &&
338
354
  certificates["signing"][0] == certificates["encryption"][0])
339
355
 
340
- if certificates.key?("signing")
341
- parsed_metadata[:idp_cert] = certificates["signing"][0]
342
- parsed_metadata[:idp_cert_fingerprint] = fingerprint(
343
- parsed_metadata[:idp_cert],
344
- parsed_metadata[:idp_cert_fingerprint_algorithm]
345
- )
356
+ if certificates.key?("signing")
357
+ parsed_metadata[:idp_cert] = certificates["signing"][0]
358
+ parsed_metadata[:idp_cert_fingerprint] = fingerprint(
359
+ parsed_metadata[:idp_cert],
360
+ parsed_metadata[:idp_cert_fingerprint_algorithm]
361
+ )
362
+ else
363
+ parsed_metadata[:idp_cert] = certificates["encryption"][0]
364
+ parsed_metadata[:idp_cert_fingerprint] = fingerprint(
365
+ parsed_metadata[:idp_cert],
366
+ parsed_metadata[:idp_cert_fingerprint_algorithm]
367
+ )
368
+ end
346
369
  else
347
- parsed_metadata[:idp_cert] = certificates["encryption"][0]
348
- parsed_metadata[:idp_cert_fingerprint] = fingerprint(
349
- parsed_metadata[:idp_cert],
350
- parsed_metadata[:idp_cert_fingerprint_algorithm]
351
- )
370
+ # symbolize keys of certificates and pass it on
371
+ parsed_metadata[:idp_cert_multi] = Hash[certificates.map { |k, v| [k.to_sym, v] }]
352
372
  end
353
- else
354
- # symbolize keys of certificates and pass it on
355
- parsed_metadata[:idp_cert_multi] = Hash[certificates.map { |k, v| [k.to_sym, v] }]
356
373
  end
357
- end
358
374
 
359
- def certificates_has_one(key)
360
- certificates.key?(key) && certificates[key].size == 1
375
+ def certificates_has_one(key)
376
+ certificates.key?(key) && certificates[key].size == 1
377
+ end
361
378
  end
362
379
 
363
380
  def merge_parsed_metadata_into(settings, parsed_metadata)
@@ -367,6 +384,8 @@ module OneLogin
367
384
 
368
385
  settings
369
386
  end
387
+
388
+ private_constant :SamlMetadata, :IdpMetadata
370
389
  end
371
390
  end
372
391
  end
@@ -89,6 +89,11 @@ module OneLogin
89
89
  # @return [String] The SAMLRequest String.
90
90
  #
91
91
  def create_logout_request_xml_doc(settings)
92
+ document = create_xml_document(settings)
93
+ sign_document(document, settings)
94
+ end
95
+
96
+ def create_xml_document(settings)
92
97
  time = Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
93
98
 
94
99
  request_doc = XMLSecurity::Document.new
@@ -122,14 +127,18 @@ module OneLogin
122
127
  sessionindex.text = settings.sessionindex
123
128
  end
124
129
 
130
+ request_doc
131
+ end
132
+
133
+ def sign_document(document, settings)
125
134
  # embed signature
126
135
  if settings.security[:logout_requests_signed] && settings.private_key && settings.certificate && settings.security[:embed_sign]
127
136
  private_key = settings.get_sp_key
128
137
  cert = settings.get_sp_cert
129
- request_doc.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method])
138
+ document.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method])
130
139
  end
131
140
 
132
- request_doc
141
+ document
133
142
  end
134
143
  end
135
144
  end
@@ -52,10 +52,7 @@ module OneLogin
52
52
  # @raise [ValidationError] if soft == false and validation fails
53
53
  #
54
54
  def success?
55
- unless status_code == "urn:oasis:names:tc:SAML:2.0:status:Success"
56
- return append_error("Bad status code. Expected <urn:oasis:names:tc:SAML:2.0:status:Success>, but was: <#@status_code>")
57
- end
58
- true
55
+ return status_code == "urn:oasis:names:tc:SAML:2.0:status:Success"
59
56
  end
60
57
 
61
58
  # @return [String|nil] Gets the InResponseTo attribute from the Logout Response if exists.
@@ -65,7 +62,7 @@ module OneLogin
65
62
  node = REXML::XPath.first(
66
63
  document,
67
64
  "/p:LogoutResponse",
68
- { "p" => PROTOCOL, "a" => ASSERTION }
65
+ { "p" => PROTOCOL }
69
66
  )
70
67
  node.nil? ? nil : node.attributes['InResponseTo']
71
68
  end
@@ -88,7 +85,7 @@ module OneLogin
88
85
  #
89
86
  def status_code
90
87
  @status_code ||= begin
91
- node = REXML::XPath.first(document, "/p:LogoutResponse/p:Status/p:StatusCode", { "p" => PROTOCOL, "a" => ASSERTION })
88
+ node = REXML::XPath.first(document, "/p:LogoutResponse/p:Status/p:StatusCode", { "p" => PROTOCOL })
92
89
  node.nil? ? nil : node.attributes["Value"]
93
90
  end
94
91
  end
@@ -98,7 +95,7 @@ module OneLogin
98
95
  node = REXML::XPath.first(
99
96
  document,
100
97
  "/p:LogoutResponse/p:Status/p:StatusMessage",
101
- { "p" => PROTOCOL, "a" => ASSERTION }
98
+ { "p" => PROTOCOL }
102
99
  )
103
100
  Utils.element_text(node)
104
101
  end
@@ -105,7 +105,7 @@ module OneLogin
105
105
  def encode_raw_saml(saml, settings)
106
106
  saml = deflate(saml) if settings.compress_request
107
107
 
108
- CGI.escape(Base64.encode64(saml))
108
+ CGI.escape(encode(saml))
109
109
  end
110
110
 
111
111
  # Base 64 decode method
@@ -121,7 +121,11 @@ module OneLogin
121
121
  # @return [String] The encoded string
122
122
  #
123
123
  def encode(string)
124
- Base64.encode64(string).gsub(/\n/, "")
124
+ if Base64.respond_to?('strict_encode64')
125
+ Base64.strict_encode64(string)
126
+ else
127
+ Base64.encode64(string).gsub(/\n/, "")
128
+ end
125
129
  end
126
130
 
127
131
  # Check if a string is base64 encoded
@@ -45,6 +45,7 @@ module OneLogin
45
45
  attr_accessor :sp_name_qualifier
46
46
  attr_accessor :name_identifier_format
47
47
  attr_accessor :name_identifier_value
48
+ attr_accessor :name_identifier_value_requested
48
49
  attr_accessor :sessionindex
49
50
  attr_accessor :compress_request
50
51
  attr_accessor :compress_response
@@ -97,6 +97,11 @@ module OneLogin
97
97
  # @return [String] The SAMLResponse String.
98
98
  #
99
99
  def create_logout_response_xml_doc(settings, request_id = nil, logout_message = nil)
100
+ document = create_xml_document(settings, request_id, logout_message)
101
+ sign_document(document, settings)
102
+ end
103
+
104
+ def create_xml_document(settings, request_id = nil, logout_message = nil)
100
105
  time = Time.now.utc.strftime('%Y-%m-%dT%H:%M:%SZ')
101
106
 
102
107
  response_doc = XMLSecurity::Document.new
@@ -126,14 +131,18 @@ module OneLogin
126
131
  status_message = status.add_element 'samlp:StatusMessage'
127
132
  status_message.text = logout_message
128
133
 
134
+ response_doc
135
+ end
136
+
137
+ def sign_document(document, settings)
129
138
  # embed signature
130
139
  if settings.security[:logout_responses_signed] && settings.private_key && settings.certificate && settings.security[:embed_sign]
131
140
  private_key = settings.get_sp_key
132
141
  cert = settings.get_sp_cert
133
- response_doc.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method])
142
+ document.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method])
134
143
  end
135
144
 
136
- response_doc
145
+ document
137
146
  end
138
147
 
139
148
  end
@@ -22,7 +22,7 @@ module OneLogin
22
22
  #
23
23
  def self.format_cert(cert)
24
24
  # don't try to format an encoded certificate or if is empty or nil
25
- return cert if cert.nil? || cert.empty? || cert.match(/\x0d/)
25
+ return cert if cert.nil? || cert.empty? || !cert.ascii_only?
26
26
 
27
27
  if cert.scan(/BEGIN CERTIFICATE/).length > 1
28
28
  formatted_cert = []
@@ -1,5 +1,5 @@
1
1
  module OneLogin
2
2
  module RubySaml
3
- VERSION = '1.9.0'
3
+ VERSION = '1.10.0'
4
4
  end
5
5
  end
data/ruby-saml.gemspec CHANGED
@@ -29,15 +29,19 @@ Gem::Specification.new do |s|
29
29
  # Nokogiri's version dependent on the Ruby version, even though we would
30
30
  # have liked to constrain Ruby 1.8.7 to install only the 1.5.x versions.
31
31
  if defined?(JRUBY_VERSION)
32
- s.add_runtime_dependency('nokogiri', '>= 1.6.0')
33
- s.add_runtime_dependency('jruby-openssl', '>= 0.9.8') if JRUBY_VERSION < '9.2.0.0'
32
+ if JRUBY_VERSION < '9.2.0.0'
33
+ s.add_runtime_dependency('nokogiri', '>= 1.8.2', '<= 1.8.5')
34
+ s.add_runtime_dependency('jruby-openssl', '>= 0.9.8')
35
+ else
36
+ s.add_runtime_dependency('nokogiri', '>= 1.8.2')
37
+ end
34
38
  elsif RUBY_VERSION < '1.9'
35
39
  s.add_runtime_dependency('uuid')
36
40
  s.add_runtime_dependency('nokogiri', '<= 1.5.11')
37
41
  elsif RUBY_VERSION < '2.1'
38
42
  s.add_runtime_dependency('nokogiri', '>= 1.5.10', '<= 1.6.8.1')
39
43
  else
40
- s.add_runtime_dependency('nokogiri', '>= 1.5.10')
44
+ s.add_runtime_dependency('nokogiri', '>= 1.8.2')
41
45
  end
42
46
 
43
47
  s.add_development_dependency('minitest', '~> 5.5')
Binary file
@@ -319,7 +319,7 @@ class IdpMetadataParserTest < Minitest::Test
319
319
  describe "parsing metadata with many entity descriptors" do
320
320
  before do
321
321
  @idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new
322
- @idp_metadata = idp_metadata_multiple_descriptors
322
+ @idp_metadata = idp_metadata_multiple_descriptors2
323
323
  @settings = @idp_metadata_parser.parse(@idp_metadata)
324
324
  end
325
325
 
@@ -342,6 +342,14 @@ class IdpMetadataParserTest < Minitest::Test
342
342
  assert_equal "https://hello.example.com/access/saml/logout", @settings.idp_slo_target_url
343
343
  assert_equal ["AuthToken", "SSOStartPage"], @settings.idp_attribute_names
344
344
  end
345
+
346
+ it "should handle multiple descriptors at once" do
347
+ settings = @idp_metadata_parser.parse_to_array(@idp_metadata)
348
+ assert_equal "https://foo.example.com/access/saml/idp.xml", settings.first[:idp_entity_id]
349
+ assert_equal "F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72", settings.first[:idp_cert_fingerprint]
350
+ assert_equal "https://bar.example.com/access/saml/idp.xml", settings.last[:idp_entity_id]
351
+ assert_equal "08:EB:6E:60:A2:14:4E:89:EC:FA:05:74:9D:72:BF:5D:BE:54:F0:1A", settings.last[:idp_cert_fingerprint]
352
+ end
345
353
  end
346
354
 
347
355
  describe "parsing metadata with no IDPSSODescriptor element" do
@@ -44,6 +44,25 @@ def unsuccessful_logout_response_document(opts = {})
44
44
  </samlp:LogoutResponse>"
45
45
  end
46
46
 
47
+ def unsuccessful_logout_response_with_message_document(opts = {})
48
+ opts = default_logout_response_opts.merge(opts)
49
+
50
+ "<samlp:LogoutResponse
51
+ xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\"
52
+ ID=\"#{random_id}\" Version=\"2.0\"
53
+ IssueInstant=\"#{opts[:issue_instant]}\"
54
+ Destination=\"#{opts[:settings].single_logout_service_url}\"
55
+ InResponseTo=\"#{opts[:uuid]}\">
56
+ <saml:Issuer xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\">#{opts[:settings].issuer}</saml:Issuer>
57
+ <samlp:Status xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\">
58
+ <samlp:StatusCode xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\"
59
+ Value=\"urn:oasis:names:tc:SAML:2.0:status:Requester\">
60
+ </samlp:StatusCode>
61
+ <samlp:StatusMessage>Logoutrequest expired</samlp:StatusMessage>
62
+ </samlp:Status>
63
+ </samlp:LogoutResponse>"
64
+ end
65
+
47
66
  def invalid_xml_logout_response_document
48
67
  "<samlp:SomethingAwful
49
68
  xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\"
@@ -104,6 +104,40 @@ class RequestTest < Minitest::Test
104
104
  settings.private_key = ruby_saml_key_text
105
105
  end
106
106
 
107
+ it "doesn't sign through create_xml_document" do
108
+ unauth_req = OneLogin::RubySaml::Logoutrequest.new
109
+ inflated = unauth_req.create_xml_document(settings).to_s
110
+
111
+ refute_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], inflated
112
+ refute_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1'/>], inflated
113
+ refute_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2000/09/xmldsig#sha1'/>], inflated
114
+ end
115
+
116
+ it "sign unsigned request" do
117
+ unauth_req = OneLogin::RubySaml::Logoutrequest.new
118
+ unauth_req_doc = unauth_req.create_xml_document(settings)
119
+ inflated = unauth_req_doc.to_s
120
+
121
+ refute_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], inflated
122
+ refute_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1'/>], inflated
123
+ refute_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2000/09/xmldsig#sha1'/>], inflated
124
+
125
+ inflated = unauth_req.sign_document(unauth_req_doc, settings).to_s
126
+
127
+ assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], inflated
128
+ assert_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1'/>], inflated
129
+ assert_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2000/09/xmldsig#sha1'/>], inflated
130
+ end
131
+
132
+ it "signs through create_logout_request_xml_doc" do
133
+ unauth_req = OneLogin::RubySaml::Logoutrequest.new
134
+ inflated = unauth_req.create_logout_request_xml_doc(settings).to_s
135
+
136
+ assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], inflated
137
+ assert_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1'/>], inflated
138
+ assert_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2000/09/xmldsig#sha1'/>], inflated
139
+ end
140
+
107
141
  it "created a signed logout request" do
108
142
  settings.compress_request = true
109
143
 
@@ -185,8 +219,8 @@ class RequestTest < Minitest::Test
185
219
  query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}"
186
220
 
187
221
  signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg'])
188
- assert_equal signature_algorithm, OpenSSL::Digest::SHA256
189
- assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string)
222
+ assert_equal signature_algorithm, OpenSSL::Digest::SHA256
223
+ assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string)
190
224
  end
191
225
 
192
226
  it "create a signature parameter with RSA_SHA384 / SHA384 and validate it" do
@@ -201,8 +235,8 @@ class RequestTest < Minitest::Test
201
235
  query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}"
202
236
 
203
237
  signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg'])
204
- assert_equal signature_algorithm, OpenSSL::Digest::SHA384
205
- assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string)
238
+ assert_equal signature_algorithm, OpenSSL::Digest::SHA384
239
+ assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string)
206
240
  end
207
241
 
208
242
  it "create a signature parameter with RSA_SHA512 / SHA512 and validate it" do
@@ -217,8 +251,8 @@ class RequestTest < Minitest::Test
217
251
  query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}"
218
252
 
219
253
  signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg'])
220
- assert_equal signature_algorithm, OpenSSL::Digest::SHA512
221
- assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string)
254
+ assert_equal signature_algorithm, OpenSSL::Digest::SHA512
255
+ assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string)
222
256
  end
223
257
 
224
258
  end
@@ -107,15 +107,22 @@ class RubySamlTest < Minitest::Test
107
107
  assert_includes logoutresponse.errors, "The InResponseTo of the Logout Response: #{logoutresponse.in_response_to}, does not match the ID of the Logout Request sent by the SP: #{expected_request_id}"
108
108
  end
109
109
 
110
- it "invalidate logout response with wrong request status" do
110
+ it "invalidate logout response with unexpected request status" do
111
111
  logoutresponse = OneLogin::RubySaml::Logoutresponse.new(unsuccessful_logout_response_document, settings)
112
112
 
113
113
  assert !logoutresponse.success?
114
114
  assert !logoutresponse.validate
115
- assert_includes logoutresponse.errors, "Bad status code. Expected <urn:oasis:names:tc:SAML:2.0:status:Success>, but was: <urn:oasis:names:tc:SAML:2.0:status:Requester>"
116
115
  assert_includes logoutresponse.errors, "The status code of the Logout Response was not Success, was Requester"
117
116
  end
118
117
 
118
+ it "invalidate logout response with unexpected request status and status message" do
119
+ logoutresponse = OneLogin::RubySaml::Logoutresponse.new(unsuccessful_logout_response_with_message_document, settings)
120
+
121
+ assert !logoutresponse.success?
122
+ assert !logoutresponse.validate
123
+ assert_includes logoutresponse.errors, "The status code of the Logout Response was not Success, was Requester -> Logoutrequest expired"
124
+ end
125
+
119
126
  it "invalidate logout response when in lack of issuer setting" do
120
127
  bad_settings = settings
121
128
  bad_settings.issuer = nil
@@ -137,7 +144,7 @@ class RubySamlTest < Minitest::Test
137
144
  logoutresponse = OneLogin::RubySaml::Logoutresponse.new(unsuccessful_logout_response_document, settings)
138
145
  collect_errors = true
139
146
  assert !logoutresponse.validate(collect_errors)
140
- assert_includes logoutresponse.errors, "Bad status code. Expected <urn:oasis:names:tc:SAML:2.0:status:Success>, but was: <urn:oasis:names:tc:SAML:2.0:status:Requester>"
147
+ assert_includes logoutresponse.errors, "The status code of the Logout Response was not Success, was Requester"
141
148
  assert_includes logoutresponse.errors, "Doesn't match the issuer, expected: <#{logoutresponse.settings.idp_entity_id}>, but was: <http://app.muda.no>"
142
149
  end
143
150
 
@@ -185,14 +192,14 @@ class RubySamlTest < Minitest::Test
185
192
  logoutresponse = OneLogin::RubySaml::Logoutresponse.new(unsuccessful_logout_response_document, settings)
186
193
 
187
194
  assert_raises(OneLogin::RubySaml::ValidationError) { logoutresponse.validate }
188
- assert_includes logoutresponse.errors, "Bad status code. Expected <urn:oasis:names:tc:SAML:2.0:status:Success>, but was: <urn:oasis:names:tc:SAML:2.0:status:Requester>"
195
+ assert_includes logoutresponse.errors, "The status code of the Logout Response was not Success, was Requester"
189
196
  end
190
197
 
191
198
  it "raise validation error when in bad state" do
192
199
  # no settings
193
200
  logoutresponse = OneLogin::RubySaml::Logoutresponse.new(unsuccessful_logout_response_document, settings)
194
201
  assert_raises(OneLogin::RubySaml::ValidationError) { logoutresponse.validate }
195
- assert_includes logoutresponse.errors, "Bad status code. Expected <urn:oasis:names:tc:SAML:2.0:status:Success>, but was: <urn:oasis:names:tc:SAML:2.0:status:Requester>"
202
+ assert_includes logoutresponse.errors, "The status code of the Logout Response was not Success, was Requester"
196
203
  end
197
204
 
198
205
  it "raise validation error when in lack of issuer setting" do
@@ -1,5 +1,11 @@
1
1
  <?xml version="1.0" encoding="UTF-8"?>
2
2
  <md:EntitiesDescriptor validUntil="2014-04-17T18:02:33.910Z" xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata">
3
+ <md:EntityDescriptor entityID="https://biz.example.com/app" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="md:EntityDescriptorType">
4
+ <md:SPSSODescriptor AuthnRequestsSigned="true" WantAssertionsSigned="false" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
5
+ <md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</md:NameIDFormat>
6
+ <md:AssertionConsumerService isDefault="true" index="1" binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" location="https://biz.example.com/app/sso"></md:AssertionConsumerService>
7
+ </md:SPSSODescriptor>
8
+ </md:EntityDescriptor>
3
9
  <md:EntityDescriptor entityID="https://foo.example.com/access/saml/idp.xml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="md:EntityDescriptorType">
4
10
  <md:IDPSSODescriptor WantAuthnRequestsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
5
11
  <md:KeyDescriptor use="signing">
@@ -0,0 +1,59 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <md:EntitiesDescriptor validUntil="2014-04-17T18:02:33.910Z" xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata">
3
+ <md:EntityDescriptor entityID="https://biz.example.com/app" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="md:EntityDescriptorType">
4
+ <md:SPSSODescriptor AuthnRequestsSigned="true" WantAssertionsSigned="false" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
5
+ <md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</md:NameIDFormat>
6
+ <md:AssertionConsumerService isDefault="true" index="1" binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" location="https://biz.example.com/app/sso"></md:AssertionConsumerService>
7
+ </md:SPSSODescriptor>
8
+ </md:EntityDescriptor>
9
+ <md:EntityDescriptor entityID="https://foo.example.com/access/saml/idp.xml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="md:EntityDescriptorType">
10
+ <md:IDPSSODescriptor WantAuthnRequestsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
11
+ <md:KeyDescriptor use="signing">
12
+ <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
13
+ <ds:X509Data>
14
+ <ds:X509Certificate>LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURxekNDQXhTZ0F3SUJBZ0lCQVRBTkJna3Foa2lHOXcwQkFRc0ZBRENCaGpFTE1Ba0dBMVVFQmhNQ1FWVXgKRERBS0JnTlZCQWdUQTA1VFZ6RVBNQTBHQTFVRUJ4TUdVM2xrYm1WNU1Rd3dDZ1lEVlFRS0RBTlFTVlF4Q1RBSApCZ05WQkFzTUFERVlNQllHQTFVRUF3d1BiR0YzY21WdVkyVndhWFF1WTI5dE1TVXdJd1lKS29aSWh2Y05BUWtCCkRCWnNZWGR5Wlc1alpTNXdhWFJBWjIxaGFXd3VZMjl0TUI0WERURXlNRFF4T1RJeU5UUXhPRm9YRFRNeU1EUXgKTkRJeU5UUXhPRm93Z1lZeEN6QUpCZ05WQkFZVEFrRlZNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVApCbE41Wkc1bGVURU1NQW9HQTFVRUNnd0RVRWxVTVFrd0J3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psCmJtTmxjR2wwTG1OdmJURWxNQ01HQ1NxR1NJYjNEUUVKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnYKYlRDQm56QU5CZ2txaGtpRzl3MEJBUUVGQUFPQmpRQXdnWWtDZ1lFQXFqaWUzUjJvaStwRGFldndJeXMvbWJVVApubkdsa3h0ZGlrcnExMXZleHd4SmlQTmhtaHFSVzNtVXVKRXpsbElkVkw2RW14R1lUcXBxZjkzSGxoa3NhZUowCjhVZ2pQOVVtTVlyaFZKdTFqY0ZXVjdmei9yKzIxL2F3VG5EVjlzTVlRcXVJUllZeTdiRzByMU9iaXdkb3ZudGsKN2dGSTA2WjB2WmFjREU1Ym9xVUNBd0VBQWFPQ0FTVXdnZ0VoTUFrR0ExVWRFd1FDTUFBd0N3WURWUjBQQkFRRApBZ1VnTUIwR0ExVWREZ1FXQkJTUk9OOEdKOG8rOGpnRnRqa3R3WmRxeDZCUnlUQVRCZ05WSFNVRUREQUtCZ2dyCkJnRUZCUWNEQVRBZEJnbGdoa2dCaHZoQ0FRMEVFQllPVkdWemRDQllOVEE1SUdObGNuUXdnYk1HQTFVZEl3U0IKcXpDQnFJQVVrVGpmQmlmS1B2STRCYlk1TGNHWGFzZWdVY21oZ1l5a2dZa3dnWVl4Q3pBSkJnTlZCQVlUQWtGVgpNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVEJsTjVaRzVsZVRFTU1Bb0dBMVVFQ2d3RFVFbFVNUWt3CkJ3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psYm1ObGNHbDBMbU52YlRFbE1DTUdDU3FHU0liM0RRRUoKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnZiWUlCQVRBTkJna3Foa2lHOXcwQkFRc0ZBQU9CZ1FDRQpUQWVKVERTQVc2ejFVRlRWN1FyZWg0VUxGT1JhajkrZUN1RjNLV0RIYyswSVFDajlyZG5ERzRRL3dmNy9yYVEwCkpuUFFDU0NkclBMSmV5b1BIN1FhVHdvYUY3ZHpWdzRMQ3N5TkpURld4NGNNNTBWdzZSNWZET2dpQzhic2ZmUzgKQkptb3VscnJaRE5OVmpHOG1XNmNMeHJZdlZRT3JSVmVjQ0ZJZ3NzQ2JBPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=</ds:X509Certificate>
15
+ </ds:X509Data>
16
+ </ds:KeyInfo>
17
+ </md:KeyDescriptor>
18
+ <md:KeyDescriptor use="encryption">
19
+ <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
20
+ <ds:X509Data>
21
+ <ds:X509Certificate>LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURxekNDQXhTZ0F3SUJBZ0lCQVRBTkJna3Foa2lHOXcwQkFRc0ZBRENCaGpFTE1Ba0dBMVVFQmhNQ1FWVXgKRERBS0JnTlZCQWdUQTA1VFZ6RVBNQTBHQTFVRUJ4TUdVM2xrYm1WNU1Rd3dDZ1lEVlFRS0RBTlFTVlF4Q1RBSApCZ05WQkFzTUFERVlNQllHQTFVRUF3d1BiR0YzY21WdVkyVndhWFF1WTI5dE1TVXdJd1lKS29aSWh2Y05BUWtCCkRCWnNZWGR5Wlc1alpTNXdhWFJBWjIxaGFXd3VZMjl0TUI0WERURXlNRFF4T1RJeU5UUXhPRm9YRFRNeU1EUXgKTkRJeU5UUXhPRm93Z1lZeEN6QUpCZ05WQkFZVEFrRlZNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVApCbE41Wkc1bGVURU1NQW9HQTFVRUNnd0RVRWxVTVFrd0J3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psCmJtTmxjR2wwTG1OdmJURWxNQ01HQ1NxR1NJYjNEUUVKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnYKYlRDQm56QU5CZ2txaGtpRzl3MEJBUUVGQUFPQmpRQXdnWWtDZ1lFQXFqaWUzUjJvaStwRGFldndJeXMvbWJVVApubkdsa3h0ZGlrcnExMXZleHd4SmlQTmhtaHFSVzNtVXVKRXpsbElkVkw2RW14R1lUcXBxZjkzSGxoa3NhZUowCjhVZ2pQOVVtTVlyaFZKdTFqY0ZXVjdmei9yKzIxL2F3VG5EVjlzTVlRcXVJUllZeTdiRzByMU9iaXdkb3ZudGsKN2dGSTA2WjB2WmFjREU1Ym9xVUNBd0VBQWFPQ0FTVXdnZ0VoTUFrR0ExVWRFd1FDTUFBd0N3WURWUjBQQkFRRApBZ1VnTUIwR0ExVWREZ1FXQkJTUk9OOEdKOG8rOGpnRnRqa3R3WmRxeDZCUnlUQVRCZ05WSFNVRUREQUtCZ2dyCkJnRUZCUWNEQVRBZEJnbGdoa2dCaHZoQ0FRMEVFQllPVkdWemRDQllOVEE1SUdObGNuUXdnYk1HQTFVZEl3U0IKcXpDQnFJQVVrVGpmQmlmS1B2STRCYlk1TGNHWGFzZWdVY21oZ1l5a2dZa3dnWVl4Q3pBSkJnTlZCQVlUQWtGVgpNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVEJsTjVaRzVsZVRFTU1Bb0dBMVVFQ2d3RFVFbFVNUWt3CkJ3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psYm1ObGNHbDBMbU52YlRFbE1DTUdDU3FHU0liM0RRRUoKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnZiWUlCQVRBTkJna3Foa2lHOXcwQkFRc0ZBQU9CZ1FDRQpUQWVKVERTQVc2ejFVRlRWN1FyZWg0VUxGT1JhajkrZUN1RjNLV0RIYyswSVFDajlyZG5ERzRRL3dmNy9yYVEwCkpuUFFDU0NkclBMSmV5b1BIN1FhVHdvYUY3ZHpWdzRMQ3N5TkpURld4NGNNNTBWdzZSNWZET2dpQzhic2ZmUzgKQkptb3VscnJaRE5OVmpHOG1XNmNMeHJZdlZRT3JSVmVjQ0ZJZ3NzQ2JBPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=</ds:X509Certificate>
22
+ </ds:X509Data>
23
+ </ds:KeyInfo>
24
+ </md:KeyDescriptor>
25
+ <md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://hello.example.com/access/saml/logout" ResponseLocation="https://hello.example.com/access/saml/logout"/>
26
+ <md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat>
27
+ <md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</md:NameIDFormat>
28
+ <md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</md:NameIDFormat>
29
+ <md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://hello.example.com/access/saml/login"/>
30
+ <saml:Attribute Name="AuthToken" NameFormat="urn:oasis:names:tc:SAML:2.0:att rname-format:basic" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"/>
31
+ <saml:Attribute Name="SSOStartPage" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"/>
32
+ </md:IDPSSODescriptor>
33
+ </md:EntityDescriptor>
34
+ <md:EntityDescriptor entityID="https://bar.example.com/access/saml/idp.xml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="md:EntityDescriptorType">
35
+ <md:IDPSSODescriptor WantAuthnRequestsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
36
+ <md:KeyDescriptor use="signing">
37
+ <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
38
+ <ds:X509Data>
39
+ <ds:X509Certificate>MIICfDCCAeWgAwIBAgIBADANBgkqhkiG9w0BAQsFADBbMQswCQYDVQQGEwJ1czEYMBYGA1UECAwPYmFyLmV4YW1wbGUuY29tMRgwFgYDVQQKDA9iYXIuZXhhbXBsZS5jb20xGDAWBgNVBAMMD2Jhci5leGFtcGxlLmNvbTAeFw0xOTAzMjExMzAzMTJaFw0yOTAzMTgxMzAzMTJaMFsxCzAJBgNVBAYTAnVzMRgwFgYDVQQIDA9iYXIuZXhhbXBsZS5jb20xGDAWBgNVBAoMD2Jhci5leGFtcGxlLmNvbTEYMBYGA1UEAwwPYmFyLmV4YW1wbGUuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDv7Xe9pxNYjxLhXhjQy7L6BbU0IVwQS9pbWqXYDtH3z1fIy+tWMEnE+XhuSFLilRcfn8Ksk0VeFLByQw2HfxAHY3O2dsCv5yLHye3/5bLKa+/pYu7r9ltE5JNjELAH1Yo0/SvAWX9Nuw6Ovw7D6frXUxBPhJaEQ+1VnatuZBUOfQIDAQABo1AwTjAdBgNVHQ4EFgQU6xbFedBYUDFTdXoQvRrELtH4jP8wHwYDVR0jBBgwFoAU6xbFedBYUDFTdXoQvRrELtH4jP8wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOBgQBvDqYSONEhlDB4+k2+hMg03XCwINLh0UHWd+QfFO1yT6iZK+Duaq9pDBl97RO9sN6qHfOFSQXYBOj6S1Xf+6Orq1yeep/Uh+UrL7wzWYMzPryZxTYn8B1PbdXePRIZCzU7d7ueMnEfUoBj9HqhQphRYD9WgXP9/xzvu9JQjTNk9g==</ds:X509Certificate>
40
+ </ds:X509Data>
41
+ </ds:KeyInfo>
42
+ </md:KeyDescriptor>
43
+ <md:KeyDescriptor use="encryption">
44
+ <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
45
+ <ds:X509Data>
46
+ <ds:X509Certificate>MIICfDCCAeWgAwIBAgIBADANBgkqhkiG9w0BAQsFADBbMQswCQYDVQQGEwJ1czEYMBYGA1UECAwPYmFyLmV4YW1wbGUuY29tMRgwFgYDVQQKDA9iYXIuZXhhbXBsZS5jb20xGDAWBgNVBAMMD2Jhci5leGFtcGxlLmNvbTAeFw0xOTAzMjExMzAzMTJaFw0yOTAzMTgxMzAzMTJaMFsxCzAJBgNVBAYTAnVzMRgwFgYDVQQIDA9iYXIuZXhhbXBsZS5jb20xGDAWBgNVBAoMD2Jhci5leGFtcGxlLmNvbTEYMBYGA1UEAwwPYmFyLmV4YW1wbGUuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDv7Xe9pxNYjxLhXhjQy7L6BbU0IVwQS9pbWqXYDtH3z1fIy+tWMEnE+XhuSFLilRcfn8Ksk0VeFLByQw2HfxAHY3O2dsCv5yLHye3/5bLKa+/pYu7r9ltE5JNjELAH1Yo0/SvAWX9Nuw6Ovw7D6frXUxBPhJaEQ+1VnatuZBUOfQIDAQABo1AwTjAdBgNVHQ4EFgQU6xbFedBYUDFTdXoQvRrELtH4jP8wHwYDVR0jBBgwFoAU6xbFedBYUDFTdXoQvRrELtH4jP8wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOBgQBvDqYSONEhlDB4+k2+hMg03XCwINLh0UHWd+QfFO1yT6iZK+Duaq9pDBl97RO9sN6qHfOFSQXYBOj6S1Xf+6Orq1yeep/Uh+UrL7wzWYMzPryZxTYn8B1PbdXePRIZCzU7d7ueMnEfUoBj9HqhQphRYD9WgXP9/xzvu9JQjTNk9g==</ds:X509Certificate>
47
+ </ds:X509Data>
48
+ </ds:KeyInfo>
49
+ </md:KeyDescriptor>
50
+ <md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://hello.example.com/access/saml/logout" ResponseLocation="https://hello.example.com/access/saml/logout"/>
51
+ <md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat>
52
+ <md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</md:NameIDFormat>
53
+ <md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</md:NameIDFormat>
54
+ <md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://hello.example.com/access/saml/login"/>
55
+ <saml:Attribute Name="AuthToken" NameFormat="urn:oasis:names:tc:SAML:2.0:att rname-format:basic" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"/>
56
+ <saml:Attribute Name="SSOStartPage" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"/>
57
+ </md:IDPSSODescriptor>
58
+ </md:EntityDescriptor>
59
+ </md:EntitiesDescriptor>
data/test/request_test.rb CHANGED
@@ -121,6 +121,23 @@ class RequestTest < Minitest::Test
121
121
  assert_match /<samlp:NameIDPolicy[^<]* Format='urn:oasis:names:tc:SAML:2.0:nameid-format:transient'/, inflated
122
122
  end
123
123
 
124
+ it "create the SAMLRequest URL parameter with Subject" do
125
+ settings.name_identifier_value_requested = "testuser@example.com"
126
+ settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
127
+ auth_url = OneLogin::RubySaml::Authrequest.new.create(settings)
128
+ assert_match /^http:\/\/example\.com\?SAMLRequest=/, auth_url
129
+ payload = CGI.unescape(auth_url.split("=").last)
130
+ decoded = Base64.decode64(payload)
131
+ zstream = Zlib::Inflate.new(-Zlib::MAX_WBITS)
132
+ inflated = zstream.inflate(decoded)
133
+ zstream.finish
134
+ zstream.close
135
+
136
+ assert inflated.include?('<saml:Subject>')
137
+ assert inflated.include?("<saml:NameID Format='urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress'>testuser@example.com</saml:NameID>")
138
+ assert inflated.include?("<saml:SubjectConfirmation Method='urn:oasis:names:tc:SAML:2.0:cm:bearer'/>")
139
+ end
140
+
124
141
  it "accept extra parameters" do
125
142
  auth_url = OneLogin::RubySaml::Authrequest.new.create(settings, { :hello => "there" })
126
143
  assert_match /&hello=there$/, auth_url
@@ -15,7 +15,7 @@ class SettingsTest < Minitest::Test
15
15
  :idp_cert, :idp_cert_fingerprint, :idp_cert_fingerprint_algorithm, :idp_cert_multi,
16
16
  :idp_attribute_names, :issuer, :assertion_consumer_service_url, :assertion_consumer_service_binding,
17
17
  :single_logout_service_url, :single_logout_service_binding,
18
- :sp_name_qualifier, :name_identifier_format, :name_identifier_value,
18
+ :sp_name_qualifier, :name_identifier_format, :name_identifier_value, :name_identifier_value_requested,
19
19
  :sessionindex, :attributes_index, :passive, :force_authn,
20
20
  :compress_request, :double_quote_xml_attribute_values, :protocol_binding,
21
21
  :security, :certificate, :private_key,
@@ -73,6 +73,40 @@ class SloLogoutresponseTest < Minitest::Test
73
73
  settings.security[:embed_sign] = true
74
74
  end
75
75
 
76
+ it "doesn't sign through create_xml_document" do
77
+ unauth_res = OneLogin::RubySaml::SloLogoutresponse.new
78
+ inflated = unauth_res.create_xml_document(settings).to_s
79
+
80
+ refute_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], inflated
81
+ refute_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1'/>], inflated
82
+ refute_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2000/09/xmldsig#sha1'/>], inflated
83
+ end
84
+
85
+ it "sign unsigned request" do
86
+ unauth_res = OneLogin::RubySaml::SloLogoutresponse.new
87
+ unauth_res_doc = unauth_res.create_xml_document(settings)
88
+ inflated = unauth_res_doc.to_s
89
+
90
+ refute_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], inflated
91
+ refute_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1'/>], inflated
92
+ refute_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2000/09/xmldsig#sha1'/>], inflated
93
+
94
+ inflated = unauth_res.sign_document(unauth_res_doc, settings).to_s
95
+
96
+ assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], inflated
97
+ assert_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1'/>], inflated
98
+ assert_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2000/09/xmldsig#sha1'/>], inflated
99
+ end
100
+
101
+ it "signs through create_logout_response_xml_doc" do
102
+ unauth_res = OneLogin::RubySaml::SloLogoutresponse.new
103
+ inflated = unauth_res.create_logout_response_xml_doc(settings).to_s
104
+
105
+ assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], inflated
106
+ assert_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1'/>], inflated
107
+ assert_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2000/09/xmldsig#sha1'/>], inflated
108
+ end
109
+
76
110
  it "create a signed logout response" do
77
111
  logout_request.settings = settings
78
112
  params = OneLogin::RubySaml::SloLogoutresponse.new.create_params(settings, logout_request.id, "Custom Logout Message")
data/test/test_helper.rb CHANGED
@@ -178,6 +178,10 @@ class Minitest::Test
178
178
  @idp_metadata_multiple_descriptors ||= File.read(File.join(File.dirname(__FILE__), 'metadata', 'idp_multiple_descriptors.xml'))
179
179
  end
180
180
 
181
+ def idp_metadata_multiple_descriptors2
182
+ @idp_metadata_multiple_descriptors2 ||= File.read(File.join(File.dirname(__FILE__), 'metadata', 'idp_multiple_descriptors_2.xml'))
183
+ end
184
+
181
185
  def idp_metadata_multiple_certs
182
186
  @idp_metadata_multiple_descriptors ||= File.read(File.join(File.dirname(__FILE__), 'metadata', 'idp_metadata_multi_certs.xml'))
183
187
  end
data/test/utils_test.rb CHANGED
@@ -29,6 +29,11 @@ class UtilsTest < Minitest::Test
29
29
  assert_equal formatted_certificate, OneLogin::RubySaml::Utils.format_cert(invalid_certificate2)
30
30
  end
31
31
 
32
+ it "returns the cert when it's encoded" do
33
+ encoded_certificate = read_certificate("certificate.der")
34
+ assert_equal encoded_certificate, OneLogin::RubySaml::Utils.format_cert(encoded_certificate)
35
+ end
36
+
32
37
  it "reformats the certificate when there line breaks and no headers" do
33
38
  invalid_certificate3 = read_certificate("invalid_certificate3")
34
39
  assert_equal formatted_certificate, OneLogin::RubySaml::Utils.format_cert(invalid_certificate3)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-saml
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.9.0
4
+ version: 1.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - OneLogin LLC
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-09-03 00:00:00.000000000 Z
11
+ date: 2019-03-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nokogiri
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 1.5.10
19
+ version: 1.8.2
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 1.5.10
26
+ version: 1.8.2
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: minitest
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -187,6 +187,7 @@ files:
187
187
  - lib/schemas/xmldsig-core-schema.xsd
188
188
  - lib/xml_security.rb
189
189
  - ruby-saml.gemspec
190
+ - test/certificates/certificate.der
190
191
  - test/certificates/certificate1
191
192
  - test/certificates/certificate_without_head_foot
192
193
  - test/certificates/formatted_certificate
@@ -226,6 +227,7 @@ files:
226
227
  - test/metadata/idp_metadata_multi_signing_certs.xml
227
228
  - test/metadata/idp_metadata_same_sign_and_encrypt_cert.xml
228
229
  - test/metadata/idp_multiple_descriptors.xml
230
+ - test/metadata/idp_multiple_descriptors_2.xml
229
231
  - test/metadata/no_idp_descriptor.xml
230
232
  - test/metadata_test.rb
231
233
  - test/request_test.rb
@@ -348,6 +350,7 @@ signing_key:
348
350
  specification_version: 4
349
351
  summary: SAML Ruby Tookit
350
352
  test_files:
353
+ - test/certificates/certificate.der
351
354
  - test/certificates/certificate1
352
355
  - test/certificates/certificate_without_head_foot
353
356
  - test/certificates/formatted_certificate
@@ -387,6 +390,7 @@ test_files:
387
390
  - test/metadata/idp_metadata_multi_signing_certs.xml
388
391
  - test/metadata/idp_metadata_same_sign_and_encrypt_cert.xml
389
392
  - test/metadata/idp_multiple_descriptors.xml
393
+ - test/metadata/idp_multiple_descriptors_2.xml
390
394
  - test/metadata/no_idp_descriptor.xml
391
395
  - test/metadata_test.rb
392
396
  - test/request_test.rb