ruby-saml 1.4.2 → 1.4.3

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

Potentially problematic release.


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

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 639540398f041bbcc593b2d2fdb14cf93028ce45
4
- data.tar.gz: ab5128758b3789b7354906a8f9145af07ac873e5
3
+ metadata.gz: 455ea502cffeb8d8b1c0bc673de3495e0e1b070a
4
+ data.tar.gz: f5327bfb4d61922b77444f397a8347910a6bab33
5
5
  SHA512:
6
- metadata.gz: 6ff40b3269727503ec6977fec4697e3babe3742cf6bc214281a156745057f581df8cfd7b93ec3499e23bf3ed99d806abe38bc03d4e7784b83ccb0b617494d883
7
- data.tar.gz: 3fe0c819cb2183ed1c574f4a2d02db01894a69bc1c4673582d4646fc80217ed88504de2ae0c59bbe22cc209212e9aa5a895a7d22c5be83fc10589be713247c50
6
+ metadata.gz: a3842b07e3f0da562c0302a7a399f63901bbe0a7ae88a68f60dce296fc0d7a9eebed08544db2cfcea2857a76dfed58cc158d6dacba10671135a804d899d8edad
7
+ data.tar.gz: 0ce3cc58921b2d384983cca9a29a75890e39a25e0b62da97e27f3a4c32baabf15b338ff9cd83b5aeb8d980ff69f10f094b72afab8a047c917ac775b7f4a1b896
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Ruby SAML [![Build Status](https://secure.travis-ci.org/onelogin/ruby-saml.png)](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)
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
3
  ## Updating from 1.3.x to 1.4.X
4
4
 
@@ -186,18 +186,19 @@ def saml_settings
186
186
  ]
187
187
 
188
188
  # Optional bindings (defaults to Redirect for logout POST for acs)
189
+ settings.single_logout_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
189
190
  settings.assertion_consumer_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
190
- settings.assertion_consumer_logout_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
191
191
 
192
192
  settings
193
193
  end
194
194
  ```
195
195
 
196
- Some assertion validations can be skipped by passing parameters to `OneLogin::RubySaml::Response.new()`. For example, you can skip the `Conditions` validation or the `SubjectConfirmation` validations by initializing the response with different options:
196
+ Some assertion validations can be skipped by passing parameters to `OneLogin::RubySaml::Response.new()`. For example, you can skip the `Conditions`, `Recipient`, or the `SubjectConfirmation` validations by initializing the response with different options:
197
197
 
198
198
  ```ruby
199
199
  response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], {skip_conditions: true}) # skips conditions
200
200
  response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], {skip_subject_confirmation: true}) # skips subject confirmation
201
+ response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], {skip_recipient_check: true}) # doens't skip subject confirmation, but skips the recipient check which is a sub check of the subject_confirmation check
201
202
  ```
202
203
 
203
204
  All that's left is to wrap everything in a controller and reference it in the initialization and consumption URLs in OneLogin. A full controller example could look like this:
@@ -251,12 +252,37 @@ class SamlController < ApplicationController
251
252
  end
252
253
  end
253
254
  ```
255
+
256
+
257
+ ## Signature validation
258
+
259
+ On the ruby-saml toolkit there are different ways to validate the signature of the SAMLResponse:
260
+ - You can provide the IdP x509 public certificate at the 'idp_cert' setting.
261
+ - You can provide the IdP x509 public certificate in fingerprint format using the 'idp_cert_fingerprint' setting parameter and additionally the 'idp_cert_fingerprint_algorithm' parameter.
262
+
263
+ When validating the signature of redirect binding, the fingerprint is useless and the certficate of the IdP is required in order to execute the validation.
264
+ You can pass the option :relax_signature_validation to SloLogoutrequest and Logoutresponse if want to avoid signature validation if no certificate of the IdP is provided.
265
+
266
+ In some scenarios the IdP uses different certificates for signing/encryption, or is under key rollover phase and more than one certificate is published on IdP metadata.
267
+
268
+ In order to handle that the toolkit offers the 'idp_cert_multi' parameter.
269
+ When used, 'idp_cert' and 'idp_cert_fingerprint' values are ignored.
270
+
271
+ That 'idp_cert_multi' must be a Hash as follows:
272
+ {
273
+ :signing => [],
274
+ :encryption => []
275
+ }
276
+
277
+ And on 'signing' and 'encryption' arrays, add the different IdP x509 public certificates published on the IdP metadata.
278
+
279
+
254
280
  ## Metadata Based Configuration
