ruby-saml 1.11.0 → 1.14.0
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 +5 -5
- data/.github/workflows/test.yml +25 -0
- data/{changelog.md → CHANGELOG.md} +49 -1
- data/README.md +363 -218
- data/UPGRADING.md +149 -0
- data/lib/onelogin/ruby-saml/attributes.rb +24 -1
- data/lib/onelogin/ruby-saml/authrequest.rb +12 -8
- data/lib/onelogin/ruby-saml/idp_metadata_parser.rb +154 -83
- data/lib/onelogin/ruby-saml/logoutrequest.rb +13 -7
- data/lib/onelogin/ruby-saml/logoutresponse.rb +6 -2
- data/lib/onelogin/ruby-saml/metadata.rb +62 -17
- data/lib/onelogin/ruby-saml/response.rb +57 -32
- data/lib/onelogin/ruby-saml/saml_message.rb +8 -3
- data/lib/onelogin/ruby-saml/setting_error.rb +6 -0
- data/lib/onelogin/ruby-saml/settings.rb +92 -50
- data/lib/onelogin/ruby-saml/slo_logoutrequest.rb +17 -30
- data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +32 -18
- data/lib/onelogin/ruby-saml/utils.rb +83 -8
- data/lib/onelogin/ruby-saml/version.rb +1 -1
- data/lib/xml_security.rb +39 -13
- data/ruby-saml.gemspec +14 -5
- metadata +29 -288
- data/.travis.yml +0 -46
- data/test/certificates/certificate.der +0 -0
- data/test/certificates/certificate1 +0 -12
- data/test/certificates/certificate_without_head_foot +0 -1
- data/test/certificates/formatted_certificate +0 -14
- data/test/certificates/formatted_chained_certificate +0 -42
- data/test/certificates/formatted_private_key +0 -12
- data/test/certificates/formatted_rsa_private_key +0 -12
- data/test/certificates/invalid_certificate1 +0 -1
- data/test/certificates/invalid_certificate2 +0 -1
- data/test/certificates/invalid_certificate3 +0 -12
- data/test/certificates/invalid_chained_certificate1 +0 -1
- data/test/certificates/invalid_private_key1 +0 -1
- data/test/certificates/invalid_private_key2 +0 -1
- data/test/certificates/invalid_private_key3 +0 -10
- data/test/certificates/invalid_rsa_private_key1 +0 -1
- data/test/certificates/invalid_rsa_private_key2 +0 -1
- data/test/certificates/invalid_rsa_private_key3 +0 -10
- data/test/certificates/ruby-saml-2.crt +0 -15
- data/test/certificates/ruby-saml.crt +0 -14
- data/test/certificates/ruby-saml.key +0 -15
- data/test/idp_metadata_parser_test.rb +0 -594
- data/test/logging_test.rb +0 -62
- data/test/logout_requests/invalid_slo_request.xml +0 -6
- data/test/logout_requests/slo_request.xml +0 -4
- data/test/logout_requests/slo_request.xml.base64 +0 -1
- data/test/logout_requests/slo_request_deflated.xml.base64 +0 -1
- data/test/logout_requests/slo_request_with_name_id_format.xml +0 -4
- data/test/logout_requests/slo_request_with_session_index.xml +0 -5
- data/test/logout_responses/logoutresponse_fixtures.rb +0 -86
- data/test/logoutrequest_test.rb +0 -260
- data/test/logoutresponse_test.rb +0 -427
- data/test/metadata/idp_descriptor.xml +0 -26
- data/test/metadata/idp_descriptor_2.xml +0 -56
- data/test/metadata/idp_descriptor_3.xml +0 -14
- data/test/metadata/idp_descriptor_4.xml +0 -72
- data/test/metadata/idp_metadata_different_sign_and_encrypt_cert.xml +0 -72
- data/test/metadata/idp_metadata_multi_certs.xml +0 -75
- data/test/metadata/idp_metadata_multi_signing_certs.xml +0 -52
- data/test/metadata/idp_metadata_same_sign_and_encrypt_cert.xml +0 -71
- data/test/metadata/idp_multiple_descriptors.xml +0 -59
- data/test/metadata/idp_multiple_descriptors_2.xml +0 -59
- data/test/metadata/no_idp_descriptor.xml +0 -21
- data/test/metadata_test.rb +0 -331
- data/test/request_test.rb +0 -340
- data/test/response_test.rb +0 -1629
- data/test/responses/adfs_response_sha1.xml +0 -46
- data/test/responses/adfs_response_sha256.xml +0 -46
- data/test/responses/adfs_response_sha384.xml +0 -46
- data/test/responses/adfs_response_sha512.xml +0 -46
- data/test/responses/adfs_response_xmlns.xml +0 -45
- data/test/responses/attackxee.xml +0 -13
- data/test/responses/invalids/duplicated_attributes.xml.base64 +0 -1
- data/test/responses/invalids/empty_destination.xml.base64 +0 -1
- data/test/responses/invalids/empty_nameid.xml.base64 +0 -1
- data/test/responses/invalids/encrypted_new_attack.xml.base64 +0 -1
- data/test/responses/invalids/invalid_audience.xml.base64 +0 -1
- data/test/responses/invalids/invalid_issuer_assertion.xml.base64 +0 -1
- data/test/responses/invalids/invalid_issuer_message.xml.base64 +0 -1
- data/test/responses/invalids/invalid_signature_position.xml.base64 +0 -1
- data/test/responses/invalids/invalid_subjectconfirmation_inresponse.xml.base64 +0 -1
- data/test/responses/invalids/invalid_subjectconfirmation_nb.xml.base64 +0 -1
- data/test/responses/invalids/invalid_subjectconfirmation_noa.xml.base64 +0 -1
- data/test/responses/invalids/invalid_subjectconfirmation_recipient.xml.base64 +0 -1
- data/test/responses/invalids/multiple_assertions.xml.base64 +0 -2
- data/test/responses/invalids/multiple_signed.xml.base64 +0 -1
- data/test/responses/invalids/no_authnstatement.xml.base64 +0 -1
- data/test/responses/invalids/no_conditions.xml.base64 +0 -1
- data/test/responses/invalids/no_id.xml.base64 +0 -1
- data/test/responses/invalids/no_issuer_assertion.xml.base64 +0 -1
- data/test/responses/invalids/no_issuer_response.xml.base64 +0 -1
- data/test/responses/invalids/no_nameid.xml.base64 +0 -1
- data/test/responses/invalids/no_saml2.xml.base64 +0 -1
- data/test/responses/invalids/no_signature.xml.base64 +0 -1
- data/test/responses/invalids/no_status.xml.base64 +0 -1
- data/test/responses/invalids/no_status_code.xml.base64 +0 -1
- data/test/responses/invalids/no_subjectconfirmation_data.xml.base64 +0 -1
- data/test/responses/invalids/no_subjectconfirmation_method.xml.base64 +0 -1
- data/test/responses/invalids/response_invalid_signed_element.xml.base64 +0 -1
- data/test/responses/invalids/response_with_concealed_signed_assertion.xml +0 -51
- data/test/responses/invalids/response_with_doubled_signed_assertion.xml +0 -49
- data/test/responses/invalids/signature_wrapping_attack.xml.base64 +0 -1
- data/test/responses/invalids/status_code_responder.xml.base64 +0 -1
- data/test/responses/invalids/status_code_responer_and_msg.xml.base64 +0 -1
- data/test/responses/invalids/wrong_spnamequalifier.xml.base64 +0 -1
- data/test/responses/no_signature_ns.xml +0 -48
- data/test/responses/open_saml_response.xml +0 -56
- data/test/responses/response_assertion_wrapped.xml.base64 +0 -93
- data/test/responses/response_audience_self_closed_tag.xml.base64 +0 -1
- data/test/responses/response_double_status_code.xml.base64 +0 -1
- data/test/responses/response_encrypted_attrs.xml.base64 +0 -1
- data/test/responses/response_encrypted_nameid.xml.base64 +0 -1
- data/test/responses/response_eval.xml +0 -7
- data/test/responses/response_no_cert_and_encrypted_attrs.xml +0 -29
- data/test/responses/response_node_text_attack.xml.base64 +0 -1
- data/test/responses/response_node_text_attack2.xml.base64 +0 -1
- data/test/responses/response_node_text_attack3.xml.base64 +0 -1
- data/test/responses/response_unsigned_xml_base64 +0 -1
- data/test/responses/response_with_ampersands.xml +0 -139
- data/test/responses/response_with_ampersands.xml.base64 +0 -93
- data/test/responses/response_with_ds_namespace_at_the_root.xml.base64 +0 -1
- data/test/responses/response_with_multiple_attribute_statements.xml +0 -72
- data/test/responses/response_with_multiple_attribute_values.xml +0 -67
- data/test/responses/response_with_retrieval_method.xml +0 -26
- data/test/responses/response_with_saml2_namespace.xml.base64 +0 -102
- data/test/responses/response_with_signed_assertion.xml.base64 +0 -66
- data/test/responses/response_with_signed_assertion_2.xml.base64 +0 -1
- data/test/responses/response_with_signed_assertion_3.xml +0 -30
- data/test/responses/response_with_signed_message_and_assertion.xml +0 -34
- data/test/responses/response_with_undefined_recipient.xml.base64 +0 -1
- data/test/responses/response_without_attributes.xml.base64 +0 -79
- data/test/responses/response_without_reference_uri.xml.base64 +0 -1
- data/test/responses/response_wrapped.xml.base64 +0 -150
- data/test/responses/signed_message_encrypted_signed_assertion.xml.base64 +0 -1
- data/test/responses/signed_message_encrypted_unsigned_assertion.xml.base64 +0 -1
- data/test/responses/signed_nameid_in_atts.xml +0 -47
- data/test/responses/signed_unqual_nameid_in_atts.xml +0 -47
- data/test/responses/simple_saml_php.xml +0 -71
- data/test/responses/starfield_response.xml.base64 +0 -1
- data/test/responses/test_sign.xml +0 -43
- data/test/responses/unsigned_encrypted_adfs.xml +0 -23
- data/test/responses/unsigned_message_aes128_encrypted_signed_assertion.xml.base64 +0 -1
- data/test/responses/unsigned_message_aes192_encrypted_signed_assertion.xml.base64 +0 -1
- data/test/responses/unsigned_message_aes256_encrypted_signed_assertion.xml.base64 +0 -1
- data/test/responses/unsigned_message_des192_encrypted_signed_assertion.xml.base64 +0 -1
- data/test/responses/unsigned_message_encrypted_assertion_without_saml_namespace.xml.base64 +0 -1
- data/test/responses/unsigned_message_encrypted_signed_assertion.xml.base64 +0 -1
- data/test/responses/unsigned_message_encrypted_unsigned_assertion.xml.base64 +0 -1
- data/test/responses/valid_response.xml.base64 +0 -1
- data/test/responses/valid_response_with_formatted_x509certificate.xml.base64 +0 -1
- data/test/responses/valid_response_without_x509certificate.xml.base64 +0 -1
- data/test/saml_message_test.rb +0 -56
- data/test/settings_test.rb +0 -338
- data/test/slo_logoutrequest_test.rb +0 -467
- data/test/slo_logoutresponse_test.rb +0 -233
- data/test/test_helper.rb +0 -333
- data/test/utils_test.rb +0 -259
- data/test/xml_security_test.rb +0 -421
data/README.md
CHANGED
@@ -1,129 +1,61 @@
|
|
1
|
-
# Ruby SAML
|
1
|
+
# Ruby SAML
|
2
|
+
[![Build Status](https://github.com/onelogin/ruby-saml/actions/workflows/test.yml/badge.svg?query=branch%3Amaster)](https://github.com/onelogin/ruby-saml/actions/workflows/test.yml?query=branch%3Amaster)
|
3
|
+
[![Coverage Status](https://coveralls.io/repos/onelogin/ruby-saml/badge.svg?branch=master)](https://coveralls.io/r/onelogin/ruby-saml?branch=master)
|
2
4
|
|
3
|
-
|
4
|
-
|
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
|
-
|
9
|
-
## Updating from 1.7.X to 1.8.0
|
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.
|
11
|
-
|
12
|
-
## Updating from 1.6.0 to 1.7.0
|
13
|
-
|
14
|
-
Version `1.7.0` is a recommended update for all Ruby SAML users as it includes a fix for the [CVE-2017-11428](https://www.cvedetails.com/cve/CVE-2017-11428/) vulnerability.
|
15
|
-
|
16
|
-
## Updating from 1.5.0 to 1.6.0
|
17
|
-
|
18
|
-
Version `1.6.0` changes the preferred way to construct instances of `Logoutresponse` and `SloLogoutrequest`. Previously the _SAMLResponse_, _RelayState_, and _SigAlg_ parameters of these message types were provided via the constructor's `options[:get_params]` parameter. Unfortunately this can result in incompatibility with other SAML implementations; signatures are specified to be computed based on the _sender's_ URI-encoding of the message, which can differ from that of Ruby SAML. In particular, Ruby SAML's URI-encoding does not match that of Microsoft ADFS, so messages from ADFS can fail signature validation.
|
19
|
-
|
20
|
-
The new preferred way to provide _SAMLResponse_, _RelayState_, and _SigAlg_ is via the `options[:raw_get_params]` parameter. For example:
|
21
|
-
|
22
|
-
```ruby
|
23
|
-
# In this example `query_params` is assumed to contain decoded query parameters,
|
24
|
-
# and `raw_query_params` is assumed to contain encoded query parameters as sent by the IDP.
|
25
|
-
settings = {
|
26
|
-
settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1
|
27
|
-
settings.soft = false
|
28
|
-
}
|
29
|
-
options = {
|
30
|
-
get_params: {
|
31
|
-
"Signature" => query_params["Signature"],
|
32
|
-
},
|
33
|
-
raw_get_params: {
|
34
|
-
"SAMLRequest" => raw_query_params["SAMLRequest"],
|
35
|
-
"SigAlg" => raw_query_params["SigAlg"],
|
36
|
-
"RelayState" => raw_query_params["RelayState"],
|
37
|
-
},
|
38
|
-
}
|
39
|
-
slo_logout_request = OneLogin::RubySaml::SloLogoutrequest.new(query_params["SAMLRequest"], settings, options)
|
40
|
-
raise "Invalid Logout Request" unless slo_logout_request.is_valid?
|
41
|
-
```
|
42
|
-
|
43
|
-
The old form is still supported for backward compatibility, but all Ruby SAML users should prefer `options[:raw_get_params]` where possible to ensure compatibility with other SAML implementations.
|
44
|
-
|
45
|
-
## Updating from 1.4.2 to 1.4.3
|
46
|
-
|
47
|
-
Version `1.4.3` introduces Recipient validation of SubjectConfirmation elements.
|
48
|
-
The 'Recipient' value is compared with the settings.assertion_consumer_service_url
|
49
|
-
value.
|
50
|
-
If you want to skip that validation, add the :skip_recipient_check option to the
|
51
|
-
initialize method of the Response object.
|
52
|
-
|
53
|
-
Parsing metadata that contains more than one certificate will propagate the
|
54
|
-
idp_cert_multi property rather than idp_cert. See [signature validation
|
55
|
-
section](#signature-validation) for details.
|
56
|
-
|
57
|
-
## Updating from 1.3.x to 1.4.X
|
58
|
-
|
59
|
-
Version `1.4.0` is a recommended update for all Ruby SAML users as it includes security improvements.
|
60
|
-
|
61
|
-
## Updating from 1.2.x to 1.3.X
|
62
|
-
|
63
|
-
Version `1.3.0` is a recommended update for all Ruby SAML users as it includes security fixes. It adds security improvements in order to prevent Signature wrapping attacks. [CVE-2016-5697](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-5697)
|
64
|
-
|
65
|
-
## Updating from 1.1.x to 1.2.X
|
66
|
-
|
67
|
-
Version `1.2` adds IDP metadata parsing improvements, uuid deprecation in favour of SecureRandom, refactor error handling and some minor improvements
|
68
|
-
|
69
|
-
There is no compatibility issue detected.
|
70
|
-
|
71
|
-
For more details, please review [the changelog](changelog.md).
|
72
|
-
|
73
|
-
## Updating from 1.0.x to 1.1.X
|
74
|
-
|
75
|
-
Version `1.1` adds some improvements on signature validation and solves some namespace conflicts.
|
76
|
-
|
77
|
-
## Updating from 0.9.x to 1.0.X
|
78
|
-
|
79
|
-
Version `1.0` is a recommended update for all Ruby SAML users as it includes security fixes.
|
80
|
-
|
81
|
-
Version `1.0` adds security improvements like entity expansion limitation, more SAML message validations, and other important improvements like decrypt support.
|
82
|
-
|
83
|
-
### Important Changes
|
84
|
-
Please note the `get_idp_metadata` method raises an exception when it is not able to fetch the idp metadata, so review your integration if you are using this functionality.
|
85
|
-
|
86
|
-
## Updating from 0.8.x to 0.9.x
|
87
|
-
Version `0.9` adds many new features and improvements.
|
88
|
-
|
89
|
-
## Updating from 0.7.x to 0.8.x
|
90
|
-
Version `0.8.x` changes the namespace of the gem from `OneLogin::Saml` to `OneLogin::RubySaml`. Please update your implementations of the gem accordingly.
|
5
|
+
Ruby SAML minor and tiny versions may introduce breaking changes. Please read
|
6
|
+
[UPGRADING.md](UPGRADING.md) for guidance on upgrading to new Ruby SAML versions.
|
91
7
|
|
92
8
|
## Overview
|
93
9
|
|
94
|
-
The Ruby SAML library is for implementing the client side of a SAML authorization,
|
10
|
+
The Ruby SAML library is for implementing the client side of a SAML authorization,
|
11
|
+
i.e. it provides a means for managing authorization initialization and confirmation
|
12
|
+
requests from identity providers.
|
95
13
|
|
96
14
|
SAML authorization is a two step process and you are expected to implement support for both.
|
97
15
|
|
98
|
-
We created a demo project for
|
16
|
+
We created a demo project for Rails 4 that uses the latest version of this library:
|
17
|
+
[ruby-saml-example](https://github.com/onelogin/ruby-saml-example)
|
18
|
+
|
19
|
+
### Supported Ruby Versions
|
20
|
+
|
21
|
+
The following Ruby versions are covered by CI testing:
|
99
22
|
|
100
|
-
### Supported versions of Ruby
|
101
|
-
* 1.8.7
|
102
|
-
* 1.9.x
|
103
|
-
* 2.0.x
|
104
23
|
* 2.1.x
|
105
24
|
* 2.2.x
|
106
25
|
* 2.3.x
|
107
26
|
* 2.4.x
|
108
27
|
* 2.5.x
|
109
28
|
* 2.6.x
|
110
|
-
*
|
111
|
-
*
|
112
|
-
* JRuby 9.
|
29
|
+
* 2.7.x
|
30
|
+
* 3.0.x
|
31
|
+
* JRuby 9.1.x
|
32
|
+
* JRuby 9.2.x
|
33
|
+
* TruffleRuby (latest)
|
34
|
+
|
35
|
+
In addition, the following may work but are untested:
|
36
|
+
|
37
|
+
* 1.8.7
|
38
|
+
* 1.9.x
|
39
|
+
* 2.0.x
|
40
|
+
* JRuby 1.7.x
|
41
|
+
* JRuby 9.0.x
|
113
42
|
|
114
43
|
## Adding Features, Pull Requests
|
44
|
+
|
115
45
|
* Fork the repository
|
116
46
|
* Make your feature addition or bug fix
|
117
47
|
* Add tests for your new features. This is important so we don't break any features in a future version unintentionally.
|
118
|
-
* Ensure all tests pass
|
48
|
+
* Ensure all tests pass by running `bundle exec rake test`.
|
119
49
|
* Do not change rakefile, version, or history.
|
120
50
|
* Open a pull request, following [this template](https://gist.github.com/Lordnibbler/11002759).
|
121
51
|
|
122
52
|
## Security Guidelines
|
123
53
|
|
124
|
-
If you believe you have discovered a security vulnerability in this gem, please report it
|
54
|
+
If you believe you have discovered a security vulnerability in this gem, please report it
|
55
|
+
at https://www.onelogin.com/security with a description. We follow responsible disclosure
|
56
|
+
guidelines, and will work with you to quickly find a resolution.
|
125
57
|
|
126
|
-
### Security
|
58
|
+
### Security Warning
|
127
59
|
|
128
60
|
Some tools may incorrectly report ruby-saml is a potential security vulnerability.
|
129
61
|
ruby-saml depends on Nokogiri, and it's possible to use Nokogiri in a dangerous way
|
@@ -133,15 +65,26 @@ can create an XML External Entity (XXE) vulnerability if the XML data is not tru
|
|
133
65
|
However, ruby-saml never enables this dangerous Nokogiri configuration;
|
134
66
|
ruby-saml never enables DTDLOAD, and it never disables NONET.
|
135
67
|
|
68
|
+
The OneLogin::RubySaml::IdpMetadataParser class does not validate in any way the URL
|
69
|
+
that is introduced in order to be parsed.
|
70
|
+
|
71
|
+
Usually the same administrator that handles the Service Provider also sets the URL to
|
72
|
+
the IdP, which should be a trusted resource.
|
73
|
+
|
74
|
+
But there are other scenarios, like a SAAS app where the administrator of the app
|
75
|
+
delegates this functionality to other users. In this case, extra precaution should
|
76
|
+
be taken in order to validate such URL inputs and avoid attacks like SSRF.
|
136
77
|
|
137
78
|
## Getting Started
|
138
|
-
|
79
|
+
|
80
|
+
In order to use Ruby SAML you will need to install the gem (either manually or using Bundler),
|
81
|
+
and require the library in your Ruby application:
|
139
82
|
|
140
83
|
Using `Gemfile`
|
141
84
|
|
142
85
|
```ruby
|
143
86
|
# latest stable
|
144
|
-
gem 'ruby-saml', '~> 1.
|
87
|
+
gem 'ruby-saml', '~> 1.11.0'
|
145
88
|
|
146
89
|
# or track master for bleeding-edge
|
147
90
|
gem 'ruby-saml', :github => 'onelogin/ruby-saml'
|
@@ -153,7 +96,8 @@ Using RubyGems
|
|
153
96
|
gem install ruby-saml
|
154
97
|
```
|
155
98
|
|
156
|
-
|
99
|
+
You may require the entire Ruby SAML gem:
|
100
|
+
|
157
101
|
```ruby
|
158
102
|
require 'onelogin/ruby-saml'
|
159
103
|
```
|
@@ -166,7 +110,9 @@ require 'onelogin/ruby-saml/authrequest'
|
|
166
110
|
|
167
111
|
### Installation on Ruby 1.8.7
|
168
112
|
|
169
|
-
This gem uses Nokogiri as a dependency, which dropped support for Ruby 1.8.x in Nokogiri 1.6.
|
113
|
+
This gem uses Nokogiri as a dependency, which dropped support for Ruby 1.8.x in Nokogiri 1.6.
|
114
|
+
When installing this gem on Ruby 1.8.7, you will need to make sure a version of Nokogiri
|
115
|
+
prior to 1.6 is installed or specified if it hasn't been already.
|
170
116
|
|
171
117
|
Using `Gemfile`
|
172
118
|
|
@@ -195,7 +141,10 @@ OneLogin::RubySaml::Logging.logger = Logger.new('/var/log/ruby-saml.log')
|
|
195
141
|
|
196
142
|
## The Initialization Phase
|
197
143
|
|
198
|
-
This is the first request you will get from the identity provider. It will hit your application
|
144
|
+
This is the first request you will get from the identity provider. It will hit your application
|
145
|
+
at a specific URL that you've announced as your SAML initialization point. The response to
|
146
|
+
this initialization is a redirect back to the identity provider, which can look something
|
147
|
+
like this (ignore the saml_settings method call for now):
|
199
148
|
|
200
149
|
```ruby
|
201
150
|
def init
|
@@ -215,7 +164,10 @@ def init
|
|
215
164
|
end
|
216
165
|
```
|
217
166
|
|
218
|
-
Once you've redirected back to the identity provider, it will ensure that the user has been
|
167
|
+
Once you've redirected back to the identity provider, it will ensure that the user has been
|
168
|
+
authorized and redirect back to your application for final consumption.
|
169
|
+
This can look something like this (the `authorize_success` and `authorize_failure`
|
170
|
+
methods are specific to your application):
|
219
171
|
|
220
172
|
```ruby
|
221
173
|
def consume
|
@@ -228,20 +180,24 @@ def consume
|
|
228
180
|
session[:attributes] = response.attributes
|
229
181
|
else
|
230
182
|
authorize_failure # This method shows an error message
|
183
|
+
# List of errors is available in response.errors array
|
231
184
|
end
|
232
185
|
end
|
233
186
|
```
|
234
187
|
|
235
|
-
In the above there are a few assumptions, one being that `response.nameid` is an email address.
|
188
|
+
In the above there are a few assumptions, one being that `response.nameid` is an email address.
|
189
|
+
This is all handled with how you specify the settings that are in play via the `saml_settings` method.
|
190
|
+
That could be implemented along the lines of this:
|
236
191
|
|
237
192
|
```
|
238
193
|
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse])
|
239
194
|
response.settings = saml_settings
|
240
195
|
```
|
241
196
|
|
242
|
-
If the assertion of the SAMLResponse is not encrypted, you can initialize the Response
|
243
|
-
|
244
|
-
|
197
|
+
If the assertion of the SAMLResponse is not encrypted, you can initialize the Response
|
198
|
+
without the `:settings` parameter and set it later. If the SAMLResponse contains an encrypted
|
199
|
+
assertion, you need to provide the settings in the initialize method in order to obtain the
|
200
|
+
decrypted assertion, using the service provider private key in order to decrypt.
|
245
201
|
If you don't know what expect, always use the former (set the settings on initialize).
|
246
202
|
|
247
203
|
```ruby
|
@@ -251,8 +207,10 @@ def saml_settings
|
|
251
207
|
settings.assertion_consumer_service_url = "http://#{request.host}/saml/consume"
|
252
208
|
settings.sp_entity_id = "http://#{request.host}/saml/metadata"
|
253
209
|
settings.idp_entity_id = "https://app.onelogin.com/saml/metadata/#{OneLoginAppId}"
|
254
|
-
settings.
|
255
|
-
settings.
|
210
|
+
settings.idp_sso_service_url = "https://app.onelogin.com/trust/saml2/http-post/sso/#{OneLoginAppId}"
|
211
|
+
settings.idp_sso_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" # or :post, :redirect
|
212
|
+
settings.idp_slo_service_url = "https://app.onelogin.com/trust/saml2/http-redirect/slo/#{OneLoginAppId}"
|
213
|
+
settings.idp_slo_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" # or :post, :redirect
|
256
214
|
settings.idp_cert_fingerprint = OneLoginAppCertFingerPrint
|
257
215
|
settings.idp_cert_fingerprint_algorithm = "http://www.w3.org/2000/09/xmldsig#sha1"
|
258
216
|
settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
|
@@ -265,26 +223,30 @@ def saml_settings
|
|
265
223
|
"urn:oasis:names:tc:SAML:2.0:ac:classes:Password"
|
266
224
|
]
|
267
225
|
|
268
|
-
# Optional bindings (defaults to Redirect for logout POST for
|
269
|
-
settings.single_logout_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
|
270
|
-
settings.assertion_consumer_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
|
226
|
+
# Optional bindings (defaults to Redirect for logout POST for ACS)
|
227
|
+
settings.single_logout_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" # or :post, :redirect
|
228
|
+
settings.assertion_consumer_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" # or :post, :redirect
|
271
229
|
|
272
230
|
settings
|
273
231
|
end
|
274
232
|
```
|
275
233
|
|
276
|
-
The use of settings.issuer is deprecated in favour of settings.sp_entity_id
|
234
|
+
The use of settings.issuer is deprecated in favour of settings.sp_entity_id since version 1.11.0
|
277
235
|
|
278
|
-
Some assertion validations can be skipped by passing parameters to `OneLogin::RubySaml::Response.new()`.
|
236
|
+
Some assertion validations can be skipped by passing parameters to `OneLogin::RubySaml::Response.new()`.
|
237
|
+
For example, you can skip the `AuthnStatement`, `Conditions`, `Recipient`, or the `SubjectConfirmation`
|
238
|
+
validations by initializing the response with different options:
|
279
239
|
|
280
240
|
```ruby
|
281
241
|
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], {skip_authnstatement: true}) # skips AuthnStatement
|
282
242
|
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], {skip_conditions: true}) # skips conditions
|
283
243
|
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], {skip_subject_confirmation: true}) # skips subject confirmation
|
284
|
-
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], {skip_recipient_check: true}) #
|
244
|
+
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], {skip_recipient_check: true}) # doesn't skip subject confirmation, but skips the recipient check which is a sub check of the subject_confirmation check
|
245
|
+
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], {skip_audience: true}) # skips audience check
|
285
246
|
```
|
286
247
|
|
287
|
-
All that's left is to wrap everything in a controller and reference it in the initialization and
|
248
|
+
All that's left is to wrap everything in a controller and reference it in the initialization and
|
249
|
+
consumption URLs in OneLogin. A full controller example could look like this:
|
288
250
|
|
289
251
|
```ruby
|
290
252
|
# This controller expects you to use the URLs /saml/init and /saml/consume in your OneLogin application.
|
@@ -305,6 +267,7 @@ class SamlController < ApplicationController
|
|
305
267
|
session[:attributes] = response.attributes
|
306
268
|
else
|
307
269
|
authorize_failure # This method shows an error message
|
270
|
+
# List of errors is available in response.errors array
|
308
271
|
end
|
309
272
|
end
|
310
273
|
|
@@ -315,7 +278,7 @@ class SamlController < ApplicationController
|
|
315
278
|
|
316
279
|
settings.assertion_consumer_service_url = "http://#{request.host}/saml/consume"
|
317
280
|
settings.sp_entity_id = "http://#{request.host}/saml/metadata"
|
318
|
-
settings.
|
281
|
+
settings.idp_sso_service_url = "https://app.onelogin.com/saml/signon/#{OneLoginAppId}"
|
319
282
|
settings.idp_cert_fingerprint = OneLoginAppCertFingerPrint
|
320
283
|
settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
|
321
284
|
|
@@ -336,44 +299,56 @@ class SamlController < ApplicationController
|
|
336
299
|
end
|
337
300
|
```
|
338
301
|
|
302
|
+
## Signature Validation
|
339
303
|
|
340
|
-
|
304
|
+
Ruby SAML allows different ways to validate the signature of the SAMLResponse:
|
305
|
+
- You can provide the IdP X.509 public certificate at the `idp_cert` setting.
|
306
|
+
- You can provide the IdP X.509 public certificate in fingerprint format using the
|
307
|
+
`idp_cert_fingerprint` setting parameter and additionally the `idp_cert_fingerprint_algorithm` parameter.
|
341
308
|
|
342
|
-
|
343
|
-
|
344
|
-
|
309
|
+
When validating the signature of redirect binding, the fingerprint is useless and the certificate
|
310
|
+
of the IdP is required in order to execute the validation. You can pass the option
|
311
|
+
`:relax_signature_validation` to `SloLogoutrequest` and `Logoutresponse` if want to avoid signature
|
312
|
+
validation if no certificate of the IdP is provided.
|
345
313
|
|
346
|
-
|
347
|
-
|
314
|
+
In production also we highly recommend to register on the settings the IdP certificate instead
|
315
|
+
of using the fingerprint method. The fingerprint, is a hash, so at the end is open to a collision
|
316
|
+
attack that can end on a signature validation bypass. Other SAML toolkits deprecated that mechanism,
|
317
|
+
we maintain it for compatibility and also to be used on test environment.
|
348
318
|
|
349
|
-
|
319
|
+
## Handling Multiple IdP Certificates
|
350
320
|
|
351
|
-
|
321
|
+
If the IdP metadata XML includes multiple certificates, you may specify the `idp_cert_multi`
|
322
|
+
parameter. When used, the `idp_cert` and `idp_cert_fingerprint` parameters are ignored.
|
323
|
+
This is useful in the following scenarios:
|
352
324
|
|
353
|
-
|
354
|
-
|
325
|
+
* The IdP uses different certificates for signing versus encryption.
|
326
|
+
* The IdP is undergoing a key rollover and is publishing the old and new certificates in parallel.
|
355
327
|
|
356
|
-
|
328
|
+
The `idp_cert_multi` must be a `Hash` as follows. The `:signing` and `:encryption` arrays below,
|
329
|
+
add the IdP X.509 public certificates which were published in the IdP metadata.
|
330
|
+
|
331
|
+
```ruby
|
357
332
|
{
|
358
333
|
:signing => [],
|
359
334
|
:encryption => []
|
360
335
|
}
|
361
|
-
|
362
|
-
And on 'signing' and 'encryption' arrays, add the different IdP x509 public certificates published on the IdP metadata.
|
363
|
-
|
336
|
+
```
|
364
337
|
|
365
338
|
## Metadata Based Configuration
|
366
339
|
|
367
|
-
The method above requires a little extra work to manually specify attributes about
|
368
|
-
|
340
|
+
The method above requires a little extra work to manually specify attributes about both the IdP and your SP application.
|
341
|
+
There's an easier method: use a metadata exchange. Metadata is an XML file that defines the capabilities of both the IdP
|
342
|
+
and the SP application. It also contains the X.509 public key certificates which add to the trusted relationship.
|
343
|
+
The IdP administrator can also configure custom settings for an SP based on the metadata.
|
369
344
|
|
370
|
-
Using
|
345
|
+
Using `IdpMetadataParser#parse_remote`, the IdP metadata will be added to the settings.
|
371
346
|
|
372
347
|
```ruby
|
373
348
|
def saml_settings
|
374
349
|
|
375
350
|
idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new
|
376
|
-
# Returns OneLogin::RubySaml::Settings
|
351
|
+
# Returns OneLogin::RubySaml::Settings pre-populated with IdP metadata
|
377
352
|
settings = idp_metadata_parser.parse_remote("https://example.com/auth/saml2/idp/metadata")
|
378
353
|
|
379
354
|
settings.assertion_consumer_service_url = "http://#{request.host}/saml/consume"
|
@@ -385,11 +360,12 @@ def saml_settings
|
|
385
360
|
settings
|
386
361
|
end
|
387
362
|
```
|
363
|
+
|
388
364
|
The following attributes are set:
|
389
365
|
* idp_entity_id
|
390
366
|
* name_identifier_format
|
391
|
-
*
|
392
|
-
*
|
367
|
+
* idp_sso_service_url
|
368
|
+
* idp_slo_service_url
|
393
369
|
* idp_attribute_names
|
394
370
|
* idp_cert
|
395
371
|
* idp_cert_fingerprint
|
@@ -403,11 +379,11 @@ IdpMetadataParser by its Entity Id value:
|
|
403
379
|
|
404
380
|
```ruby
|
405
381
|
validate_cert = true
|
406
|
-
settings =
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
382
|
+
settings = idp_metadata_parser.parse_remote(
|
383
|
+
"https://example.com/auth/saml2/idp/metadata",
|
384
|
+
validate_cert,
|
385
|
+
entity_id: "http//example.com/target/entity"
|
386
|
+
)
|
411
387
|
```
|
412
388
|
|
413
389
|
### Parsing Metadata into an Hash
|
@@ -422,7 +398,7 @@ If you are using `saml:AttributeStatement` to transfer data like the username, y
|
|
422
398
|
`single_value_compatibility` (when activated, only the first value is returned)
|
423
399
|
|
424
400
|
```ruby
|
425
|
-
response
|
401
|
+
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse])
|
426
402
|
response.settings = saml_settings
|
427
403
|
|
428
404
|
response.attributes[:username]
|
@@ -455,6 +431,9 @@ Imagine this `saml:AttributeStatement`
|
|
455
431
|
<saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>
|
456
432
|
<saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="1"/>
|
457
433
|
</saml:Attribute>
|
434
|
+
<saml:Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname">
|
435
|
+
<saml:AttributeValue>usersName</saml:AttributeValue>
|
436
|
+
</saml:Attribute>
|
458
437
|
</saml:AttributeStatement>
|
459
438
|
```
|
460
439
|
|
@@ -465,7 +444,8 @@ pp(response.attributes) # is an OneLogin::RubySaml::Attributes object
|
|
465
444
|
"another_value"=>["value1", "value2"],
|
466
445
|
"role"=>["role1", "role2", "role3"],
|
467
446
|
"attribute_with_nil_value"=>[nil],
|
468
|
-
"attribute_with_nils_and_empty_strings"=>["", "valuePresent", nil, nil]
|
447
|
+
"attribute_with_nils_and_empty_strings"=>["", "valuePresent", nil, nil]
|
448
|
+
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname"=>["usersName"]}>
|
469
449
|
|
470
450
|
# Active single_value_compatibility
|
471
451
|
OneLogin::RubySaml::Attributes.single_value_compatibility = true
|
@@ -482,6 +462,9 @@ pp(response.attributes.single(:role))
|
|
482
462
|
pp(response.attributes.multi(:role))
|
483
463
|
# => ["role1", "role2", "role3"]
|
484
464
|
|
465
|
+
pp(response.attributes.fetch(:role))
|
466
|
+
# => "role1"
|
467
|
+
|
485
468
|
pp(response.attributes[:attribute_with_nil_value])
|
486
469
|
# => nil
|
487
470
|
|
@@ -497,7 +480,10 @@ pp(response.attributes.single(:not_exists))
|
|
497
480
|
pp(response.attributes.multi(:not_exists))
|
498
481
|
# => nil
|
499
482
|
|
500
|
-
|
483
|
+
pp(response.attributes.fetch(/givenname/))
|
484
|
+
# => "usersName"
|
485
|
+
|
486
|
+
# Deprecated single_value_compatibility
|
501
487
|
OneLogin::RubySaml::Attributes.single_value_compatibility = false
|
502
488
|
|
503
489
|
pp(response.attributes[:uid])
|
@@ -512,6 +498,9 @@ pp(response.attributes.single(:role))
|
|
512
498
|
pp(response.attributes.multi(:role))
|
513
499
|
# => ["role1", "role2", "role3"]
|
514
500
|
|
501
|
+
pp(response.attributes.fetch(:role))
|
502
|
+
# => ["role1", "role2", "role3"]
|
503
|
+
|
515
504
|
pp(response.attributes[:attribute_with_nil_value])
|
516
505
|
# => [nil]
|
517
506
|
|
@@ -526,82 +515,204 @@ pp(response.attributes.single(:not_exists))
|
|
526
515
|
|
527
516
|
pp(response.attributes.multi(:not_exists))
|
528
517
|
# => nil
|
518
|
+
|
519
|
+
pp(response.attributes.fetch(/givenname/))
|
520
|
+
# => ["usersName"]
|
529
521
|
```
|
530
522
|
|
531
523
|
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').
|
532
524
|
To add a `saml:AuthnContextDeclRef`, define `settings.authn_context_decl_ref`.
|
533
525
|
|
534
|
-
In a SP-
|
526
|
+
In a SP-initiated 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
|
535
527
|
building the authrequest object.
|
536
528
|
|
529
|
+
## Service Provider Metadata
|
530
|
+
|
531
|
+
To form a trusted pair relationship with the IdP, the SP (you) need to provide metadata XML
|
532
|
+
to the IdP for various good reasons. (Caching, certificate lookups, relaying party permissions, etc)
|
533
|
+
|
534
|
+
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.
|
537
535
|
|
538
|
-
|
536
|
+
The metadata will be polled by the IdP every few minutes, so updating your settings should propagate
|
537
|
+
to the IdP settings.
|
539
538
|
|
540
|
-
|
539
|
+
```ruby
|
540
|
+
class SamlController < ApplicationController
|
541
|
+
# ... the rest of your controller definitions ...
|
542
|
+
def metadata
|
543
|
+
settings = Account.get_saml_settings
|
544
|
+
meta = OneLogin::RubySaml::Metadata.new
|
545
|
+
render :xml => meta.generate(settings), :content_type => "application/samlmetadata+xml"
|
546
|
+
end
|
547
|
+
end
|
548
|
+
```
|
541
549
|
|
542
|
-
|
550
|
+
You can add `ValidUntil` and `CacheDuration` to the SP Metadata XML using instead:
|
543
551
|
|
544
552
|
```ruby
|
545
|
-
|
546
|
-
|
553
|
+
# Valid until => 2 days from now
|
554
|
+
# Cache duration = 604800s = 1 week
|
555
|
+
valid_until = Time.now + 172800
|
556
|
+
cache_duration = 604800
|
557
|
+
meta.generate(settings, false, valid_until, cache_duration)
|
547
558
|
```
|
548
559
|
|
549
|
-
|
560
|
+
## Signing and Decryption
|
561
|
+
|
562
|
+
Ruby SAML supports the following functionality:
|
563
|
+
|
564
|
+
1. Signing your SP Metadata XML
|
565
|
+
2. Signing your SP SAML messages
|
566
|
+
3. Decrypting IdP Assertion messages upon receipt (EncryptedAssertion)
|
567
|
+
4. Verifying signatures on SAML messages and IdP Assertions
|
568
|
+
|
569
|
+
In order to use functions 1-3 above, you must first define your SP public certificate and private key:
|
550
570
|
|
551
571
|
```ruby
|
552
|
-
settings.
|
553
|
-
settings.
|
554
|
-
|
555
|
-
|
556
|
-
|
572
|
+
settings.certificate = "CERTIFICATE TEXT WITH BEGIN/END HEADER AND FOOTER"
|
573
|
+
settings.private_key = "PRIVATE KEY TEXT WITH BEGIN/END HEADER AND FOOTER"
|
574
|
+
```
|
575
|
+
|
576
|
+
Note that the same certificate (and its associated private key) are used to perform
|
577
|
+
all decryption and signing-related functions (1-4) above. Ruby SAML does not currently allow
|
578
|
+
to specify different certificates for each function.
|
557
579
|
|
580
|
+
You may also globally set the SP signature and digest method, to be used in SP signing (functions 1 and 2 above):
|
581
|
+
|
582
|
+
```ruby
|
558
583
|
settings.security[:digest_method] = XMLSecurity::Document::SHA1
|
559
584
|
settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1
|
585
|
+
```
|
586
|
+
|
587
|
+
#### Signing SP Metadata
|
588
|
+
|
589
|
+
You may add a `<ds:Signature>` digital signature element to your SP Metadata XML using the following setting:
|
560
590
|
|
561
|
-
|
562
|
-
|
563
|
-
settings.
|
564
|
-
|
565
|
-
settings.security[:
|
591
|
+
```ruby
|
592
|
+
settings.certificate = "CERTIFICATE TEXT WITH BEGIN/END HEADER AND FOOTER"
|
593
|
+
settings.private_key = "PRIVATE KEY TEXT WITH BEGIN/END HEADER AND FOOTER"
|
594
|
+
|
595
|
+
settings.security[:metadata_signed] = true # Enable signature on Metadata
|
596
|
+
```
|
597
|
+
|
598
|
+
#### Signing SP SAML Messages
|
599
|
+
|
600
|
+
Ruby SAML supports SAML request signing. The Service Provider will sign the
|
601
|
+
request/responses with its private key. The Identity Provider will then validate the signature
|
602
|
+
of the received request/responses with the public X.509 cert of the Service Provider.
|
603
|
+
|
604
|
+
To enable, please first set your certificate and private key. This will add `<md:KeyDescriptor use="signing">`
|
605
|
+
to your SP Metadata XML, to be read by the IdP.
|
606
|
+
|
607
|
+
```ruby
|
608
|
+
settings.certificate = "CERTIFICATE TEXT WITH BEGIN/END HEADER AND FOOTER"
|
609
|
+
settings.private_key = "PRIVATE KEY TEXT WITH BEGIN/END HEADER AND FOOTER"
|
610
|
+
```
|
611
|
+
|
612
|
+
Next, you may specify the specific SP SAML messages you would like to sign:
|
613
|
+
|
614
|
+
```ruby
|
615
|
+
settings.security[:authn_requests_signed] = true # Enable signature on AuthNRequest
|
616
|
+
settings.security[:logout_requests_signed] = true # Enable signature on Logout Request
|
617
|
+
settings.security[:logout_responses_signed] = true # Enable signature on Logout Response
|
566
618
|
```
|
567
619
|
|
568
|
-
|
620
|
+
Signatures will be handled automatically for both `HTTP-Redirect` and `HTTP-Redirect` Binding.
|
621
|
+
Note that the RelayState parameter is used when creating the Signature on the `HTTP-Redirect` Binding.
|
569
622
|
Remember to provide it to the Signature builder if you are sending a `GET RelayState` parameter or the
|
570
623
|
signature validation process will fail at the Identity Provider.
|
571
624
|
|
572
|
-
|
573
|
-
The Identity Provider will validate the sign of the received request/responses with the public x500 cert of the
|
574
|
-
Service Provider.
|
625
|
+
#### Decrypting IdP SAML Assertions
|
575
626
|
|
576
|
-
|
627
|
+
Ruby SAML supports EncryptedAssertion. The Identity Provider will encrypt the Assertion with the
|
628
|
+
public cert of the Service Provider. The Service Provider will decrypt the EncryptedAssertion with its private key.
|
577
629
|
|
578
|
-
|
630
|
+
You may enable EncryptedAssertion as follows. This will add `<md:KeyDescriptor use="encrytion">` to your
|
631
|
+
SP Metadata XML, to be read by the IdP.
|
579
632
|
|
580
|
-
|
633
|
+
```ruby
|
634
|
+
settings.certificate = "CERTIFICATE TEXT WITH BEGIN/END HEADER AND FOOTER"
|
635
|
+
settings.private_key = "PRIVATE KEY TEXT WITH BEGIN/END HEADER AND FOOTER"
|
636
|
+
|
637
|
+
settings.security[:want_assertions_encrypted] = true # Invalidate SAML messages without an EncryptedAssertion
|
638
|
+
```
|
581
639
|
|
582
|
-
|
640
|
+
#### Verifying Signature on IdP Assertions
|
583
641
|
|
584
|
-
|
642
|
+
You may require the IdP to sign its SAML Assertions using the following setting.
|
643
|
+
With will add `<md:SPSSODescriptor WantAssertionsSigned="true">` to your SP Metadata XML.
|
644
|
+
The signature will be checked against the `<md:KeyDescriptor use="signing">` element
|
645
|
+
present in the IdP's metadata.
|
585
646
|
|
586
647
|
```ruby
|
587
|
-
settings.
|
588
|
-
settings.private_key = "PRIVATE KEY TEXT WITH HEAD AND FOOT"
|
648
|
+
settings.security[:want_assertions_signed] = true # Require the IdP to sign its SAML Assertions
|
589
649
|
```
|
590
650
|
|
591
|
-
|
592
|
-
The Service Provider will decrypt the EncryptedAssertion with its private key.
|
651
|
+
#### Certificate and Signature Validation
|
593
652
|
|
594
|
-
|
653
|
+
You may require SP and IdP certificates to be non-expired using the following settings:
|
595
654
|
|
655
|
+
```ruby
|
656
|
+
settings.security[:check_idp_cert_expiration] = true # Raise error if IdP X.509 cert is expired
|
657
|
+
settings.security[:check_sp_cert_expiration] = true # Raise error SP X.509 cert is expired
|
658
|
+
```
|
596
659
|
|
597
|
-
|
660
|
+
By default, Ruby SAML will raise a `OneLogin::RubySaml::ValidationError` if a signature or certificate
|
661
|
+
validation fails. You may disable such exceptions using the `settings.security[:soft]` parameter.
|
598
662
|
|
599
|
-
|
663
|
+
```ruby
|
664
|
+
settings.security[:soft] = true # Do not raise error on failed signature/certificate validations
|
665
|
+
```
|
666
|
+
|
667
|
+
#### Audience Validation
|
668
|
+
|
669
|
+
A service provider should only consider a SAML response valid if the IdP includes an <AudienceRestriction>
|
670
|
+
element containting an <Audience> element that uniquely identifies the service provider. Unless you specify
|
671
|
+
the `skip_audience` option, Ruby SAML will validate that each SAML response includes an <Audience> element
|
672
|
+
whose contents matches `settings.sp_entity_id`.
|
600
673
|
|
674
|
+
By default, Ruby SAML considers an <AudienceRestriction> element containing only empty <Audience> elements
|
675
|
+
to be valid. That means an otherwise valid SAML response with a condition like this would be valid:
|
676
|
+
|
677
|
+
```xml
|
678
|
+
<AudienceRestriction>
|
679
|
+
<Audience />
|
680
|
+
</AudienceRestriction>
|
681
|
+
```
|
682
|
+
|
683
|
+
You may enforce that an <AudienceRestriction> element containing only empty <Audience> elements
|
684
|
+
is invalid using the `settings.security[:strict_audience_validation]` parameter.
|
685
|
+
|
686
|
+
```ruby
|
687
|
+
settings.security[:strict_audience_validation] = true
|
688
|
+
```
|
689
|
+
|
690
|
+
#### Key Rollover
|
691
|
+
|
692
|
+
To update the SP X.509 certificate and private key without disruption of service, you may define the parameter
|
693
|
+
`settings.certificate_new`. This will publish the new SP certificate in your metadata so that your IdP counterparties
|
694
|
+
may cache it in preparation for rollover.
|
695
|
+
|
696
|
+
For example, if you to rollover from `CERT A` to `CERT B`. Before rollover, your settings should look as follows.
|
697
|
+
Both `CERT A` and `CERT B` will now appear in your SP metadata, however `CERT A` will still be used for signing
|
698
|
+
and encryption at this time.
|
699
|
+
|
700
|
+
```ruby
|
701
|
+
settings.certificate = "CERT A"
|
702
|
+
settings.private_key = "PRIVATE KEY FOR CERT A"
|
703
|
+
settings.certificate_new = "CERT B"
|
704
|
+
```
|
705
|
+
|
706
|
+
After the IdP has cached `CERT B`, you may then change your settings as follows:
|
707
|
+
|
708
|
+
```ruby
|
709
|
+
settings.certificate = "CERT B"
|
710
|
+
settings.private_key = "PRIVATE KEY FOR CERT B"
|
711
|
+
```
|
601
712
|
|
602
713
|
## Single Log Out
|
603
714
|
|
604
|
-
|
715
|
+
Ruby SAML supports SP-initiated Single Logout and IdP-Initiated Single Logout.
|
605
716
|
|
606
717
|
Here is an example that we could add to our previous controller to generate and send a SAML Logout Request to the IdP:
|
607
718
|
|
@@ -611,22 +722,28 @@ def sp_logout_request
|
|
611
722
|
# LogoutRequest accepts plain browser requests w/o paramters
|
612
723
|
settings = saml_settings
|
613
724
|
|
614
|
-
if settings.
|
725
|
+
if settings.idp_slo_service_url.nil?
|
615
726
|
logger.info "SLO IdP Endpoint not found in settings, executing then a normal logout'"
|
616
727
|
delete_session
|
617
728
|
else
|
618
729
|
|
619
|
-
|
620
|
-
|
621
|
-
logout_request = OneLogin::RubySaml::Logoutrequest.new()
|
622
|
-
session[:transaction_id] = logout_request.uuid
|
623
|
-
logger.info "New SP SLO for userid '#{session[:userid]}' transactionid '#{session[:transaction_id]}'"
|
730
|
+
logout_request = OneLogin::RubySaml::Logoutrequest.new
|
731
|
+
logger.info "New SP SLO for userid '#{session[:userid]}' transactionid '#{logout_request.uuid}'"
|
624
732
|
|
625
733
|
if settings.name_identifier_value.nil?
|
626
734
|
settings.name_identifier_value = session[:userid]
|
627
735
|
end
|
628
736
|
|
629
|
-
|
737
|
+
# Ensure user is logged out before redirect to IdP, in case anything goes wrong during single logout process (as recommended by saml2int [SDP-SP34])
|
738
|
+
logged_user = session[:userid]
|
739
|
+
logger.info "Delete session for '#{session[:userid]}'"
|
740
|
+
delete_session
|
741
|
+
|
742
|
+
# Save the transaction_id to compare it with the response we get back
|
743
|
+
session[:transaction_id] = logout_request.uuid
|
744
|
+
session[:logged_out_user] = logged_user
|
745
|
+
|
746
|
+
relayState = url_for(controller: 'saml', action: 'index')
|
630
747
|
redirect_to(logout_request.create(settings, :RelayState => relayState))
|
631
748
|
end
|
632
749
|
end
|
@@ -653,7 +770,7 @@ def process_logout_response
|
|
653
770
|
logger.error "The SAML Logout Response is invalid"
|
654
771
|
else
|
655
772
|
# Actually log out this session
|
656
|
-
logger.info "
|
773
|
+
logger.info "SLO completed for '#{session[:logged_out_user]}'"
|
657
774
|
delete_session
|
658
775
|
end
|
659
776
|
end
|
@@ -662,6 +779,8 @@ end
|
|
662
779
|
def delete_session
|
663
780
|
session[:userid] = nil
|
664
781
|
session[:attributes] = nil
|
782
|
+
session[:transaction_id] = nil
|
783
|
+
session[:logged_out_user] = nil
|
665
784
|
end
|
666
785
|
```
|
667
786
|
|
@@ -671,10 +790,16 @@ Here is an example that we could add to our previous controller to process a SAM
|
|
671
790
|
# Method to handle IdP initiated logouts
|
672
791
|
def idp_logout_request
|
673
792
|
settings = Account.get_saml_settings
|
674
|
-
|
793
|
+
# ADFS URL-Encodes SAML data as lowercase, and the toolkit by default uses
|
794
|
+
# uppercase. Turn it True for ADFS compatibility on signature verification
|
795
|
+
settings.security[:lowercase_url_encoding] = true
|
796
|
+
|
797
|
+
logout_request = OneLogin::RubySaml::SloLogoutrequest.new(
|
798
|
+
params[:SAMLRequest], settings: settings
|
799
|
+
)
|
675
800
|
if !logout_request.is_valid?
|
676
801
|
logger.error "IdP initiated LogoutRequest was not valid!"
|
677
|
-
render :inline => logger.error
|
802
|
+
return render :inline => logger.error
|
678
803
|
end
|
679
804
|
logger.info "IdP initiated Logout for #{logout_request.name_id}"
|
680
805
|
|
@@ -706,30 +831,6 @@ def logout
|
|
706
831
|
end
|
707
832
|
```
|
708
833
|
|
709
|
-
|
710
|
-
|
711
|
-
## Service Provider Metadata
|
712
|
-
|
713
|
-
To form a trusted pair relationship with the IdP, the SP (you) need to provide metadata XML
|
714
|
-
to the IdP for various good reasons. (Caching, certificate lookups, relaying party permissions, etc)
|
715
|
-
|
716
|
-
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.
|
717
|
-
|
718
|
-
The metadata will be polled by the IdP every few minutes, so updating your settings should propagate
|
719
|
-
to the IdP settings.
|
720
|
-
|
721
|
-
```ruby
|
722
|
-
class SamlController < ApplicationController
|
723
|
-
# ... the rest of your controller definitions ...
|
724
|
-
def metadata
|
725
|
-
settings = Account.get_saml_settings
|
726
|
-
meta = OneLogin::RubySaml::Metadata.new
|
727
|
-
render :xml => meta.generate(settings), :content_type => "application/samlmetadata+xml"
|
728
|
-
end
|
729
|
-
end
|
730
|
-
```
|
731
|
-
|
732
|
-
|
733
834
|
## Clock Drift
|
734
835
|
|
735
836
|
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.
|
@@ -744,13 +845,33 @@ response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], :allowed_cloc
|
|
744
845
|
|
745
846
|
Make sure to keep the value as comfortably small as possible to keep security risks to a minimum.
|
746
847
|
|
848
|
+
## Deflation Limit
|
849
|
+
|
850
|
+
To protect against decompression bombs (a form of DoS attack), SAML messages are limited to 250,000 bytes by default.
|
851
|
+
Sometimes legitimate SAML messages will exceed this limit,
|
852
|
+
for example due to custom claims like including groups a user is a member of.
|
853
|
+
If you want to customize this limit, you need to provide a different setting when initializing the response object.
|
854
|
+
Example:
|
855
|
+
|
856
|
+
```ruby
|
857
|
+
def consume
|
858
|
+
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], { settings: saml_settings })
|
859
|
+
...
|
860
|
+
end
|
861
|
+
|
862
|
+
private
|
863
|
+
|
864
|
+
def saml_settings
|
865
|
+
OneLogin::RubySaml::Settings.new(message_max_bytesize: 500_000)
|
866
|
+
end
|
867
|
+
```
|
868
|
+
|
747
869
|
## Attribute Service
|
748
870
|
|
749
871
|
To request attributes from the IdP the SP needs to provide an attribute service within it's metadata and reference the index in the assertion.
|
750
872
|
|
751
873
|
```ruby
|
752
874
|
settings = OneLogin::RubySaml::Settings.new
|
753
|
-
|
754
875
|
settings.attributes_index = 5
|
755
876
|
settings.attribute_consuming_service.configure do
|
756
877
|
service_name "Service"
|
@@ -761,3 +882,27 @@ end
|
|
761
882
|
```
|
762
883
|
|
763
884
|
The `attribute_value` option additionally accepts an array of possible values.
|
885
|
+
|
886
|
+
## Custom Metadata Fields
|
887
|
+
|
888
|
+
Some IdPs may require to add SPs to add additional fields (Organization, ContactPerson, etc.)
|
889
|
+
into the SP metadata. This can be achieved by extending the `OneLogin::RubySaml::Metadata`
|
890
|
+
class and overriding the `#add_extras` method as per the following example:
|
891
|
+
|
892
|
+
```ruby
|
893
|
+
class MyMetadata < OneLogin::RubySaml::Metadata
|
894
|
+
def add_extras(root, _settings)
|
895
|
+
org = root.add_element("md:Organization")
|
896
|
+
org.add_element("md:OrganizationName", 'xml:lang' => "en-US").text = 'ACME Inc.'
|
897
|
+
org.add_element("md:OrganizationDisplayName", 'xml:lang' => "en-US").text = 'ACME'
|
898
|
+
org.add_element("md:OrganizationURL", 'xml:lang' => "en-US").text = 'https://www.acme.com'
|
899
|
+
|
900
|
+
cp = root.add_element("md:ContactPerson", 'contactType' => 'technical')
|
901
|
+
cp.add_element("md:GivenName").text = 'ACME SAML Team'
|
902
|
+
cp.add_element("md:EmailAddress").text = 'saml@acme.com'
|
903
|
+
end
|
904
|
+
end
|
905
|
+
|
906
|
+
# Output XML with custom metadata
|
907
|
+
MyMetadata.new.generate(settings)
|
908
|
+
```
|