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 +4 -4
- data/README.md +70 -13
- data/changelog.md +14 -1
- data/lib/onelogin/ruby-saml/idp_metadata_parser.rb +209 -97
- data/lib/onelogin/ruby-saml/logoutrequest.rb +2 -1
- data/lib/onelogin/ruby-saml/logoutresponse.rb +31 -8
- data/lib/onelogin/ruby-saml/metadata.rb +20 -14
- data/lib/onelogin/ruby-saml/response.rb +32 -15
- data/lib/onelogin/ruby-saml/saml_message.rb +1 -2
- data/lib/onelogin/ruby-saml/settings.rb +39 -1
- data/lib/onelogin/ruby-saml/slo_logoutrequest.rb +29 -7
- data/lib/onelogin/ruby-saml/version.rb +1 -1
- data/lib/schemas/xmldsig-core-schema.xsd +1 -1
- data/lib/xml_security.rb +25 -0
- data/test/certificates/ruby-saml-2.crt +15 -0
- data/test/idp_metadata_parser_test.rb +211 -15
- data/test/logoutresponse_test.rb +60 -0
- data/test/metadata/idp_descriptor.xml +26 -0
- data/test/metadata/idp_descriptor_2.xml +56 -0
- data/test/metadata/idp_descriptor_3.xml +14 -0
- data/test/metadata/idp_multiple_descriptors.xml +53 -0
- data/test/metadata_test.rb +70 -2
- data/test/response_test.rb +289 -243
- data/test/settings_test.rb +105 -22
- data/test/slo_logoutrequest_test.rb +66 -0
- data/test/test_helper.rb +23 -3
- metadata +13 -5
- data/test/responses/idp_descriptor.xml +0 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 455ea502cffeb8d8b1c0bc673de3495e0e1b070a
|
4
|
+
data.tar.gz: f5327bfb4d61922b77444f397a8347910a6bab33
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
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`
|
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
|
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
|
-
*
|
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
|
-
|
511
|
-
|
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
|
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.
|
data/changelog.md
CHANGED
@@ -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
|
30
|
-
# @param
|
31
|
-
#
|
32
|
-
# @
|
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
|
-
|
77
|
+
parsed_metadata = parse_to_hash(idp_metadata, options)
|
44
78
|
|
45
79
|
settings = options[:settings]
|
46
|
-
|
47
|
-
|
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
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
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
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
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
|
-
|
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
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
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
|
-
|
96
|
-
|
97
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
118
|
-
"
|
119
|
-
|
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
|
-
|
130
|
-
"
|
131
|
-
|
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]
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
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
|
-
|
160
|
-
"
|
161
|
-
|
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]
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
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
|
187
|
-
@
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
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
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
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
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
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
|
-
|
232
|
-
"
|
233
|
-
|
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
|