255
281
 
256
282
  The method above requires a little extra work to manually specify attributes about the IdP. (And your SP application) There's an easier method -- use a metadata exchange. Metadata is just an XML file that defines the capabilities of both the IdP and the SP application. It also contains the X.509 public
257
283
  key certificates which add to the trusted relationship. The IdP administrator can also configure custom settings for an SP based on the metadata.
258
284
 
259
- Using ```idp_metadata_parser.parse_remote``` IdP metadata will be added to the settings withouth further ado.
285
+ Using ```idp_metadata_parser.parse_remote``` IdP metadata will be added to the settings without further ado.
260
286
 
261
287
  ```ruby
262
288
  def saml_settings
@@ -275,9 +301,35 @@ def saml_settings
275
301
  end
276
302
  ```
277
303
  The following attributes are set:
304
+ * idp_entity_id
305
+ * name_identifier_format
278
306
  * idp_sso_target_url
279
307
  * idp_slo_target_url
280
- * idp_cert_fingerprint
308
+ * idp_attribute_names
309
+ * idp_cert
310
+ * idp_cert_fingerprint
311
+ * idp_cert_multi
312
+
313
+ ### Retrieve one Entity Descriptor when many exist in Metadata
314
+
315
+ If the Metadata contains several entities, the relevant Entity
316
+ Descriptor can be specified when retrieving the settings from the
317
+ IdpMetadataParser by its Entity Id value:
318
+
319
+ ```ruby
320
+ validate_cert = true
321
+ settings = idp_metadata_parser.parse_remote(
322
+ "https://example.com/auth/saml2/idp/metadata",
323
+ validate_cert,
324
+ entity_id: "http//example.com/target/entity"
325
+ )
326
+ ```
327
+
328
+ ### Parsing Metadata into an Hash
329
+
330
+ The `OneLogin::RubySaml::IdpMetadataParser` also provides the methods `#parse_to_hash` and `#parse_remote_to_hash`.
331
+ Those return an Hash instead of a `Settings` object, which may be useful for configuring
332
+ [omniauth-saml](https://github.com/omniauth/omniauth-saml), for instance.
281
333
 
282
334
  ## Retrieving Attributes
283
335
 
@@ -411,9 +463,9 @@ The settings related to sign are stored in the `security` attribute of the setti
411
463
  ```ruby
412
464
  settings.security[:authn_requests_signed] = true # Enable or not signature on AuthNRequest
413
465
  settings.security[:logout_requests_signed] = true # Enable or not signature on Logout Request
414
- settings.security[:logout_responses_signed] = true # Enable or not
466
+ settings.security[:logout_responses_signed] = true # Enable or not
415
467
  signature on Logout Response
416
- settings.security[:want_assertions_signed] = true # Enable or not
468
+ settings.security[:want_assertions_signed] = true # Enable or not
417
469
  the requirement of signed assertion
418
470
  settings.security[:metadata_signed] = true # Enable or not signature on Metadata
419
471
 
@@ -426,7 +478,7 @@ The settings related to sign are stored in the `security` attribute of the setti
426
478
  ```
427
479
 
428
480
  Notice that the RelayState parameter is used when creating the Signature on the HTTP-Redirect Binding.
429
- Remember to provide it to the Signature builder if you are sending a `GET RelayState` parameter or the
481
+ Remember to provide it to the Signature builder if you are sending a `GET RelayState` parameter or the
430
482
  signature validation process will fail at the Identity Provider.
431
483
 
432
484
  The Service Provider will sign the request/responses with its private key.
@@ -453,6 +505,12 @@ The Service Provider will decrypt the EncryptedAssertion with its private key.
453
505
 
454
506
  Notice that this toolkit uses 'settings.certificate' and 'settings.private_key' for the sign and decrypt processes.
455
507
 
508
+
509
+ ## Key rollover
510
+
511
+ If you plan to update the SP x509cert and privateKey you can define the parameter 'certificate_new' at the settings and that new SP public certificate will be published on the SP metadata so Identity Providers can read them and get ready for rollover.
512
+
513
+
456
514
  ## Single Log Out
457
515
 
458
516
  The Ruby Toolkit supports SP-initiated Single Logout and IdP-Initiated Single Logout.
@@ -507,10 +565,8 @@ def process_logout_response
507
565
  logger.error "The SAML Logout Response is invalid"
508
566
  else
509
567
  # Actually log out this session
510
- if logout_response.success?
511
- logger.info "Delete session for '#{session[:userid]}'"
512
- delete_session
513
- end
568
+ logger.info "Delete session for '#{session[:userid]}'"
569
+ delete_session
514
570
  end
515
571
  end
516
572
 
@@ -571,7 +627,7 @@ to the IdP for various good reasons. (Caching, certificate lookups, relaying pa
571
627
 
572
628
  The class `OneLogin::RubySaml::Metadata` takes care of this by reading the Settings and returning XML. All you have to do is add a controller to return the data, then give this URL to the IdP administrator.
573
629
 
574
- The metdata will be polled by the IdP every few minutes, so updating your settings should propagate
630
+ The metadata will be polled by the IdP every few minutes, so updating your settings should propagate
575
631
  to the IdP settings.
576
632
 
577
633
  ```ruby
@@ -585,6 +641,7 @@ class SamlController < ApplicationController
585
641
  end
586
642
  ```
587
643
 
644
+
588
645
  ## Clock Drift
589
646
 
590
647
  Server clocks tend to drift naturally. If during validation of the response you get the error "Current time is earlier than NotBefore condition", this may be due to clock differences between your system and that of the Identity Provider.
@@ -1,9 +1,22 @@
1
1
  # RubySaml Changelog
2
+ ### 1.4.3 (May 18, 2017)
3
+ * Added SubjectConfirmation Recipient validation
4
+ * [#393](https://github.com/onelogin/ruby-saml/pull/393) Implement IdpMetadataParser#parse_to_hash
5
+ * Adapt IdP XML metadata parser to take care of multiple IdP certificates and be able to inject the data obtained on the settings.
6
+ * Improve binding detection on idp metadata parser
7
+ * [#373](https://github.com/onelogin/ruby-saml/pull/373) Allow metadata to be retrieved from source containing data for multiple entities
8
+ * Be able to register future SP x509cert on the settings and publish it on SP metadata
9
+ * Be able to register more than 1 Identity Provider x509cert, linked with an specific use (signing or encryption.
10
+ * Improve regex to detect base64 encoded messages
11
+ * Fix binding configuration example in README.md
12
+ * Add Fix SLO request. Correct NameQualifier/SPNameQualifier values.
13
+ * Validate serial number as string to work around libxml2 limitation
14
+ * Propagate isRequired on md:RequestedAttribute when generating SP metadata
2
15
 
3
16
  ### 1.4.2 (January 11, 2017)
4
17
  * Improve tests format
5
18
  * Fix nokogiri requirements based on ruby version
6
- * Only publish KeyDescriptor[use="encryption"] at SP metadata if security[:want_assertions_encrypted] is true
19
+ * Only publish `KeyDescriptor[use="encryption"]` at SP metadata if `security[:want_assertions_encrypted]` is true
7
20
  * Be able to skip destination validation
8
21
  * Improved inResponse validation on SAMLResponses and LogoutResponses
9
22
  * [#354](https://github.com/onelogin/ruby-saml/pull/354) Allow scheme and domain to match ignoring case
@@ -22,39 +22,97 @@ module OneLogin
22
22
 
23
23
  attr_reader :document
24
24
  attr_reader :response
25
+ attr_reader :options
25
26
 
26
27
  # Parse the Identity Provider metadata and update the settings with the
27
28
  # IdP values
28
29
  #
29
- # @param (see IdpMetadataParser#get_idp_metadata)
30
- # @param options [Hash] :settings to provide the OneLogin::RubySaml::Settings object or an hash for Settings overrides
31
- # @return (see IdpMetadataParser#get_idp_metadata)
32
- # @raise (see IdpMetadataParser#get_idp_metadata)
30
+ # @param url [String] Url where the XML of the Identity Provider Metadata is published.
31
+ # @param validate_cert [Boolean] If true and the URL is HTTPs, the cert of the domain is checked.
32
+ #
33
+ # @param options [Hash] options used for parsing the metadata and the returned Settings instance
34
+ # @option options [OneLogin::RubySaml::Settings, Hash] :settings the OneLogin::RubySaml::Settings object which gets the parsed metadata merged into or an hash for Settings overrides.
35
+ # @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.
36
+ # @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.
37
+ # @option options [String, nil] :entity_id when this is given, the entity descriptor for this ID is used. When ommitted, the first entity descriptor is used.
38
+ #
39
+ # @return [OneLogin::RubySaml::Settings]
40
+ #
41
+ # @raise [HttpError] Failure to fetch remote IdP metadata
33
42
  def parse_remote(url, validate_cert = true, options = {})
34
43
  idp_metadata = get_idp_metadata(url, validate_cert)
35
44
  parse(idp_metadata, options)
36
45
  end
37
46
 
47
+ # Parse the Identity Provider metadata and return the results as Hash
48
+ #
49
+ # @param url [String] Url where the XML of the Identity Provider Metadata is published.
50
+ # @param validate_cert [Boolean] If true and the URL is HTTPs, the cert of the domain is checked.
51
+ #
52
+ # @param options [Hash] options used for parsing the metadata
53
+ # @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.
54
+ # @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.
55
+ # @option options [String, nil] :entity_id when this is given, the entity descriptor for this ID is used. When ommitted, the first entity descriptor is used.
56
+ #
57
+ # @return [Hash]
58
+ #
59
+ # @raise [HttpError] Failure to fetch remote IdP metadata
60
+ def parse_remote_to_hash(url, validate_cert = true, options = {})
61
+ idp_metadata = get_idp_metadata(url, validate_cert)
62
+ parse_to_hash(idp_metadata, options)
63
+ end
64
+
38
65
  # Parse the Identity Provider metadata and update the settings with the IdP values
39
- # @param idp_metadata [String]
40
- # @param options [Hash] :settings to provide the OneLogin::RubySaml::Settings object or an hash for Settings overrides
41
66
  #
67
+ # @param idp_metadata [String]
68
+ #
69
+ # @param options [Hash] :settings to provide the OneLogin::RubySaml::Settings object or an hash for Settings overrides
70
+ # @option options [OneLogin::RubySaml::Settings, Hash] :settings the OneLogin::RubySaml::Settings object which gets the parsed metadata merged into or an hash for Settings overrides.
71
+ # @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.
72
+ # @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.
73
+ # @option options [String, nil] :entity_id when this is given, the entity descriptor for this ID is used. When ommitted, the first entity descriptor is used.
74
+ #
75
+ # @return [OneLogin::RubySaml::Settings]
42
76
  def parse(idp_metadata, options = {})
43
- @document = REXML::Document.new(idp_metadata)
77
+ parsed_metadata = parse_to_hash(idp_metadata, options)
44
78
 
45
79
  settings = options[:settings]
46
- if settings.nil? || settings.is_a?(Hash)
47
- settings = OneLogin::RubySaml::Settings.new(settings || {})
80
+
81
+ if settings.nil?
82
+ OneLogin::RubySaml::Settings.new(parsed_metadata)
83
+ elsif settings.is_a?(Hash)
84
+ OneLogin::RubySaml::Settings.new(settings.merge(parsed_metadata))
85
+ else
86
+ merge_parsed_metadata_into(settings, parsed_metadata)
48
87
  end
88
+ end
49
89
 
50
- settings.tap do |settings|
51
- settings.idp_entity_id = idp_entity_id
52
- settings.name_identifier_format = idp_name_id_format
53
- settings.idp_sso_target_url = single_signon_service_url(options)
54
- settings.idp_slo_target_url = single_logout_service_url(options)
55
- settings.idp_cert = certificate_base64
56
- settings.idp_cert_fingerprint = fingerprint(settings.idp_cert_fingerprint_algorithm)
57
- settings.idp_attribute_names = attribute_names
90
+ # Parse the Identity Provider metadata and return the results as Hash
91
+ #
92
+ # @param idp_metadata [String]
93
+ #
94
+ # @param options [Hash] options used for parsing the metadata and the returned Settings instance
95
+ # @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.
96
+ # @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.
97
+ # @option options [String, nil] :entity_id when this is given, the entity descriptor for this ID is used. When ommitted, the first entity descriptor is used.
98
+ #
99
+ # @return [Hash]
100
+ def parse_to_hash(idp_metadata, options = {})
101
+ @document = REXML::Document.new(idp_metadata)
102
+ @options = options
103
+ @entity_descriptor = nil
104
+
105
+ {
106
+ :idp_entity_id => idp_entity_id,
107
+ :name_identifier_format => idp_name_id_format,
108
+ :idp_sso_target_url => single_signon_service_url(options),
109
+ :idp_slo_target_url => single_logout_service_url(options),
110
+ :idp_attribute_names => attribute_names,
111
+ :idp_cert => nil,
112
+ :idp_cert_fingerprint => nil,
113
+ :idp_cert_multi => nil
114
+ }.tap do |response_hash|
115
+ merge_certificates_into(response_hash) unless certificates.nil?
58
116
  end
59
117
  end
60
118
 
@@ -67,56 +125,58 @@ module OneLogin
67
125
  # @raise [HttpError] Failure to fetch remote IdP metadata
68
126
  def get_idp_metadata(url, validate_cert)
69
127
  uri = URI.parse(url)
70
- if uri.scheme == "http"
71
- response = Net::HTTP.get_response(uri)
72
- meta_text = response.body
73
- elsif uri.scheme == "https"
74
- http = Net::HTTP.new(uri.host, uri.port)
128
+ raise ArgumentError.new("url must begin with http or https") unless /^https?/ =~ uri.scheme
129
+ http = Net::HTTP.new(uri.host, uri.port)
130
+
131
+ if uri.scheme == "https"
75
132
  http.use_ssl = true
76
133
  # Most IdPs will probably use self signed certs
77
- if validate_cert
78
- http.verify_mode = OpenSSL::SSL::VERIFY_PEER
134
+ http.verify_mode = validate_cert ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
79
135
 
80
- # Net::HTTP in Ruby 1.8 did not set the default certificate store
81
- # automatically when VERIFY_PEER was specified.
82
- if RUBY_VERSION < '1.9' && !http.ca_file && !http.ca_path && !http.cert_store
83
- http.cert_store = OpenSSL::SSL::SSLContext::DEFAULT_CERT_STORE
84
- end
85
- else
86
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE
136
+ # Net::HTTP in Ruby 1.8 did not set the default certificate store
137
+ # automatically when VERIFY_PEER was specified.
138
+ if RUBY_VERSION < '1.9' && !http.ca_file && !http.ca_path && !http.cert_store
139
+ http.cert_store = OpenSSL::SSL::SSLContext::DEFAULT_CERT_STORE
87
140
  end
88
- get = Net::HTTP::Get.new(uri.request_uri)
89
- response = http.request(get)
90
- meta_text = response.body
91
- else
92
- raise ArgumentError.new("url must begin with http or https")
93
141
  end
94
142
 
95
- unless response.is_a? Net::HTTPSuccess
96
- raise OneLogin::RubySaml::HttpError.new("Failed to fetch idp metadata")
97
- end
143
+ get = Net::HTTP::Get.new(uri.request_uri)
144
+ response = http.request(get)
145
+ return response.body if response.is_a? Net::HTTPSuccess
98
146
 
99
- meta_text
147
+ raise OneLogin::RubySaml::HttpError.new(
148
+ "Failed to fetch idp metadata: #{response.code}: #{response.message}"
149
+ )
150
+ end
151
+
152
+ def entity_descriptor
153
+ @entity_descriptor ||= REXML::XPath.first(
154
+ document,
155
+ entity_descriptor_path,
156
+ namespace
157
+ )
158
+ end
159
+
160
+ def entity_descriptor_path
161
+ path = "//md:EntityDescriptor"
162
+ entity_id = options[:entity_id]
163
+ return path unless entity_id
164
+ path << "[@entityID=\"#{entity_id}\"]"
100
165
  end
101
166
 
102
167
  # @return [String|nil] IdP Entity ID value if exists
103
168
  #
104
169
  def idp_entity_id
105
- node = REXML::XPath.first(
106
- document,
107
- "/md:EntityDescriptor/@entityID",
108
- { "md" => METADATA }
109
- )
110
- node.value if node
170
+ entity_descriptor.attributes["entityID"]
111
171
  end
112
172
 
113
173
  # @return [String|nil] IdP Name ID Format value if exists
114
174
  #
115
175
  def idp_name_id_format
116
176
  node = REXML::XPath.first(
117
- document,
118
- "/md:EntityDescriptor/md:IDPSSODescriptor/md:NameIDFormat",
119
- { "md" => METADATA }
177
+ entity_descriptor,
178
+ "md:IDPSSODescriptor/md:NameIDFormat",
179
+ namespace
120
180
  )
121
181
  node.text if node
122
182
  end
@@ -126,9 +186,9 @@ module OneLogin
126
186
  #
127
187
  def single_signon_service_binding(binding_priority = nil)
128
188
  nodes = REXML::XPath.match(
129
- document,
130
- "/md:EntityDescriptor/md:IDPSSODescriptor/md:SingleSignOnService/@Binding",
131
- { "md" => METADATA }
189
+ entity_descriptor,
190
+ "md:IDPSSODescriptor/md:SingleSignOnService/@Binding",
191
+ namespace
132
192
  )
133
193
  if binding_priority
134
194
  values = nodes.map(&:value)
@@ -142,13 +202,15 @@ module OneLogin
142
202
  # @return [String|nil] SingleSignOnService endpoint if exists
143
203
  #
144
204
  def single_signon_service_url(options = {})
145
- binding = options[:sso_binding] || "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
146
- node = REXML::XPath.first(
147
- document,
148
- "/md:EntityDescriptor/md:IDPSSODescriptor/md:SingleSignOnService[@Binding=\"#{binding}\"]/@Location",
149
- { "md" => METADATA }
150
- )
151
- node.value if node
205
+ binding = single_signon_service_binding(options[:sso_binding])
206
+ unless binding.nil?
207
+ node = REXML::XPath.first(
208
+ entity_descriptor,
209
+ "md:IDPSSODescriptor/md:SingleSignOnService[@Binding=\"#{binding}\"]/@Location",
210
+ namespace
211
+ )
212
+ return node.value if node
213
+ end
152
214
  end
153
215
 
154
216
  # @param binding_priority [Array]
@@ -156,9 +218,9 @@ module OneLogin
156
218
  #
157
219
  def single_logout_service_binding(binding_priority = nil)
158
220
  nodes = REXML::XPath.match(
159
- document,
160
- "/md:EntityDescriptor/md:IDPSSODescriptor/md:SingleLogoutService/@Binding",
161
- { "md" => METADATA }
221
+ entity_descriptor,
222
+ "md:IDPSSODescriptor/md:SingleLogoutService/@Binding",
223
+ namespace
162
224
  )
163
225
  if binding_priority
164
226
  values = nodes.map(&:value)
@@ -172,51 +234,60 @@ module OneLogin
172
234
  # @return [String|nil] SingleLogoutService endpoint if exists
173
235
  #
174
236
  def single_logout_service_url(options = {})
175
- binding = options[:slo_binding] || "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
176
- node = REXML::XPath.first(
177
- document,
178
- "/md:EntityDescriptor/md:IDPSSODescriptor/md:SingleLogoutService[@Binding=\"#{binding}\"]/@Location",
179
- { "md" => METADATA }
180
- )
181
- node.value if node
237
+ binding = single_logout_service_binding(options[:slo_binding])
238
+ unless binding.nil?
239
+ node = REXML::XPath.first(
240
+ entity_descriptor,
241
+ "md:IDPSSODescriptor/md:SingleLogoutService[@Binding=\"#{binding}\"]/@Location",
242
+ namespace
243
+ )
244
+ return node.value if node
245
+ end
182
246
  end
183
247
 
184
248
  # @return [String|nil] Unformatted Certificate if exists
185
249
  #
186
- def certificate_base64
187
- @certificate_base64 ||= begin
188
- node = REXML::XPath.first(
189
- document,
190
- "/md:EntityDescriptor/md:IDPSSODescriptor/md:KeyDescriptor[@use='signing']/ds:KeyInfo/ds:X509Data/ds:X509Certificate",
191
- { "md" => METADATA, "ds" => DSIG }
250
+ def certificates
251
+ @certificates ||= begin
252
+ signing_nodes = REXML::XPath.match(
253
+ entity_descriptor,
254
+ "md:IDPSSODescriptor/md:KeyDescriptor[not(contains(@use, 'encryption'))]/ds:KeyInfo/ds:X509Data/ds:X509Certificate",
255
+ namespace
192
256
  )
193
257
 
194
- unless node
195
- node = REXML::XPath.first(
196
- document,
197
- "/md:EntityDescriptor/md:IDPSSODescriptor/md:KeyDescriptor/ds:KeyInfo/ds:X509Data/ds:X509Certificate",
198
- { "md" => METADATA, "ds" => DSIG }
199
- )
200
- end
201
- node.text if node
202
- end
203
- end
258
+ encryption_nodes = REXML::XPath.match(
259
+ entity_descriptor,
260
+ "md:IDPSSODescriptor/md:KeyDescriptor[not(contains(@use, 'signing'))]/ds:KeyInfo/ds:X509Data/ds:X509Certificate",
261
+ namespace
262
+ )
204
263
 
205
- # @return [String|nil] X509Certificate if exists
206
- #
207
- def certificate
208
- @certificate ||= begin
209
- Base64.decode64(certificate_base64) if certificate_base64
264
+ certs = nil
265
+ unless signing_nodes.empty? && encryption_nodes.empty?
266
+ certs = {}
267
+ unless signing_nodes.empty?
268
+ certs['signing'] = []
269
+ signing_nodes.each do |cert_node|
270
+ certs['signing'] << cert_node.text
271
+ end
272
+ end
273
+
274
+ unless encryption_nodes.empty?
275
+ certs['encryption'] = []
276
+ encryption_nodes.each do |cert_node|
277
+ certs['encryption'] << cert_node.text
278
+ end
279
+ end
280
+ end
281
+ certs
210
282
  end
211
283
  end
212
284
 
213
-
214
- # @return [String|nil] the SHA-1 fingerpint of the X509Certificate if it exists
285
+ # @return [String|nil] the fingerpint of the X509Certificate if it exists
215
286
  #
216
- def fingerprint(fingerprint_algorithm = XMLSecurity::Document::SHA1)
287
+ def fingerprint(certificate, fingerprint_algorithm = XMLSecurity::Document::SHA1)
217
288
  @fingerprint ||= begin
218
289
  if certificate
219
- cert = OpenSSL::X509::Certificate.new(certificate)
290
+ cert = OpenSSL::X509::Certificate.new(Base64.decode64(certificate))
220
291
 
221
292
  fingerprint_alg = XMLSecurity::BaseDocument.new.algorithm(fingerprint_algorithm).new
222
293
  fingerprint_alg.hexdigest(cert.to_der).upcase.scan(/../).join(":")
@@ -228,12 +299,53 @@ module OneLogin
228
299
  #
229
300
  def attribute_names
230
301
  nodes = REXML::XPath.match(
231
- document,
232
- "/md:EntityDescriptor/md:IDPSSODescriptor/saml:Attribute/@Name",
233
- { "md" => METADATA, "NameFormat" => NAME_FORMAT, "saml" => SAML_ASSERTION }
302
+ entity_descriptor,
303
+ "md:IDPSSODescriptor/saml:Attribute/@Name",
304
+ namespace
234
305
  )
235
306
  nodes.map(&:value)
236
307
  end
308
+
309
+ def namespace
310
+ {
311
+ "md" => METADATA,
312
+ "NameFormat" => NAME_FORMAT,
313
+ "saml" => SAML_ASSERTION,
314
+ "ds" => DSIG
315
+ }
316
+ end
317
+
318
+ def merge_certificates_into(parsed_metadata)
319
+ if certificates.size == 1 ||
320
+ ((certificates.key?("signing") && certificates["signing"].size == 1) &&
321
+ (certificates.key?("encryption") && certificates["encryption"].size == 1) &&
322
+ certificates["signing"][0] == certificates["encryption"][0])
323
+
324
+ if certificates.key?("signing")
325
+ parsed_metadata[:idp_cert] = certificates["signing"][0]
326
+ parsed_metadata[:idp_cert_fingerprint] = fingerprint(
327
+ parsed_metadata[:idp_cert],
328
+ parsed_metadata[:idp_cert_fingerprint_algorithm]
329
+ )
330
+ else
331
+ parsed_metadata[:idp_cert] = certificates["encryption"][0]
332
+ parsed_metadata[:idp_cert_fingerprint] = fingerprint(
333
+ parsed_metadata[:idp_cert],
334
+ parsed_metadata[:idp_cert_fingerprint_algorithm]
335
+ )
336
+ end
337
+ else
338
+ parsed_metadata[:idp_cert_multi] = certificates
339
+ end
340
+ end
341
+
342
+ def merge_parsed_metadata_into(settings, parsed_metadata)
343
+ parsed_metadata.each do |key, value|
344
+ settings.send("#{key}=".to_sym, value)
345
+ end
346
+
347
+ settings
348
+ end
237
349
  end
238
350
  end
239
351
  end