ruby-saml 1.9.0 → 1.15.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 +43 -0
- data/{changelog.md → CHANGELOG.md} +72 -1
- data/LICENSE +2 -1
- data/README.md +439 -212
- data/UPGRADING.md +149 -0
- data/lib/onelogin/ruby-saml/attributes.rb +24 -1
- data/lib/onelogin/ruby-saml/authrequest.rb +27 -11
- data/lib/onelogin/ruby-saml/idp_metadata_parser.rb +285 -184
- data/lib/onelogin/ruby-saml/logging.rb +3 -3
- data/lib/onelogin/ruby-saml/logoutrequest.rb +27 -12
- data/lib/onelogin/ruby-saml/logoutresponse.rb +27 -11
- data/lib/onelogin/ruby-saml/metadata.rb +62 -17
- data/lib/onelogin/ruby-saml/response.rb +87 -38
- data/lib/onelogin/ruby-saml/saml_message.rb +16 -8
- data/lib/onelogin/ruby-saml/setting_error.rb +6 -0
- data/lib/onelogin/ruby-saml/settings.rb +123 -52
- data/lib/onelogin/ruby-saml/slo_logoutrequest.rb +33 -31
- data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +44 -21
- data/lib/onelogin/ruby-saml/utils.rb +101 -9
- data/lib/onelogin/ruby-saml/version.rb +1 -1
- data/lib/xml_security.rb +41 -15
- data/ruby-saml.gemspec +49 -13
- metadata +71 -308
- data/.travis.yml +0 -32
- 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 -579
- 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 -67
- data/test/logoutrequest_test.rb +0 -226
- data/test/logoutresponse_test.rb +0 -402
- 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 -53
- data/test/metadata/no_idp_descriptor.xml +0 -21
- data/test/metadata_test.rb +0 -331
- data/test/request_test.rb +0 -323
- data/test/response_test.rb +0 -1619
- 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 -329
- data/test/slo_logoutrequest_test.rb +0 -448
- data/test/slo_logoutresponse_test.rb +0 -199
- data/test/test_helper.rb +0 -327
- data/test/utils_test.rb +0 -254
- data/test/xml_security_test.rb +0 -421
data/README.md
CHANGED
@@ -1,130 +1,92 @@
|
|
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
|
+
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.
|
5
7
|
|
6
|
-
##
|
7
|
-
|
8
|
-
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.
|
9
|
-
|
10
|
-
## Updating from 1.5.0 to 1.6.0
|
11
|
-
|
12
|
-
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.
|
13
|
-
|
14
|
-
The new preferred way to provide _SAMLResponse_, _RelayState_, and _SigAlg_ is via the `options[:raw_get_params]` parameter. For example:
|
15
|
-
|
16
|
-
```ruby
|
17
|
-
# In this example `query_params` is assumed to contain decoded query parameters,
|
18
|
-
# and `raw_query_params` is assumed to contain encoded query parameters as sent by the IDP.
|
19
|
-
settings = {
|
20
|
-
settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1
|
21
|
-
settings.soft = false
|
22
|
-
}
|
23
|
-
options = {
|
24
|
-
get_params: {
|
25
|
-
"Signature" => query_params["Signature"],
|
26
|
-
},
|
27
|
-
raw_get_params: {
|
28
|
-
"SAMLRequest" => raw_query_params["SAMLRequest"],
|
29
|
-
"SigAlg" => raw_query_params["SigAlg"],
|
30
|
-
"RelayState" => raw_query_params["RelayState"],
|
31
|
-
},
|
32
|
-
}
|
33
|
-
slo_logout_request = OneLogin::RubySaml::SloLogoutrequest.new(query_params["SAMLRequest"], settings, options)
|
34
|
-
raise "Invalid Logout Request" unless slo_logout_request.is_valid?
|
35
|
-
```
|
36
|
-
|
37
|
-
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.
|
38
|
-
|
39
|
-
## Updating from 1.4.2 to 1.4.3
|
40
|
-
|
41
|
-
Version `1.4.3` introduces Recipient validation of SubjectConfirmation elements.
|
42
|
-
The 'Recipient' value is compared with the settings.assertion_consumer_service_url
|
43
|
-
value.
|
44
|
-
If you want to skip that validation, add the :skip_recipient_check option to the
|
45
|
-
initialize method of the Response object.
|
46
|
-
|
47
|
-
Parsing metadata that contains more than one certificate will propagate the
|
48
|
-
idp_cert_multi property rather than idp_cert. See [signature validation
|
49
|
-
section](#signature-validation) for details.
|
50
|
-
|
51
|
-
## Updating from 1.3.x to 1.4.X
|
52
|
-
|
53
|
-
Version `1.4.0` is a recommended update for all Ruby SAML users as it includes security improvements.
|
54
|
-
|
55
|
-
## Updating from 1.2.x to 1.3.X
|
56
|
-
|
57
|
-
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)
|
58
|
-
|
59
|
-
## Updating from 1.1.x to 1.2.X
|
60
|
-
|
61
|
-
Version `1.2` adds IDP metadata parsing improvements, uuid deprecation in favour of SecureRandom, refactor error handling and some minor improvements
|
62
|
-
|
63
|
-
There is no compatibility issue detected.
|
64
|
-
|
65
|
-
For more details, please review [the changelog](changelog.md).
|
66
|
-
|
67
|
-
## Updating from 1.0.x to 1.1.X
|
68
|
-
|
69
|
-
Version `1.1` adds some improvements on signature validation and solves some namespace conflicts.
|
70
|
-
|
71
|
-
## Updating from 0.9.x to 1.0.X
|
72
|
-
|
73
|
-
Version `1.0` is a recommended update for all Ruby SAML users as it includes security fixes.
|
74
|
-
|
75
|
-
Version `1.0` adds security improvements like entity expansion limitation, more SAML message validations, and other important improvements like decrypt support.
|
8
|
+
## Overview
|
76
9
|
|
77
|
-
|
78
|
-
|
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.
|
79
13
|
|
80
|
-
|
81
|
-
Version `0.9` adds many new features and improvements.
|
14
|
+
SAML authorization is a two step process and you are expected to implement support for both.
|
82
15
|
|
83
|
-
|
84
|
-
|
16
|
+
We created a demo project for Rails 4 that uses the latest version of this library:
|
17
|
+
[ruby-saml-example](https://github.com/saml-toolkit/ruby-saml-example)
|
85
18
|
|
86
|
-
|
19
|
+
### Supported Ruby Versions
|
87
20
|
|
88
|
-
The Ruby
|
21
|
+
The following Ruby versions are covered by CI testing:
|
89
22
|
|
90
|
-
|
23
|
+
* 2.1.x
|
24
|
+
* 2.2.x
|
25
|
+
* 2.3.x
|
26
|
+
* 2.4.x
|
27
|
+
* 2.5.x
|
28
|
+
* 2.6.x
|
29
|
+
* 2.7.x
|
30
|
+
* 3.0.x
|
31
|
+
* JRuby 9.1.x
|
32
|
+
* JRuby 9.2.x
|
33
|
+
* TruffleRuby (latest)
|
91
34
|
|
92
|
-
|
35
|
+
In addition, the following may work but are untested:
|
93
36
|
|
94
|
-
### Supported versions of Ruby
|
95
37
|
* 1.8.7
|
96
38
|
* 1.9.x
|
97
39
|
* 2.0.x
|
98
|
-
*
|
99
|
-
*
|
100
|
-
* 2.3.x
|
101
|
-
* 2.4.x
|
102
|
-
* JRuby 1.7.19
|
103
|
-
* JRuby 9.0.0.0
|
40
|
+
* JRuby 1.7.x
|
41
|
+
* JRuby 9.0.x
|
104
42
|
|
105
43
|
## Adding Features, Pull Requests
|
44
|
+
|
106
45
|
* Fork the repository
|
107
46
|
* Make your feature addition or bug fix
|
108
47
|
* Add tests for your new features. This is important so we don't break any features in a future version unintentionally.
|
109
|
-
* Ensure all tests pass
|
48
|
+
* Ensure all tests pass by running `bundle exec rake test`.
|
110
49
|
* Do not change rakefile, version, or history.
|
111
50
|
* Open a pull request, following [this template](https://gist.github.com/Lordnibbler/11002759).
|
112
51
|
|
113
52
|
## Security Guidelines
|
114
53
|
|
115
|
-
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
|
+
by mail to the maintainer: sixto.martin.garcia+security@gmail.com
|
56
|
+
|
57
|
+
### Security Warning
|
58
|
+
|
59
|
+
Some tools may incorrectly report ruby-saml is a potential security vulnerability.
|
60
|
+
ruby-saml depends on Nokogiri, and it's possible to use Nokogiri in a dangerous way
|
61
|
+
(by enabling its DTDLOAD option and disabling its NONET option).
|
62
|
+
This dangerous Nokogiri configuration, which is sometimes used by other components,
|
63
|
+
can create an XML External Entity (XXE) vulnerability if the XML data is not trusted.
|
64
|
+
However, ruby-saml never enables this dangerous Nokogiri configuration;
|
65
|
+
ruby-saml never enables DTDLOAD, and it never disables NONET.
|
66
|
+
|
67
|
+
The OneLogin::RubySaml::IdpMetadataParser class does not validate in any way the URL
|
68
|
+
that is introduced in order to be parsed.
|
69
|
+
|
70
|
+
Usually the same administrator that handles the Service Provider also sets the URL to
|
71
|
+
the IdP, which should be a trusted resource.
|
72
|
+
|
73
|
+
But there are other scenarios, like a SAAS app where the administrator of the app
|
74
|
+
delegates this functionality to other users. In this case, extra precaution should
|
75
|
+
be taken in order to validate such URL inputs and avoid attacks like SSRF.
|
116
76
|
|
117
77
|
## Getting Started
|
118
|
-
|
78
|
+
|
79
|
+
In order to use Ruby SAML you will need to install the gem (either manually or using Bundler),
|
80
|
+
and require the library in your Ruby application:
|
119
81
|
|
120
82
|
Using `Gemfile`
|
121
83
|
|
122
84
|
```ruby
|
123
85
|
# latest stable
|
124
|
-
gem 'ruby-saml', '~> 1.
|
86
|
+
gem 'ruby-saml', '~> 1.11.0'
|
125
87
|
|
126
88
|
# or track master for bleeding-edge
|
127
|
-
gem 'ruby-saml', :github => '
|
89
|
+
gem 'ruby-saml', :github => 'saml-toolkit/ruby-saml'
|
128
90
|
```
|
129
91
|
|
130
92
|
Using RubyGems
|
@@ -133,7 +95,8 @@ Using RubyGems
|
|
133
95
|
gem install ruby-saml
|
134
96
|
```
|
135
97
|
|
136
|
-
|
98
|
+
You may require the entire Ruby SAML gem:
|
99
|
+
|
137
100
|
```ruby
|
138
101
|
require 'onelogin/ruby-saml'
|
139
102
|
```
|
@@ -146,7 +109,9 @@ require 'onelogin/ruby-saml/authrequest'
|
|
146
109
|
|
147
110
|
### Installation on Ruby 1.8.7
|
148
111
|
|
149
|
-
This gem uses Nokogiri as a dependency, which dropped support for Ruby 1.8.x in Nokogiri 1.6.
|
112
|
+
This gem uses Nokogiri as a dependency, which dropped support for Ruby 1.8.x in Nokogiri 1.6.
|
113
|
+
When installing this gem on Ruby 1.8.7, you will need to make sure a version of Nokogiri
|
114
|
+
prior to 1.6 is installed or specified if it hasn't been already.
|
150
115
|
|
151
116
|
Using `Gemfile`
|
152
117
|
|
@@ -170,21 +135,38 @@ To override the default behavior and control the destination of log messages, pr
|
|
170
135
|
a ruby Logger object to the gem's logging singleton:
|
171
136
|
|
172
137
|
```ruby
|
173
|
-
OneLogin::RubySaml::Logging.logger = Logger.new(
|
138
|
+
OneLogin::RubySaml::Logging.logger = Logger.new('/var/log/ruby-saml.log')
|
174
139
|
```
|
175
140
|
|
176
141
|
## The Initialization Phase
|
177
142
|
|
178
|
-
This is the first request you will get from the identity provider. It will hit your application
|
143
|
+
This is the first request you will get from the identity provider. It will hit your application
|
144
|
+
at a specific URL that you've announced as your SAML initialization point. The response to
|
145
|
+
this initialization is a redirect back to the identity provider, which can look something
|
146
|
+
like this (ignore the saml_settings method call for now):
|
147
|
+
|
148
|
+
```ruby
|
149
|
+
def init
|
150
|
+
request = OneLogin::RubySaml::Authrequest.new
|
151
|
+
redirect_to(request.create(saml_settings))
|
152
|
+
end
|
153
|
+
```
|
154
|
+
|
155
|
+
If the SP knows who should be authenticated in the IdP, then can provide that info as follows:
|
179
156
|
|
180
157
|
```ruby
|
181
158
|
def init
|
182
159
|
request = OneLogin::RubySaml::Authrequest.new
|
160
|
+
saml_settings.name_identifier_value_requested = "testuser@example.com"
|
161
|
+
saml_settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
|
183
162
|
redirect_to(request.create(saml_settings))
|
184
163
|
end
|
185
164
|
```
|
186
165
|
|
187
|
-
Once you've redirected back to the identity provider, it will ensure that the user has been
|
166
|
+
Once you've redirected back to the identity provider, it will ensure that the user has been
|
167
|
+
authorized and redirect back to your application for final consumption.
|
168
|
+
This can look something like this (the `authorize_success` and `authorize_failure`
|
169
|
+
methods are specific to your application):
|
188
170
|
|
189
171
|
```ruby
|
190
172
|
def consume
|
@@ -197,20 +179,24 @@ def consume
|
|
197
179
|
session[:attributes] = response.attributes
|
198
180
|
else
|
199
181
|
authorize_failure # This method shows an error message
|
182
|
+
# List of errors is available in response.errors array
|
200
183
|
end
|
201
184
|
end
|
202
185
|
```
|
203
186
|
|
204
|
-
In the above there are a few assumptions, one being that `response.nameid` is an email address.
|
187
|
+
In the above there are a few assumptions, one being that `response.nameid` is an email address.
|
188
|
+
This is all handled with how you specify the settings that are in play via the `saml_settings` method.
|
189
|
+
That could be implemented along the lines of this:
|
205
190
|
|
206
191
|
```
|
207
192
|
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse])
|
208
193
|
response.settings = saml_settings
|
209
194
|
```
|
210
195
|
|
211
|
-
If the assertion of the SAMLResponse is not encrypted, you can initialize the Response
|
212
|
-
|
213
|
-
|
196
|
+
If the assertion of the SAMLResponse is not encrypted, you can initialize the Response
|
197
|
+
without the `:settings` parameter and set it later. If the SAMLResponse contains an encrypted
|
198
|
+
assertion, you need to provide the settings in the initialize method in order to obtain the
|
199
|
+
decrypted assertion, using the service provider private key in order to decrypt.
|
214
200
|
If you don't know what expect, always use the former (set the settings on initialize).
|
215
201
|
|
216
202
|
```ruby
|
@@ -218,10 +204,12 @@ def saml_settings
|
|
218
204
|
settings = OneLogin::RubySaml::Settings.new
|
219
205
|
|
220
206
|
settings.assertion_consumer_service_url = "http://#{request.host}/saml/consume"
|
221
|
-
settings.
|
207
|
+
settings.sp_entity_id = "http://#{request.host}/saml/metadata"
|
222
208
|
settings.idp_entity_id = "https://app.onelogin.com/saml/metadata/#{OneLoginAppId}"
|
223
|
-
settings.
|
224
|
-
settings.
|
209
|
+
settings.idp_sso_service_url = "https://app.onelogin.com/trust/saml2/http-post/sso/#{OneLoginAppId}"
|
210
|
+
settings.idp_sso_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" # or :post, :redirect
|
211
|
+
settings.idp_slo_service_url = "https://app.onelogin.com/trust/saml2/http-redirect/slo/#{OneLoginAppId}"
|
212
|
+
settings.idp_slo_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" # or :post, :redirect
|
225
213
|
settings.idp_cert_fingerprint = OneLoginAppCertFingerPrint
|
226
214
|
settings.idp_cert_fingerprint_algorithm = "http://www.w3.org/2000/09/xmldsig#sha1"
|
227
215
|
settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
|
@@ -234,24 +222,30 @@ def saml_settings
|
|
234
222
|
"urn:oasis:names:tc:SAML:2.0:ac:classes:Password"
|
235
223
|
]
|
236
224
|
|
237
|
-
# Optional bindings (defaults to Redirect for logout POST for
|
238
|
-
settings.single_logout_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
|
239
|
-
settings.assertion_consumer_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
|
225
|
+
# Optional bindings (defaults to Redirect for logout POST for ACS)
|
226
|
+
settings.single_logout_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" # or :post, :redirect
|
227
|
+
settings.assertion_consumer_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" # or :post, :redirect
|
240
228
|
|
241
229
|
settings
|
242
230
|
end
|
243
231
|
```
|
244
232
|
|
245
|
-
|
233
|
+
The use of settings.issuer is deprecated in favour of settings.sp_entity_id since version 1.11.0
|
234
|
+
|
235
|
+
Some assertion validations can be skipped by passing parameters to `OneLogin::RubySaml::Response.new()`.
|
236
|
+
For example, you can skip the `AuthnStatement`, `Conditions`, `Recipient`, or the `SubjectConfirmation`
|
237
|
+
validations by initializing the response with different options:
|
246
238
|
|
247
239
|
```ruby
|
248
240
|
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], {skip_authnstatement: true}) # skips AuthnStatement
|
249
241
|
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], {skip_conditions: true}) # skips conditions
|
250
242
|
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], {skip_subject_confirmation: true}) # skips subject confirmation
|
251
|
-
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], {skip_recipient_check: true}) #
|
243
|
+
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
|
244
|
+
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], {skip_audience: true}) # skips audience check
|
252
245
|
```
|
253
246
|
|
254
|
-
All that's left is to wrap everything in a controller and reference it in the initialization and
|
247
|
+
All that's left is to wrap everything in a controller and reference it in the initialization and
|
248
|
+
consumption URLs in OneLogin. A full controller example could look like this:
|
255
249
|
|
256
250
|
```ruby
|
257
251
|
# This controller expects you to use the URLs /saml/init and /saml/consume in your OneLogin application.
|
@@ -272,6 +266,7 @@ class SamlController < ApplicationController
|
|
272
266
|
session[:attributes] = response.attributes
|
273
267
|
else
|
274
268
|
authorize_failure # This method shows an error message
|
269
|
+
# List of errors is available in response.errors array
|
275
270
|
end
|
276
271
|
end
|
277
272
|
|
@@ -281,8 +276,8 @@ class SamlController < ApplicationController
|
|
281
276
|
settings = OneLogin::RubySaml::Settings.new
|
282
277
|
|
283
278
|
settings.assertion_consumer_service_url = "http://#{request.host}/saml/consume"
|
284
|
-
settings.
|
285
|
-
settings.
|
279
|
+
settings.sp_entity_id = "http://#{request.host}/saml/metadata"
|
280
|
+
settings.idp_sso_service_url = "https://app.onelogin.com/saml/signon/#{OneLoginAppId}"
|
286
281
|
settings.idp_cert_fingerprint = OneLoginAppCertFingerPrint
|
287
282
|
settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
|
288
283
|
|
@@ -303,48 +298,60 @@ class SamlController < ApplicationController
|
|
303
298
|
end
|
304
299
|
```
|
305
300
|
|
301
|
+
## Signature Validation
|
306
302
|
|
307
|
-
|
303
|
+
Ruby SAML allows different ways to validate the signature of the SAMLResponse:
|
304
|
+
- You can provide the IdP X.509 public certificate at the `idp_cert` setting.
|
305
|
+
- You can provide the IdP X.509 public certificate in fingerprint format using the
|
306
|
+
`idp_cert_fingerprint` setting parameter and additionally the `idp_cert_fingerprint_algorithm` parameter.
|
308
307
|
|
309
|
-
|
310
|
-
|
311
|
-
|
308
|
+
When validating the signature of redirect binding, the fingerprint is useless and the certificate
|
309
|
+
of the IdP is required in order to execute the validation. You can pass the option
|
310
|
+
`:relax_signature_validation` to `SloLogoutrequest` and `Logoutresponse` if want to avoid signature
|
311
|
+
validation if no certificate of the IdP is provided.
|
312
312
|
|
313
|
-
|
314
|
-
|
313
|
+
In production also we highly recommend to register on the settings the IdP certificate instead
|
314
|
+
of using the fingerprint method. The fingerprint, is a hash, so at the end is open to a collision
|
315
|
+
attack that can end on a signature validation bypass. Other SAML toolkits deprecated that mechanism,
|
316
|
+
we maintain it for compatibility and also to be used on test environment.
|
315
317
|
|
316
|
-
|
318
|
+
## Handling Multiple IdP Certificates
|
317
319
|
|
318
|
-
|
320
|
+
If the IdP metadata XML includes multiple certificates, you may specify the `idp_cert_multi`
|
321
|
+
parameter. When used, the `idp_cert` and `idp_cert_fingerprint` parameters are ignored.
|
322
|
+
This is useful in the following scenarios:
|
319
323
|
|
320
|
-
|
321
|
-
|
324
|
+
* The IdP uses different certificates for signing versus encryption.
|
325
|
+
* The IdP is undergoing a key rollover and is publishing the old and new certificates in parallel.
|
322
326
|
|
323
|
-
|
327
|
+
The `idp_cert_multi` must be a `Hash` as follows. The `:signing` and `:encryption` arrays below,
|
328
|
+
add the IdP X.509 public certificates which were published in the IdP metadata.
|
329
|
+
|
330
|
+
```ruby
|
324
331
|
{
|
325
332
|
:signing => [],
|
326
333
|
:encryption => []
|
327
334
|
}
|
328
|
-
|
329
|
-
And on 'signing' and 'encryption' arrays, add the different IdP x509 public certificates published on the IdP metadata.
|
330
|
-
|
335
|
+
```
|
331
336
|
|
332
337
|
## Metadata Based Configuration
|
333
338
|
|
334
|
-
The method above requires a little extra work to manually specify attributes about
|
335
|
-
|
339
|
+
The method above requires a little extra work to manually specify attributes about both the IdP and your SP application.
|
340
|
+
There's an easier method: use a metadata exchange. Metadata is an XML file that defines the capabilities of both the IdP
|
341
|
+
and the SP application. It also contains the X.509 public key certificates which add to the trusted relationship.
|
342
|
+
The IdP administrator can also configure custom settings for an SP based on the metadata.
|
336
343
|
|
337
|
-
Using
|
344
|
+
Using `IdpMetadataParser#parse_remote`, the IdP metadata will be added to the settings.
|
338
345
|
|
339
346
|
```ruby
|
340
347
|
def saml_settings
|
341
348
|
|
342
349
|
idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new
|
343
|
-
# Returns OneLogin::RubySaml::Settings
|
350
|
+
# Returns OneLogin::RubySaml::Settings pre-populated with IdP metadata
|
344
351
|
settings = idp_metadata_parser.parse_remote("https://example.com/auth/saml2/idp/metadata")
|
345
352
|
|
346
353
|
settings.assertion_consumer_service_url = "http://#{request.host}/saml/consume"
|
347
|
-
settings.
|
354
|
+
settings.sp_entity_id = "http://#{request.host}/saml/metadata"
|
348
355
|
settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
|
349
356
|
# Optional for most SAML IdPs
|
350
357
|
settings.authn_context = "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport"
|
@@ -352,14 +359,15 @@ def saml_settings
|
|
352
359
|
settings
|
353
360
|
end
|
354
361
|
```
|
362
|
+
|
355
363
|
The following attributes are set:
|
356
364
|
* idp_entity_id
|
357
365
|
* name_identifier_format
|
358
|
-
*
|
359
|
-
*
|
366
|
+
* idp_sso_service_url
|
367
|
+
* idp_slo_service_url
|
360
368
|
* idp_attribute_names
|
361
|
-
* idp_cert
|
362
|
-
* idp_cert_fingerprint
|
369
|
+
* idp_cert
|
370
|
+
* idp_cert_fingerprint
|
363
371
|
* idp_cert_multi
|
364
372
|
|
365
373
|
### Retrieve one Entity Descriptor when many exist in Metadata
|
@@ -370,11 +378,11 @@ IdpMetadataParser by its Entity Id value:
|
|
370
378
|
|
371
379
|
```ruby
|
372
380
|
validate_cert = true
|
373
|
-
settings =
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
381
|
+
settings = idp_metadata_parser.parse_remote(
|
382
|
+
"https://example.com/auth/saml2/idp/metadata",
|
383
|
+
validate_cert,
|
384
|
+
entity_id: "http//example.com/target/entity"
|
385
|
+
)
|
378
386
|
```
|
379
387
|
|
380
388
|
### Parsing Metadata into an Hash
|
@@ -383,13 +391,58 @@ The `OneLogin::RubySaml::IdpMetadataParser` also provides the methods `#parse_to
|
|
383
391
|
Those return an Hash instead of a `Settings` object, which may be useful for configuring
|
384
392
|
[omniauth-saml](https://github.com/omniauth/omniauth-saml), for instance.
|
385
393
|
|
394
|
+
|
395
|
+
### Validating Signature of Metadata and retrieve settings
|
396
|
+
|
397
|
+
Right now there is no method at ruby_saml to validate the signature of the metadata that gonna be parsed,
|
398
|
+
but it can be done as follows:
|
399
|
+
* Download the XML.
|
400
|
+
* Validate the Signature, providing the cert.
|
401
|
+
* Provide the XML to the parse method if the signature was validated
|
402
|
+
|
403
|
+
```
|
404
|
+
require "xml_security"
|
405
|
+
require "onelogin/ruby-saml/utils"
|
406
|
+
require "onelogin/ruby-saml/idp_metadata_parser"
|
407
|
+
|
408
|
+
url = "<url_to_the_metadata>"
|
409
|
+
idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new
|
410
|
+
|
411
|
+
uri = URI.parse(url)
|
412
|
+
raise ArgumentError.new("url must begin with http or https") unless /^https?/ =~ uri.scheme
|
413
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
414
|
+
if uri.scheme == "https"
|
415
|
+
http.use_ssl = true
|
416
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
417
|
+
end
|
418
|
+
|
419
|
+
get = Net::HTTP::Get.new(uri.request_uri)
|
420
|
+
get.basic_auth uri.user, uri.password if uri.user
|
421
|
+
response = http.request(get)
|
422
|
+
xml = response.body
|
423
|
+
errors = []
|
424
|
+
doc = XMLSecurity::SignedDocument.new(xml, errors)
|
425
|
+
cert_str = "<include_cert_here>"
|
426
|
+
cert = OneLogin::RubySaml::Utils.format_cert("cert_str")
|
427
|
+
metadata_sign_cert = OpenSSL::X509::Certificate.new(cert)
|
428
|
+
valid = doc.validate_document_with_cert(metadata_sign_cert, true)
|
429
|
+
if valid
|
430
|
+
settings = idp_metadata_parser.parse(
|
431
|
+
xml,
|
432
|
+
entity_id: "<entity_id_of_the_entity_to_be_retrieved>"
|
433
|
+
)
|
434
|
+
else
|
435
|
+
print "Metadata Signarture failed to be verified with the cert provided"
|
436
|
+
end
|
437
|
+
|
438
|
+
|
386
439
|
## Retrieving Attributes
|
387
440
|
|
388
441
|
If you are using `saml:AttributeStatement` to transfer data like the username, you can access all the attributes through `response.attributes`. It contains all the `saml:AttributeStatement`s with its 'Name' as an indifferent key and one or more `saml:AttributeValue`s as values. The value returned depends on the value of the
|
389
442
|
`single_value_compatibility` (when activated, only the first value is returned)
|
390
443
|
|
391
444
|
```ruby
|
392
|
-
response
|
445
|
+
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse])
|
393
446
|
response.settings = saml_settings
|
394
447
|
|
395
448
|
response.attributes[:username]
|
@@ -422,6 +475,9 @@ Imagine this `saml:AttributeStatement`
|
|
422
475
|
<saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>
|
423
476
|
<saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="1"/>
|
424
477
|
</saml:Attribute>
|
478
|
+
<saml:Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname">
|
479
|
+
<saml:AttributeValue>usersName</saml:AttributeValue>
|
480
|
+
</saml:Attribute>
|
425
481
|
</saml:AttributeStatement>
|
426
482
|
```
|
427
483
|
|
@@ -432,7 +488,8 @@ pp(response.attributes) # is an OneLogin::RubySaml::Attributes object
|
|
432
488
|
"another_value"=>["value1", "value2"],
|
433
489
|
"role"=>["role1", "role2", "role3"],
|
434
490
|
"attribute_with_nil_value"=>[nil],
|
435
|
-
"attribute_with_nils_and_empty_strings"=>["", "valuePresent", nil, nil]
|
491
|
+
"attribute_with_nils_and_empty_strings"=>["", "valuePresent", nil, nil]
|
492
|
+
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname"=>["usersName"]}>
|
436
493
|
|
437
494
|
# Active single_value_compatibility
|
438
495
|
OneLogin::RubySaml::Attributes.single_value_compatibility = true
|
@@ -449,6 +506,9 @@ pp(response.attributes.single(:role))
|
|
449
506
|
pp(response.attributes.multi(:role))
|
450
507
|
# => ["role1", "role2", "role3"]
|
451
508
|
|
509
|
+
pp(response.attributes.fetch(:role))
|
510
|
+
# => "role1"
|
511
|
+
|
452
512
|
pp(response.attributes[:attribute_with_nil_value])
|
453
513
|
# => nil
|
454
514
|
|
@@ -464,7 +524,10 @@ pp(response.attributes.single(:not_exists))
|
|
464
524
|
pp(response.attributes.multi(:not_exists))
|
465
525
|
# => nil
|
466
526
|
|
467
|
-
|
527
|
+
pp(response.attributes.fetch(/givenname/))
|
528
|
+
# => "usersName"
|
529
|
+
|
530
|
+
# Deprecated single_value_compatibility
|
468
531
|
OneLogin::RubySaml::Attributes.single_value_compatibility = false
|
469
532
|
|
470
533
|
pp(response.attributes[:uid])
|
@@ -479,6 +542,9 @@ pp(response.attributes.single(:role))
|
|
479
542
|
pp(response.attributes.multi(:role))
|
480
543
|
# => ["role1", "role2", "role3"]
|
481
544
|
|
545
|
+
pp(response.attributes.fetch(:role))
|
546
|
+
# => ["role1", "role2", "role3"]
|
547
|
+
|
482
548
|
pp(response.attributes[:attribute_with_nil_value])
|
483
549
|
# => [nil]
|
484
550
|
|
@@ -493,77 +559,204 @@ pp(response.attributes.single(:not_exists))
|
|
493
559
|
|
494
560
|
pp(response.attributes.multi(:not_exists))
|
495
561
|
# => nil
|
562
|
+
|
563
|
+
pp(response.attributes.fetch(/givenname/))
|
564
|
+
# => ["usersName"]
|
496
565
|
```
|
497
566
|
|
498
567
|
The `saml:AuthnContextClassRef` of the AuthNRequest can be provided by `settings.authn_context`; possible values are described at [SAMLAuthnCxt]. The comparison method can be set using `settings.authn_context_comparison` parameter. Possible values include: 'exact', 'better', 'maximum' and 'minimum' (default value is 'exact').
|
499
568
|
To add a `saml:AuthnContextDeclRef`, define `settings.authn_context_decl_ref`.
|
500
569
|
|
570
|
+
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
|
571
|
+
building the authrequest object.
|
572
|
+
|
573
|
+
## Service Provider Metadata
|
574
|
+
|
575
|
+
To form a trusted pair relationship with the IdP, the SP (you) need to provide metadata XML
|
576
|
+
to the IdP for various good reasons. (Caching, certificate lookups, relaying party permissions, etc)
|
501
577
|
|
502
|
-
|
578
|
+
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.
|
579
|
+
|
580
|
+
The metadata will be polled by the IdP every few minutes, so updating your settings should propagate
|
581
|
+
to the IdP settings.
|
503
582
|
|
504
|
-
|
583
|
+
```ruby
|
584
|
+
class SamlController < ApplicationController
|
585
|
+
# ... the rest of your controller definitions ...
|
586
|
+
def metadata
|
587
|
+
settings = Account.get_saml_settings
|
588
|
+
meta = OneLogin::RubySaml::Metadata.new
|
589
|
+
render :xml => meta.generate(settings), :content_type => "application/samlmetadata+xml"
|
590
|
+
end
|
591
|
+
end
|
592
|
+
```
|
505
593
|
|
506
|
-
|
594
|
+
You can add `ValidUntil` and `CacheDuration` to the SP Metadata XML using instead:
|
507
595
|
|
508
596
|
```ruby
|
509
|
-
|
510
|
-
|
597
|
+
# Valid until => 2 days from now
|
598
|
+
# Cache duration = 604800s = 1 week
|
599
|
+
valid_until = Time.now + 172800
|
600
|
+
cache_duration = 604800
|
601
|
+
meta.generate(settings, false, valid_until, cache_duration)
|
511
602
|
```
|
512
603
|
|
513
|
-
|
604
|
+
## Signing and Decryption
|
605
|
+
|
606
|
+
Ruby SAML supports the following functionality:
|
607
|
+
|
608
|
+
1. Signing your SP Metadata XML
|
609
|
+
2. Signing your SP SAML messages
|
610
|
+
3. Decrypting IdP Assertion messages upon receipt (EncryptedAssertion)
|
611
|
+
4. Verifying signatures on SAML messages and IdP Assertions
|
612
|
+
|
613
|
+
In order to use functions 1-3 above, you must first define your SP public certificate and private key:
|
514
614
|
|
515
615
|
```ruby
|
516
|
-
settings.
|
517
|
-
settings.
|
518
|
-
|
519
|
-
|
520
|
-
|
616
|
+
settings.certificate = "CERTIFICATE TEXT WITH BEGIN/END HEADER AND FOOTER"
|
617
|
+
settings.private_key = "PRIVATE KEY TEXT WITH BEGIN/END HEADER AND FOOTER"
|
618
|
+
```
|
619
|
+
|
620
|
+
Note that the same certificate (and its associated private key) are used to perform
|
621
|
+
all decryption and signing-related functions (1-4) above. Ruby SAML does not currently allow
|
622
|
+
to specify different certificates for each function.
|
521
623
|
|
624
|
+
You may also globally set the SP signature and digest method, to be used in SP signing (functions 1 and 2 above):
|
625
|
+
|
626
|
+
```ruby
|
522
627
|
settings.security[:digest_method] = XMLSecurity::Document::SHA1
|
523
628
|
settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1
|
629
|
+
```
|
524
630
|
|
525
|
-
|
526
|
-
|
527
|
-
|
631
|
+
#### Signing SP Metadata
|
632
|
+
|
633
|
+
You may add a `<ds:Signature>` digital signature element to your SP Metadata XML using the following setting:
|
634
|
+
|
635
|
+
```ruby
|
636
|
+
settings.certificate = "CERTIFICATE TEXT WITH BEGIN/END HEADER AND FOOTER"
|
637
|
+
settings.private_key = "PRIVATE KEY TEXT WITH BEGIN/END HEADER AND FOOTER"
|
638
|
+
|
639
|
+
settings.security[:metadata_signed] = true # Enable signature on Metadata
|
528
640
|
```
|
529
641
|
|
530
|
-
|
642
|
+
#### Signing SP SAML Messages
|
643
|
+
|
644
|
+
Ruby SAML supports SAML request signing. The Service Provider will sign the
|
645
|
+
request/responses with its private key. The Identity Provider will then validate the signature
|
646
|
+
of the received request/responses with the public X.509 cert of the Service Provider.
|
647
|
+
|
648
|
+
To enable, please first set your certificate and private key. This will add `<md:KeyDescriptor use="signing">`
|
649
|
+
to your SP Metadata XML, to be read by the IdP.
|
650
|
+
|
651
|
+
```ruby
|
652
|
+
settings.certificate = "CERTIFICATE TEXT WITH BEGIN/END HEADER AND FOOTER"
|
653
|
+
settings.private_key = "PRIVATE KEY TEXT WITH BEGIN/END HEADER AND FOOTER"
|
654
|
+
```
|
655
|
+
|
656
|
+
Next, you may specify the specific SP SAML messages you would like to sign:
|
657
|
+
|
658
|
+
```ruby
|
659
|
+
settings.security[:authn_requests_signed] = true # Enable signature on AuthNRequest
|
660
|
+
settings.security[:logout_requests_signed] = true # Enable signature on Logout Request
|
661
|
+
settings.security[:logout_responses_signed] = true # Enable signature on Logout Response
|
662
|
+
```
|
663
|
+
|
664
|
+
Signatures will be handled automatically for both `HTTP-Redirect` and `HTTP-Redirect` Binding.
|
665
|
+
Note that the RelayState parameter is used when creating the Signature on the `HTTP-Redirect` Binding.
|
531
666
|
Remember to provide it to the Signature builder if you are sending a `GET RelayState` parameter or the
|
532
667
|
signature validation process will fail at the Identity Provider.
|
533
668
|
|
534
|
-
|
535
|
-
|
536
|
-
|
669
|
+
#### Decrypting IdP SAML Assertions
|
670
|
+
|
671
|
+
Ruby SAML supports EncryptedAssertion. The Identity Provider will encrypt the Assertion with the
|
672
|
+
public cert of the Service Provider. The Service Provider will decrypt the EncryptedAssertion with its private key.
|
537
673
|
|
538
|
-
|
674
|
+
You may enable EncryptedAssertion as follows. This will add `<md:KeyDescriptor use="encrytion">` to your
|
675
|
+
SP Metadata XML, to be read by the IdP.
|
539
676
|
|
540
|
-
|
677
|
+
```ruby
|
678
|
+
settings.certificate = "CERTIFICATE TEXT WITH BEGIN/END HEADER AND FOOTER"
|
679
|
+
settings.private_key = "PRIVATE KEY TEXT WITH BEGIN/END HEADER AND FOOTER"
|
541
680
|
|
542
|
-
|
681
|
+
settings.security[:want_assertions_encrypted] = true # Invalidate SAML messages without an EncryptedAssertion
|
682
|
+
```
|
543
683
|
|
544
|
-
|
684
|
+
#### Verifying Signature on IdP Assertions
|
545
685
|
|
546
|
-
|
686
|
+
You may require the IdP to sign its SAML Assertions using the following setting.
|
687
|
+
With will add `<md:SPSSODescriptor WantAssertionsSigned="true">` to your SP Metadata XML.
|
688
|
+
The signature will be checked against the `<md:KeyDescriptor use="signing">` element
|
689
|
+
present in the IdP's metadata.
|
547
690
|
|
548
691
|
```ruby
|
549
|
-
settings.
|
550
|
-
settings.private_key = "PRIVATE KEY TEXT WITH HEAD AND FOOT"
|
692
|
+
settings.security[:want_assertions_signed] = true # Require the IdP to sign its SAML Assertions
|
551
693
|
```
|
552
694
|
|
553
|
-
|
554
|
-
The Service Provider will decrypt the EncryptedAssertion with its private key.
|
695
|
+
#### Certificate and Signature Validation
|
555
696
|
|
556
|
-
|
697
|
+
You may require SP and IdP certificates to be non-expired using the following settings:
|
557
698
|
|
699
|
+
```ruby
|
700
|
+
settings.security[:check_idp_cert_expiration] = true # Raise error if IdP X.509 cert is expired
|
701
|
+
settings.security[:check_sp_cert_expiration] = true # Raise error SP X.509 cert is expired
|
702
|
+
```
|
703
|
+
|
704
|
+
By default, Ruby SAML will raise a `OneLogin::RubySaml::ValidationError` if a signature or certificate
|
705
|
+
validation fails. You may disable such exceptions using the `settings.security[:soft]` parameter.
|
706
|
+
|
707
|
+
```ruby
|
708
|
+
settings.security[:soft] = true # Do not raise error on failed signature/certificate validations
|
709
|
+
```
|
710
|
+
|
711
|
+
#### Audience Validation
|
712
|
+
|
713
|
+
A service provider should only consider a SAML response valid if the IdP includes an <AudienceRestriction>
|
714
|
+
element containting an <Audience> element that uniquely identifies the service provider. Unless you specify
|
715
|
+
the `skip_audience` option, Ruby SAML will validate that each SAML response includes an <Audience> element
|
716
|
+
whose contents matches `settings.sp_entity_id`.
|
717
|
+
|
718
|
+
By default, Ruby SAML considers an <AudienceRestriction> element containing only empty <Audience> elements
|
719
|
+
to be valid. That means an otherwise valid SAML response with a condition like this would be valid:
|
720
|
+
|
721
|
+
```xml
|
722
|
+
<AudienceRestriction>
|
723
|
+
<Audience />
|
724
|
+
</AudienceRestriction>
|
725
|
+
```
|
726
|
+
|
727
|
+
You may enforce that an <AudienceRestriction> element containing only empty <Audience> elements
|
728
|
+
is invalid using the `settings.security[:strict_audience_validation]` parameter.
|
558
729
|
|
559
|
-
|
730
|
+
```ruby
|
731
|
+
settings.security[:strict_audience_validation] = true
|
732
|
+
```
|
733
|
+
|
734
|
+
#### Key Rollover
|
735
|
+
|
736
|
+
To update the SP X.509 certificate and private key without disruption of service, you may define the parameter
|
737
|
+
`settings.certificate_new`. This will publish the new SP certificate in your metadata so that your IdP counterparties
|
738
|
+
may cache it in preparation for rollover.
|
739
|
+
|
740
|
+
For example, if you to rollover from `CERT A` to `CERT B`. Before rollover, your settings should look as follows.
|
741
|
+
Both `CERT A` and `CERT B` will now appear in your SP metadata, however `CERT A` will still be used for signing
|
742
|
+
and encryption at this time.
|
743
|
+
|
744
|
+
```ruby
|
745
|
+
settings.certificate = "CERT A"
|
746
|
+
settings.private_key = "PRIVATE KEY FOR CERT A"
|
747
|
+
settings.certificate_new = "CERT B"
|
748
|
+
```
|
560
749
|
|
561
|
-
|
750
|
+
After the IdP has cached `CERT B`, you may then change your settings as follows:
|
562
751
|
|
752
|
+
```ruby
|
753
|
+
settings.certificate = "CERT B"
|
754
|
+
settings.private_key = "PRIVATE KEY FOR CERT B"
|
755
|
+
```
|
563
756
|
|
564
757
|
## Single Log Out
|
565
758
|
|
566
|
-
|
759
|
+
Ruby SAML supports SP-initiated Single Logout and IdP-Initiated Single Logout.
|
567
760
|
|
568
761
|
Here is an example that we could add to our previous controller to generate and send a SAML Logout Request to the IdP:
|
569
762
|
|
@@ -573,22 +766,28 @@ def sp_logout_request
|
|
573
766
|
# LogoutRequest accepts plain browser requests w/o paramters
|
574
767
|
settings = saml_settings
|
575
768
|
|
576
|
-
if settings.
|
769
|
+
if settings.idp_slo_service_url.nil?
|
577
770
|
logger.info "SLO IdP Endpoint not found in settings, executing then a normal logout'"
|
578
771
|
delete_session
|
579
772
|
else
|
580
773
|
|
581
|
-
|
582
|
-
|
583
|
-
logout_request = OneLogin::RubySaml::Logoutrequest.new()
|
584
|
-
session[:transaction_id] = logout_request.uuid
|
585
|
-
logger.info "New SP SLO for userid '#{session[:userid]}' transactionid '#{session[:transaction_id]}'"
|
774
|
+
logout_request = OneLogin::RubySaml::Logoutrequest.new
|
775
|
+
logger.info "New SP SLO for userid '#{session[:userid]}' transactionid '#{logout_request.uuid}'"
|
586
776
|
|
587
777
|
if settings.name_identifier_value.nil?
|
588
778
|
settings.name_identifier_value = session[:userid]
|
589
779
|
end
|
590
780
|
|
591
|
-
|
781
|
+
# Ensure user is logged out before redirect to IdP, in case anything goes wrong during single logout process (as recommended by saml2int [SDP-SP34])
|
782
|
+
logged_user = session[:userid]
|
783
|
+
logger.info "Delete session for '#{session[:userid]}'"
|
784
|
+
delete_session
|
785
|
+
|
786
|
+
# Save the transaction_id to compare it with the response we get back
|
787
|
+
session[:transaction_id] = logout_request.uuid
|
788
|
+
session[:logged_out_user] = logged_user
|
789
|
+
|
790
|
+
relayState = url_for(controller: 'saml', action: 'index')
|
592
791
|
redirect_to(logout_request.create(settings, :RelayState => relayState))
|
593
792
|
end
|
594
793
|
end
|
@@ -615,7 +814,7 @@ def process_logout_response
|
|
615
814
|
logger.error "The SAML Logout Response is invalid"
|
616
815
|
else
|
617
816
|
# Actually log out this session
|
618
|
-
logger.info "
|
817
|
+
logger.info "SLO completed for '#{session[:logged_out_user]}'"
|
619
818
|
delete_session
|
620
819
|
end
|
621
820
|
end
|
@@ -624,6 +823,8 @@ end
|
|
624
823
|
def delete_session
|
625
824
|
session[:userid] = nil
|
626
825
|
session[:attributes] = nil
|
826
|
+
session[:transaction_id] = nil
|
827
|
+
session[:logged_out_user] = nil
|
627
828
|
end
|
628
829
|
```
|
629
830
|
|
@@ -633,10 +834,16 @@ Here is an example that we could add to our previous controller to process a SAM
|
|
633
834
|
# Method to handle IdP initiated logouts
|
634
835
|
def idp_logout_request
|
635
836
|
settings = Account.get_saml_settings
|
636
|
-
|
837
|
+
# ADFS URL-Encodes SAML data as lowercase, and the toolkit by default uses
|
838
|
+
# uppercase. Turn it True for ADFS compatibility on signature verification
|
839
|
+
settings.security[:lowercase_url_encoding] = true
|
840
|
+
|
841
|
+
logout_request = OneLogin::RubySaml::SloLogoutrequest.new(
|
842
|
+
params[:SAMLRequest], settings: settings
|
843
|
+
)
|
637
844
|
if !logout_request.is_valid?
|
638
845
|
logger.error "IdP initiated LogoutRequest was not valid!"
|
639
|
-
render :inline => logger.error
|
846
|
+
return render :inline => logger.error
|
640
847
|
end
|
641
848
|
logger.info "IdP initiated Logout for #{logout_request.name_id}"
|
642
849
|
|
@@ -668,30 +875,6 @@ def logout
|
|
668
875
|
end
|
669
876
|
```
|
670
877
|
|
671
|
-
|
672
|
-
|
673
|
-
## Service Provider Metadata
|
674
|
-
|
675
|
-
To form a trusted pair relationship with the IdP, the SP (you) need to provide metadata XML
|
676
|
-
to the IdP for various good reasons. (Caching, certificate lookups, relaying party permissions, etc)
|
677
|
-
|
678
|
-
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.
|
679
|
-
|
680
|
-
The metadata will be polled by the IdP every few minutes, so updating your settings should propagate
|
681
|
-
to the IdP settings.
|
682
|
-
|
683
|
-
```ruby
|
684
|
-
class SamlController < ApplicationController
|
685
|
-
# ... the rest of your controller definitions ...
|
686
|
-
def metadata
|
687
|
-
settings = Account.get_saml_settings
|
688
|
-
meta = OneLogin::RubySaml::Metadata.new
|
689
|
-
render :xml => meta.generate(settings), :content_type => "application/samlmetadata+xml"
|
690
|
-
end
|
691
|
-
end
|
692
|
-
```
|
693
|
-
|
694
|
-
|
695
878
|
## Clock Drift
|
696
879
|
|
697
880
|
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.
|
@@ -706,13 +889,33 @@ response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], :allowed_cloc
|
|
706
889
|
|
707
890
|
Make sure to keep the value as comfortably small as possible to keep security risks to a minimum.
|
708
891
|
|
892
|
+
## Deflation Limit
|
893
|
+
|
894
|
+
To protect against decompression bombs (a form of DoS attack), SAML messages are limited to 250,000 bytes by default.
|
895
|
+
Sometimes legitimate SAML messages will exceed this limit,
|
896
|
+
for example due to custom claims like including groups a user is a member of.
|
897
|
+
If you want to customize this limit, you need to provide a different setting when initializing the response object.
|
898
|
+
Example:
|
899
|
+
|
900
|
+
```ruby
|
901
|
+
def consume
|
902
|
+
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], { settings: saml_settings })
|
903
|
+
...
|
904
|
+
end
|
905
|
+
|
906
|
+
private
|
907
|
+
|
908
|
+
def saml_settings
|
909
|
+
OneLogin::RubySaml::Settings.new(message_max_bytesize: 500_000)
|
910
|
+
end
|
911
|
+
```
|
912
|
+
|
709
913
|
## Attribute Service
|
710
914
|
|
711
915
|
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.
|
712
916
|
|
713
917
|
```ruby
|
714
918
|
settings = OneLogin::RubySaml::Settings.new
|
715
|
-
|
716
919
|
settings.attributes_index = 5
|
717
920
|
settings.attribute_consuming_service.configure do
|
718
921
|
service_name "Service"
|
@@ -723,3 +926,27 @@ end
|
|
723
926
|
```
|
724
927
|
|
725
928
|
The `attribute_value` option additionally accepts an array of possible values.
|
929
|
+
|
930
|
+
## Custom Metadata Fields
|
931
|
+
|
932
|
+
Some IdPs may require to add SPs to add additional fields (Organization, ContactPerson, etc.)
|
933
|
+
into the SP metadata. This can be achieved by extending the `OneLogin::RubySaml::Metadata`
|
934
|
+
class and overriding the `#add_extras` method as per the following example:
|
935
|
+
|
936
|
+
```ruby
|
937
|
+
class MyMetadata < OneLogin::RubySaml::Metadata
|
938
|
+
def add_extras(root, _settings)
|
939
|
+
org = root.add_element("md:Organization")
|
940
|
+
org.add_element("md:OrganizationName", 'xml:lang' => "en-US").text = 'ACME Inc.'
|
941
|
+
org.add_element("md:OrganizationDisplayName", 'xml:lang' => "en-US").text = 'ACME'
|
942
|
+
org.add_element("md:OrganizationURL", 'xml:lang' => "en-US").text = 'https://www.acme.com'
|
943
|
+
|
944
|
+
cp = root.add_element("md:ContactPerson", 'contactType' => 'technical')
|
945
|
+
cp.add_element("md:GivenName").text = 'ACME SAML Team'
|
946
|
+
cp.add_element("md:EmailAddress").text = 'saml@acme.com'
|
947
|
+
end
|
948
|
+
end
|
949
|
+
|
950
|
+
# Output XML with custom metadata
|
951
|
+
MyMetadata.new.generate(settings)
|
952
|
+
```
|