ruby-saml 1.12.4 → 1.18.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/FUNDING.yml +3 -0
- data/.github/workflows/test.yml +29 -2
- data/{changelog.md → CHANGELOG.md} +64 -15
- data/LICENSE +2 -1
- data/README.md +425 -233
- data/UPGRADING.md +158 -0
- data/lib/onelogin/ruby-saml/authrequest.rb +9 -11
- data/lib/onelogin/ruby-saml/idp_metadata_parser.rb +115 -84
- data/lib/onelogin/ruby-saml/logoutrequest.rb +9 -9
- data/lib/onelogin/ruby-saml/logoutresponse.rb +2 -2
- data/lib/onelogin/ruby-saml/metadata.rb +75 -42
- data/lib/onelogin/ruby-saml/response.rb +130 -70
- data/lib/onelogin/ruby-saml/saml_message.rb +16 -19
- data/lib/onelogin/ruby-saml/settings.rb +214 -110
- data/lib/onelogin/ruby-saml/slo_logoutrequest.rb +51 -37
- data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +9 -9
- data/lib/onelogin/ruby-saml/utils.rb +129 -46
- data/lib/onelogin/ruby-saml/version.rb +1 -1
- data/lib/xml_security.rb +81 -48
- data/ruby-saml.gemspec +40 -14
- metadata +29 -32
- data/.travis.yml +0 -48
data/README.md
CHANGED
|
@@ -1,169 +1,109 @@
|
|
|
1
|
-
# Ruby SAML
|
|
1
|
+
# Ruby SAML
|
|
2
|
+
[](https://github.com/SAML-Toolkits/ruby-saml/actions/workflows/test.yml)
|
|
3
|
+
[](https://coveralls.io/github/SAML-Toolkits/ruby-saml?branch=master)
|
|
4
|
+
[](https://badge.fury.io/rb/ruby-saml)
|
|
5
|
+
[](https://badge.fury.io/gh/SAML-Toolkits%2Fruby-saml)   
|
|
2
6
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
change/adds specific error messages for signature validations
|
|
7
|
+
Minor and patch versions of Ruby SAML may introduce breaking changes. Please read
|
|
8
|
+
[UPGRADING.md](UPGRADING.md) for guidance on upgrading to new Ruby SAML versions.
|
|
6
9
|
|
|
7
|
-
|
|
8
|
-
In IDPMetadataParser, `parse`, `parse_to_hash` and `parse_to_array` methods now retrieve SSO URL and SLO URL endpoints with
|
|
9
|
-
`idp_sso_service_url` and `idp_slo_service_url` (previously `idp_sso_target_url` and `idp_slo_target_url` respectively).
|
|
10
|
+
### Pay it Forward: Support RubySAML and Strengthen Open-Source Security
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
Version `1.11.0` deprecates the use of `settings.issuer` in favour of `settings.sp_entity_id`.
|
|
13
|
-
There are two new security settings: `settings.security[:check_idp_cert_expiration]` and `settings.security[:check_sp_cert_expiration]` (both false by default) that check if the IdP or SP X.509 certificate has expired, respectively.
|
|
12
|
+
RubySAML is a trusted authentication library used by startups and enterprises alike.
|
|
14
13
|
|
|
15
|
-
|
|
14
|
+
But security doesn't happen in a vacuum. Vulnerabilities in authentication libraries can
|
|
15
|
+
have widespread consequences. Maintaining open-source security requires continuous
|
|
16
|
+
effort, expertise, and funding. By supporting RubySAML, you’re not just securing your
|
|
17
|
+
own systems—you’re strengthening auth security globally.
|
|
16
18
|
|
|
17
|
-
|
|
19
|
+
#### How you can help
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
* Sponsor RubySAML: [GitHub Sponsors](https://github.com/sponsors/SAML-Toolkits)
|
|
22
|
+
* Contribute to secure-by-design improvements
|
|
23
|
+
* Responsibly report vulnerabilities (see "Vulnerability Reporting" above)
|
|
21
24
|
|
|
22
|
-
|
|
23
|
-
|
|
25
|
+
Security is a shared responsibility. If RubySAML has helped your organization, please
|
|
26
|
+
consider giving back. Together, we can keep authentication secure.
|
|
24
27
|
|
|
25
|
-
|
|
26
|
-
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.
|
|
28
|
+
### Sponsors
|
|
27
29
|
|
|
28
|
-
|
|
30
|
+
Thanks to the following sponsors for securing the open source ecosystem,
|
|
29
31
|
|
|
30
|
-
|
|
32
|
+
[<img alt="84codes" src="https://avatars.githubusercontent.com/u/5353257" width="75px">](https://www.84codes.com)
|
|
31
33
|
|
|
32
|
-
## Updating from 1.5.0 to 1.6.0
|
|
33
34
|
|
|
34
|
-
|
|
35
|
+
## Vulnerabilities
|
|
35
36
|
|
|
36
|
-
|
|
37
|
+
CVE-2025-54572 affects version ruby-saml < 1.18.1
|
|
37
38
|
|
|
38
|
-
```ruby
|
|
39
|
-
# In this example `query_params` is assumed to contain decoded query parameters,
|
|
40
|
-
# and `raw_query_params` is assumed to contain encoded query parameters as sent by the IDP.
|
|
41
|
-
settings = {
|
|
42
|
-
settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1
|
|
43
|
-
settings.soft = false
|
|
44
|
-
}
|
|
45
|
-
options = {
|
|
46
|
-
get_params: {
|
|
47
|
-
"Signature" => query_params["Signature"],
|
|
48
|
-
},
|
|
49
|
-
raw_get_params: {
|
|
50
|
-
"SAMLRequest" => raw_query_params["SAMLRequest"],
|
|
51
|
-
"SigAlg" => raw_query_params["SigAlg"],
|
|
52
|
-
"RelayState" => raw_query_params["RelayState"],
|
|
53
|
-
},
|
|
54
|
-
}
|
|
55
|
-
slo_logout_request = OneLogin::RubySaml::SloLogoutrequest.new(query_params["SAMLRequest"], settings, options)
|
|
56
|
-
raise "Invalid Logout Request" unless slo_logout_request.is_valid?
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
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.
|
|
60
|
-
|
|
61
|
-
## Updating from 1.4.2 to 1.4.3
|
|
62
|
-
|
|
63
|
-
Version `1.4.3` introduces Recipient validation of SubjectConfirmation elements.
|
|
64
|
-
The 'Recipient' value is compared with the settings.assertion_consumer_service_url
|
|
65
|
-
value.
|
|
66
|
-
If you want to skip that validation, add the :skip_recipient_check option to the
|
|
67
|
-
initialize method of the Response object.
|
|
68
|
-
|
|
69
|
-
Parsing metadata that contains more than one certificate will propagate the
|
|
70
|
-
idp_cert_multi property rather than idp_cert. See [signature validation
|
|
71
|
-
section](#signature-validation) for details.
|
|
72
|
-
|
|
73
|
-
## Updating from 1.3.x to 1.4.X
|
|
74
|
-
|
|
75
|
-
Version `1.4.0` is a recommended update for all Ruby SAML users as it includes security improvements.
|
|
76
39
|
|
|
77
|
-
|
|
40
|
+
There are critical vulnerabilities affecting ruby-saml < 1.18.0, two of them allows SAML authentication bypass (CVE-2025-25291, CVE-2025-25292, CVE-2025-25293). Please upgrade to a fixed version (1.18.0)
|
|
78
41
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
## Updating from 1.1.x to 1.2.X
|
|
82
|
-
|
|
83
|
-
Version `1.2` adds IDP metadata parsing improvements, uuid deprecation in favour of SecureRandom, refactor error handling and some minor improvements
|
|
84
|
-
|
|
85
|
-
There is no compatibility issue detected.
|
|
86
|
-
|
|
87
|
-
For more details, please review [the changelog](changelog.md).
|
|
88
|
-
|
|
89
|
-
## Updating from 1.0.x to 1.1.X
|
|
90
|
-
|
|
91
|
-
Version `1.1` adds some improvements on signature validation and solves some namespace conflicts.
|
|
92
|
-
|
|
93
|
-
## Updating from 0.9.x to 1.0.X
|
|
94
|
-
|
|
95
|
-
Version `1.0` is a recommended update for all Ruby SAML users as it includes security fixes.
|
|
42
|
+
## Overview
|
|
96
43
|
|
|
97
|
-
|
|
44
|
+
The Ruby SAML library is for implementing the client side of a SAML authorization,
|
|
45
|
+
i.e. it provides a means for managing authorization initialization and confirmation
|
|
46
|
+
requests from identity providers.
|
|
98
47
|
|
|
99
|
-
|
|
100
|
-
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.
|
|
48
|
+
SAML authorization is a two-step process and you are expected to implement support for both.
|
|
101
49
|
|
|
102
|
-
|
|
103
|
-
|
|
50
|
+
We created a demo project for Rails 4 that uses the latest version of this library:
|
|
51
|
+
[ruby-saml-example](https://github.com/saml-toolkits/ruby-saml-example)
|
|
104
52
|
|
|
105
|
-
|
|
106
|
-
Version `0.8.x` changes the namespace of the gem from `OneLogin::Saml` to `OneLogin::RubySaml`. Please update your implementations of the gem accordingly.
|
|
53
|
+
### Supported Ruby Versions
|
|
107
54
|
|
|
108
|
-
|
|
55
|
+
The following Ruby versions are covered by CI testing:
|
|
109
56
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
We created a demo project for Rails4 that uses the latest version of this library: [ruby-saml-example](https://github.com/onelogin/ruby-saml-example)
|
|
115
|
-
|
|
116
|
-
### Supported versions of Ruby
|
|
117
|
-
* 1.8.7
|
|
118
|
-
* 1.9.x
|
|
119
|
-
* 2.0.x
|
|
120
|
-
* 2.1.x
|
|
121
|
-
* 2.2.x
|
|
122
|
-
* 2.3.x
|
|
123
|
-
* 2.4.x
|
|
124
|
-
* 2.5.x
|
|
125
|
-
* 2.6.x
|
|
126
|
-
* 2.7.x
|
|
127
|
-
* 3.0.x
|
|
128
|
-
* JRuby 1.7.x
|
|
129
|
-
* JRuby 9.0.x
|
|
130
|
-
* JRuby 9.1.x
|
|
131
|
-
* JRuby 9.2.x
|
|
57
|
+
* Ruby (MRI) 2.1 to 3.4
|
|
58
|
+
* JRuby 9.1 to 9.4
|
|
59
|
+
* TruffleRuby (latest)
|
|
132
60
|
|
|
133
61
|
## Adding Features, Pull Requests
|
|
62
|
+
|
|
134
63
|
* Fork the repository
|
|
135
64
|
* Make your feature addition or bug fix
|
|
136
65
|
* Add tests for your new features. This is important so we don't break any features in a future version unintentionally.
|
|
137
|
-
* Ensure all tests pass
|
|
138
|
-
* Do not change
|
|
66
|
+
* Ensure all tests pass by running `bundle exec rake test`.
|
|
67
|
+
* Do not change Rakefile, version, or history.
|
|
139
68
|
* Open a pull request, following [this template](https://gist.github.com/Lordnibbler/11002759).
|
|
140
69
|
|
|
141
70
|
## Security Guidelines
|
|
142
71
|
|
|
143
|
-
If you believe you have discovered a security vulnerability in this gem, please report it
|
|
72
|
+
If you believe you have discovered a security vulnerability in this gem, please report it
|
|
73
|
+
by mail to the maintainer: sixto.martin.garcia+security@gmail.com
|
|
144
74
|
|
|
145
|
-
### Security
|
|
75
|
+
### Security Warning
|
|
146
76
|
|
|
147
77
|
Some tools may incorrectly report ruby-saml is a potential security vulnerability.
|
|
148
|
-
ruby-saml depends on Nokogiri, and it
|
|
78
|
+
ruby-saml depends on Nokogiri, and it is possible to use Nokogiri in a dangerous way
|
|
149
79
|
(by enabling its DTDLOAD option and disabling its NONET option).
|
|
150
80
|
This dangerous Nokogiri configuration, which is sometimes used by other components,
|
|
151
81
|
can create an XML External Entity (XXE) vulnerability if the XML data is not trusted.
|
|
152
82
|
However, ruby-saml never enables this dangerous Nokogiri configuration;
|
|
153
83
|
ruby-saml never enables DTDLOAD, and it never disables NONET.
|
|
154
84
|
|
|
85
|
+
The OneLogin::RubySaml::IdpMetadataParser class does not validate the provided URL before parsing.
|
|
86
|
+
|
|
87
|
+
Usually, the same administrator who handles the Service Provider also sets the URL to
|
|
88
|
+
the IdP, which should be a trusted resource.
|
|
89
|
+
|
|
90
|
+
But there are other scenarios, like a SaaS app where the administrator of the app
|
|
91
|
+
delegates this functionality to other users. In this case, extra precautions should
|
|
92
|
+
be taken in order to validate such URL inputs and avoid attacks like SSRF.
|
|
155
93
|
|
|
156
94
|
## Getting Started
|
|
157
|
-
|
|
95
|
+
|
|
96
|
+
In order to use Ruby SAML you will need to install the gem (either manually or using Bundler),
|
|
97
|
+
and require the library in your Ruby application:
|
|
158
98
|
|
|
159
99
|
Using `Gemfile`
|
|
160
100
|
|
|
161
101
|
```ruby
|
|
162
102
|
# latest stable
|
|
163
|
-
gem 'ruby-saml', '~> 1.
|
|
103
|
+
gem 'ruby-saml', '~> 1.18.0'
|
|
164
104
|
|
|
165
105
|
# or track master for bleeding-edge
|
|
166
|
-
gem 'ruby-saml', :github => '
|
|
106
|
+
gem 'ruby-saml', :github => 'saml-toolkit/ruby-saml'
|
|
167
107
|
```
|
|
168
108
|
|
|
169
109
|
Using RubyGems
|
|
@@ -172,7 +112,8 @@ Using RubyGems
|
|
|
172
112
|
gem install ruby-saml
|
|
173
113
|
```
|
|
174
114
|
|
|
175
|
-
|
|
115
|
+
You may require the entire Ruby SAML gem:
|
|
116
|
+
|
|
176
117
|
```ruby
|
|
177
118
|
require 'onelogin/ruby-saml'
|
|
178
119
|
```
|
|
@@ -185,7 +126,9 @@ require 'onelogin/ruby-saml/authrequest'
|
|
|
185
126
|
|
|
186
127
|
### Installation on Ruby 1.8.7
|
|
187
128
|
|
|
188
|
-
This gem uses Nokogiri as a dependency, which dropped support for Ruby 1.8.x in Nokogiri 1.6.
|
|
129
|
+
This gem uses Nokogiri as a dependency, which dropped support for Ruby 1.8.x in Nokogiri 1.6.
|
|
130
|
+
When installing this gem on Ruby 1.8.7, you will need to make sure a version of Nokogiri
|
|
131
|
+
prior to 1.6 is installed or specified if it hasn't been already.
|
|
189
132
|
|
|
190
133
|
Using `Gemfile`
|
|
191
134
|
|
|
@@ -202,8 +145,8 @@ gem install nokogiri --version '~> 1.5.10'
|
|
|
202
145
|
### Configuring Logging
|
|
203
146
|
|
|
204
147
|
When troubleshooting SAML integration issues, you will find it extremely helpful to examine the
|
|
205
|
-
output of this gem's business logic. By default, log messages are emitted to RAILS_DEFAULT_LOGGER
|
|
206
|
-
when the gem is used in a Rails context, and to STDOUT when the gem is used outside of Rails.
|
|
148
|
+
output of this gem's business logic. By default, log messages are emitted to `RAILS_DEFAULT_LOGGER`
|
|
149
|
+
when the gem is used in a Rails context, and to `STDOUT` when the gem is used outside of Rails.
|
|
207
150
|
|
|
208
151
|
To override the default behavior and control the destination of log messages, provide
|
|
209
152
|
a ruby Logger object to the gem's logging singleton:
|
|
@@ -214,7 +157,10 @@ OneLogin::RubySaml::Logging.logger = Logger.new('/var/log/ruby-saml.log')
|
|
|
214
157
|
|
|
215
158
|
## The Initialization Phase
|
|
216
159
|
|
|
217
|
-
This is the first request you will get from the identity provider. It will hit your application
|
|
160
|
+
This is the first request you will get from the identity provider. It will hit your application
|
|
161
|
+
at a specific URL that you've announced as your SAML initialization point. The response to
|
|
162
|
+
this initialization is a redirect back to the identity provider, which can look something
|
|
163
|
+
like this (ignore the saml_settings method call for now):
|
|
218
164
|
|
|
219
165
|
```ruby
|
|
220
166
|
def init
|
|
@@ -223,7 +169,7 @@ def init
|
|
|
223
169
|
end
|
|
224
170
|
```
|
|
225
171
|
|
|
226
|
-
If the SP knows who should be authenticated in the IdP,
|
|
172
|
+
If the SP knows who should be authenticated in the IdP, it can provide that info as follows:
|
|
227
173
|
|
|
228
174
|
```ruby
|
|
229
175
|
def init
|
|
@@ -234,7 +180,10 @@ def init
|
|
|
234
180
|
end
|
|
235
181
|
```
|
|
236
182
|
|
|
237
|
-
Once you've redirected back to the identity provider, it will ensure that the user has been
|
|
183
|
+
Once you've redirected back to the identity provider, it will ensure that the user has been
|
|
184
|
+
authorized and redirect back to your application for final consumption.
|
|
185
|
+
This can look something like this (the `authorize_success` and `authorize_failure`
|
|
186
|
+
methods are specific to your application):
|
|
238
187
|
|
|
239
188
|
```ruby
|
|
240
189
|
def consume
|
|
@@ -252,16 +201,19 @@ def consume
|
|
|
252
201
|
end
|
|
253
202
|
```
|
|
254
203
|
|
|
255
|
-
In the above there are a few assumptions, one being that `response.nameid` is an email address.
|
|
204
|
+
In the above there are a few assumptions, one being that `response.nameid` is an email address.
|
|
205
|
+
This is all handled with how you specify the settings that are in play via the `saml_settings` method.
|
|
206
|
+
That could be implemented along the lines of this:
|
|
256
207
|
|
|
257
208
|
```
|
|
258
209
|
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse])
|
|
259
210
|
response.settings = saml_settings
|
|
260
211
|
```
|
|
261
212
|
|
|
262
|
-
If the assertion of the SAMLResponse is not encrypted, you can initialize the Response
|
|
263
|
-
|
|
264
|
-
|
|
213
|
+
If the assertion of the SAMLResponse is not encrypted, you can initialize the Response
|
|
214
|
+
without the `:settings` parameter and set it later. If the SAMLResponse contains an encrypted
|
|
215
|
+
assertion, you need to provide the settings in the initialize method in order to obtain the
|
|
216
|
+
decrypted assertion, using the service provider private key in order to decrypt.
|
|
265
217
|
If you don't know what expect, always use the former (set the settings on initialize).
|
|
266
218
|
|
|
267
219
|
```ruby
|
|
@@ -271,8 +223,10 @@ def saml_settings
|
|
|
271
223
|
settings.assertion_consumer_service_url = "http://#{request.host}/saml/consume"
|
|
272
224
|
settings.sp_entity_id = "http://#{request.host}/saml/metadata"
|
|
273
225
|
settings.idp_entity_id = "https://app.onelogin.com/saml/metadata/#{OneLoginAppId}"
|
|
274
|
-
settings.idp_sso_service_url
|
|
275
|
-
settings.
|
|
226
|
+
settings.idp_sso_service_url = "https://app.onelogin.com/trust/saml2/http-post/sso/#{OneLoginAppId}"
|
|
227
|
+
settings.idp_sso_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" # or :post, :redirect
|
|
228
|
+
settings.idp_slo_service_url = "https://app.onelogin.com/trust/saml2/http-redirect/slo/#{OneLoginAppId}"
|
|
229
|
+
settings.idp_slo_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" # or :post, :redirect
|
|
276
230
|
settings.idp_cert_fingerprint = OneLoginAppCertFingerPrint
|
|
277
231
|
settings.idp_cert_fingerprint_algorithm = "http://www.w3.org/2000/09/xmldsig#sha1"
|
|
278
232
|
settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
|
|
@@ -285,27 +239,30 @@ def saml_settings
|
|
|
285
239
|
"urn:oasis:names:tc:SAML:2.0:ac:classes:Password"
|
|
286
240
|
]
|
|
287
241
|
|
|
288
|
-
# Optional bindings (defaults to Redirect for logout POST for
|
|
289
|
-
settings.single_logout_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
|
|
290
|
-
settings.assertion_consumer_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
|
|
242
|
+
# Optional bindings (defaults to Redirect for logout POST for ACS)
|
|
243
|
+
settings.single_logout_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" # or :post, :redirect
|
|
244
|
+
settings.assertion_consumer_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" # or :post, :redirect
|
|
291
245
|
|
|
292
246
|
settings
|
|
293
247
|
end
|
|
294
248
|
```
|
|
295
249
|
|
|
296
|
-
The use of settings.issuer is deprecated in
|
|
250
|
+
The use of `settings.issuer` is deprecated in favor of `settings.sp_entity_id` since version 1.11.0
|
|
297
251
|
|
|
298
|
-
Some assertion validations can be skipped by passing parameters to `OneLogin::RubySaml::Response.new()`.
|
|
252
|
+
Some assertion validations can be skipped by passing parameters to `OneLogin::RubySaml::Response.new()`.
|
|
253
|
+
For example, you can skip the `AuthnStatement`, `Conditions`, `Recipient`, or the `SubjectConfirmation`
|
|
254
|
+
validations by initializing the response with different options:
|
|
299
255
|
|
|
300
256
|
```ruby
|
|
301
257
|
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], {skip_authnstatement: true}) # skips AuthnStatement
|
|
302
258
|
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], {skip_conditions: true}) # skips conditions
|
|
303
259
|
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], {skip_subject_confirmation: true}) # skips subject confirmation
|
|
304
|
-
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], {skip_recipient_check: true}) #
|
|
260
|
+
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
|
|
305
261
|
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], {skip_audience: true}) # skips audience check
|
|
306
262
|
```
|
|
307
263
|
|
|
308
|
-
All that's left is to wrap everything in a controller and reference it in the initialization and
|
|
264
|
+
All that's left is to wrap everything in a controller and reference it in the initialization and
|
|
265
|
+
consumption URLs in OneLogin. A full controller example could look like this:
|
|
309
266
|
|
|
310
267
|
```ruby
|
|
311
268
|
# This controller expects you to use the URLs /saml/init and /saml/consume in your OneLogin application.
|
|
@@ -358,44 +315,56 @@ class SamlController < ApplicationController
|
|
|
358
315
|
end
|
|
359
316
|
```
|
|
360
317
|
|
|
318
|
+
## Signature Validation
|
|
361
319
|
|
|
362
|
-
|
|
320
|
+
Ruby SAML allows different ways to validate the signature of the SAMLResponse:
|
|
321
|
+
- You can provide the IdP X.509 public certificate at the `idp_cert` setting.
|
|
322
|
+
- You can provide the IdP X.509 public certificate in fingerprint format using the
|
|
323
|
+
`idp_cert_fingerprint` setting parameter and additionally the `idp_cert_fingerprint_algorithm` parameter.
|
|
363
324
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
325
|
+
When validating the signature of redirect binding, the fingerprint is useless and the certificate
|
|
326
|
+
of the IdP is required in order to execute the validation. You can pass the option
|
|
327
|
+
`:relax_signature_validation` to `SloLogoutrequest` and `Logoutresponse` if want to avoid signature
|
|
328
|
+
validation if no certificate of the IdP is provided.
|
|
367
329
|
|
|
368
|
-
|
|
369
|
-
|
|
330
|
+
In production also we highly recommend to register on the settings the IdP certificate instead
|
|
331
|
+
of using the fingerprint method. The fingerprint, is a hash, so at the end is open to a collision
|
|
332
|
+
attack that can end on a signature validation bypass. Other SAML toolkits deprecated that mechanism,
|
|
333
|
+
we maintain it for compatibility and also to be used on test environment.
|
|
370
334
|
|
|
371
|
-
|
|
335
|
+
## Handling Multiple IdP Certificates
|
|
372
336
|
|
|
373
|
-
|
|
337
|
+
If the IdP metadata XML includes multiple certificates, you may specify the `idp_cert_multi`
|
|
338
|
+
parameter. When used, the `idp_cert` and `idp_cert_fingerprint` parameters are ignored.
|
|
339
|
+
This is useful in the following scenarios:
|
|
374
340
|
|
|
375
|
-
|
|
376
|
-
|
|
341
|
+
* The IdP uses different certificates for signing versus encryption.
|
|
342
|
+
* The IdP is undergoing a key rollover and is publishing the old and new certificates in parallel.
|
|
377
343
|
|
|
378
|
-
|
|
344
|
+
The `idp_cert_multi` must be a `Hash` as follows. The `:signing` and `:encryption` arrays below,
|
|
345
|
+
add the IdP X.509 public certificates which were published in the IdP metadata.
|
|
346
|
+
|
|
347
|
+
```ruby
|
|
379
348
|
{
|
|
380
349
|
:signing => [],
|
|
381
350
|
:encryption => []
|
|
382
351
|
}
|
|
383
|
-
|
|
384
|
-
And on 'signing' and 'encryption' arrays, add the different IdP x509 public certificates published on the IdP metadata.
|
|
385
|
-
|
|
352
|
+
```
|
|
386
353
|
|
|
387
354
|
## Metadata Based Configuration
|
|
388
355
|
|
|
389
|
-
The method above requires a little extra work to manually specify attributes about
|
|
390
|
-
|
|
356
|
+
The method above requires a little extra work to manually specify attributes about both the IdP and your SP application.
|
|
357
|
+
There's an easier method: use a metadata exchange. Metadata is an XML file that defines the capabilities of both the IdP
|
|
358
|
+
and the SP application. It also contains the X.509 public key certificates which add to the trusted relationship.
|
|
359
|
+
The IdP administrator can also configure custom settings for an SP based on the metadata.
|
|
391
360
|
|
|
392
|
-
Using
|
|
361
|
+
Using `IdpMetadataParser#parse_remote`, the IdP metadata will be added to the settings.
|
|
393
362
|
|
|
394
363
|
```ruby
|
|
395
364
|
def saml_settings
|
|
396
365
|
|
|
397
366
|
idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new
|
|
398
|
-
# Returns OneLogin::RubySaml::Settings
|
|
367
|
+
# Returns OneLogin::RubySaml::Settings pre-populated with IdP metadata
|
|
399
368
|
settings = idp_metadata_parser.parse_remote("https://example.com/auth/saml2/idp/metadata")
|
|
400
369
|
|
|
401
370
|
settings.assertion_consumer_service_url = "http://#{request.host}/saml/consume"
|
|
@@ -407,6 +376,7 @@ def saml_settings
|
|
|
407
376
|
settings
|
|
408
377
|
end
|
|
409
378
|
```
|
|
379
|
+
|
|
410
380
|
The following attributes are set:
|
|
411
381
|
* idp_entity_id
|
|
412
382
|
* name_identifier_format
|
|
@@ -425,11 +395,32 @@ IdpMetadataParser by its Entity Id value:
|
|
|
425
395
|
|
|
426
396
|
```ruby
|
|
427
397
|
validate_cert = true
|
|
428
|
-
settings =
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
398
|
+
settings = idp_metadata_parser.parse_remote(
|
|
399
|
+
"https://example.com/auth/saml2/idp/metadata",
|
|
400
|
+
validate_cert,
|
|
401
|
+
entity_id: "http//example.com/target/entity"
|
|
402
|
+
)
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
### Retrieve one Entity Descriptor with a specific binding and nameid format when several are available
|
|
406
|
+
|
|
407
|
+
If the metadata contains multiple bindings and NameID formats, the relevant ones
|
|
408
|
+
can be specified when retrieving the settings from the IdpMetadataParser
|
|
409
|
+
by the values of binding and NameID:
|
|
410
|
+
|
|
411
|
+
```ruby
|
|
412
|
+
validate_cert = true
|
|
413
|
+
options = {
|
|
414
|
+
entity_id: "http//example.com/target/entity",
|
|
415
|
+
name_id_format: "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
|
|
416
|
+
sso_binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
|
|
417
|
+
slo_binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
|
|
418
|
+
}
|
|
419
|
+
settings = idp_metadata_parser.parse_remote(
|
|
420
|
+
"https://example.com/auth/saml2/idp/metadata",
|
|
421
|
+
validate_cert,
|
|
422
|
+
options
|
|
423
|
+
)
|
|
433
424
|
```
|
|
434
425
|
|
|
435
426
|
### Parsing Metadata into an Hash
|
|
@@ -438,13 +429,58 @@ The `OneLogin::RubySaml::IdpMetadataParser` also provides the methods `#parse_to
|
|
|
438
429
|
Those return an Hash instead of a `Settings` object, which may be useful for configuring
|
|
439
430
|
[omniauth-saml](https://github.com/omniauth/omniauth-saml), for instance.
|
|
440
431
|
|
|
432
|
+
|
|
433
|
+
### Validating Signature of Metadata and retrieve settings
|
|
434
|
+
|
|
435
|
+
Right now there is no method at ruby_saml to validate the signature of the metadata that gonna be parsed,
|
|
436
|
+
but it can be done as follows:
|
|
437
|
+
* Download the XML.
|
|
438
|
+
* Validate the Signature, providing the cert.
|
|
439
|
+
* Provide the XML to the parse method if the signature was validated
|
|
440
|
+
|
|
441
|
+
```ruby
|
|
442
|
+
require "xml_security"
|
|
443
|
+
require "onelogin/ruby-saml/utils"
|
|
444
|
+
require "onelogin/ruby-saml/idp_metadata_parser"
|
|
445
|
+
|
|
446
|
+
url = "<url_to_the_metadata>"
|
|
447
|
+
idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new
|
|
448
|
+
|
|
449
|
+
uri = URI.parse(url)
|
|
450
|
+
raise ArgumentError.new("url must begin with http or https") unless /^https?/ =~ uri.scheme
|
|
451
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
452
|
+
if uri.scheme == "https"
|
|
453
|
+
http.use_ssl = true
|
|
454
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
get = Net::HTTP::Get.new(uri.request_uri)
|
|
458
|
+
get.basic_auth uri.user, uri.password if uri.user
|
|
459
|
+
response = http.request(get)
|
|
460
|
+
xml = response.body
|
|
461
|
+
errors = []
|
|
462
|
+
doc = XMLSecurity::SignedDocument.new(xml, errors)
|
|
463
|
+
cert_str = "<include_cert_here>"
|
|
464
|
+
cert = OneLogin::RubySaml::Utils.format_cert("cert_str")
|
|
465
|
+
metadata_sign_cert = OpenSSL::X509::Certificate.new(cert)
|
|
466
|
+
valid = doc.validate_document_with_cert(metadata_sign_cert, true)
|
|
467
|
+
if valid
|
|
468
|
+
settings = idp_metadata_parser.parse(
|
|
469
|
+
xml,
|
|
470
|
+
entity_id: "<entity_id_of_the_entity_to_be_retrieved>"
|
|
471
|
+
)
|
|
472
|
+
else
|
|
473
|
+
print "Metadata Signature failed to be verified with the cert provided"
|
|
474
|
+
end
|
|
475
|
+
```
|
|
476
|
+
|
|
441
477
|
## Retrieving Attributes
|
|
442
478
|
|
|
443
|
-
If you are using `saml:AttributeStatement` to transfer data
|
|
479
|
+
If you are using `saml:AttributeStatement` to transfer data, such as 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
|
|
444
480
|
`single_value_compatibility` (when activated, only the first value is returned)
|
|
445
481
|
|
|
446
482
|
```ruby
|
|
447
|
-
response
|
|
483
|
+
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse])
|
|
448
484
|
response.settings = saml_settings
|
|
449
485
|
|
|
450
486
|
response.attributes[:username]
|
|
@@ -529,7 +565,7 @@ pp(response.attributes.multi(:not_exists))
|
|
|
529
565
|
pp(response.attributes.fetch(/givenname/))
|
|
530
566
|
# => "usersName"
|
|
531
567
|
|
|
532
|
-
#
|
|
568
|
+
# Deprecated single_value_compatibility
|
|
533
569
|
OneLogin::RubySaml::Attributes.single_value_compatibility = false
|
|
534
570
|
|
|
535
571
|
pp(response.attributes[:uid])
|
|
@@ -572,74 +608,212 @@ To add a `saml:AuthnContextDeclRef`, define `settings.authn_context_decl_ref`.
|
|
|
572
608
|
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
|
|
573
609
|
building the authrequest object.
|
|
574
610
|
|
|
611
|
+
## Service Provider Metadata
|
|
575
612
|
|
|
576
|
-
|
|
613
|
+
To form a trusted pair relationship with the IdP, the SP (you) need to provide metadata XML
|
|
614
|
+
to the IdP for various good reasons. (Caching, certificate lookups, relaying party permissions, etc)
|
|
577
615
|
|
|
578
|
-
The
|
|
616
|
+
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
617
|
|
|
580
|
-
|
|
618
|
+
The metadata will be polled by the IdP every few minutes, so updating your settings should propagate
|
|
619
|
+
to the IdP settings.
|
|
581
620
|
|
|
582
621
|
```ruby
|
|
583
|
-
|
|
584
|
-
|
|
622
|
+
class SamlController < ApplicationController
|
|
623
|
+
# ... the rest of your controller definitions ...
|
|
624
|
+
def metadata
|
|
625
|
+
settings = Account.get_saml_settings
|
|
626
|
+
meta = OneLogin::RubySaml::Metadata.new
|
|
627
|
+
render :xml => meta.generate(settings), :content_type => "application/samlmetadata+xml"
|
|
628
|
+
end
|
|
629
|
+
end
|
|
585
630
|
```
|
|
586
631
|
|
|
587
|
-
|
|
632
|
+
You can add `ValidUntil` and `CacheDuration` to the SP Metadata XML using instead:
|
|
588
633
|
|
|
589
634
|
```ruby
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
settings
|
|
635
|
+
# Valid until => 2 days from now
|
|
636
|
+
# Cache duration = 604800s = 1 week
|
|
637
|
+
valid_until = Time.now + 172800
|
|
638
|
+
cache_duration = 604800
|
|
639
|
+
meta.generate(settings, false, valid_until, cache_duration)
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
## Signing and Decryption
|
|
643
|
+
|
|
644
|
+
Ruby SAML supports the following functionality:
|
|
645
|
+
|
|
646
|
+
1. Signing your SP Metadata XML
|
|
647
|
+
2. Signing your SP SAML messages
|
|
648
|
+
3. Decrypting IdP Assertion messages upon receipt (EncryptedAssertion)
|
|
649
|
+
4. Verifying signatures on SAML messages and IdP Assertions
|
|
650
|
+
|
|
651
|
+
In order to use functions 1-3 above, you must first define your SP public certificate and private key:
|
|
652
|
+
|
|
653
|
+
```ruby
|
|
654
|
+
settings.certificate = "CERTIFICATE TEXT WITH BEGIN/END HEADER AND FOOTER"
|
|
655
|
+
settings.private_key = "PRIVATE KEY TEXT WITH BEGIN/END HEADER AND FOOTER"
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
Note that the same certificate (and its associated private key) are used to perform
|
|
659
|
+
all decryption and signing-related functions (1-4) above. Ruby SAML does not currently allow
|
|
660
|
+
to specify different certificates for each function.
|
|
595
661
|
|
|
662
|
+
You may also globally set the SP signature and digest method, to be used in SP signing (functions 1 and 2 above):
|
|
663
|
+
|
|
664
|
+
```ruby
|
|
596
665
|
settings.security[:digest_method] = XMLSecurity::Document::SHA1
|
|
597
666
|
settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1
|
|
667
|
+
```
|
|
668
|
+
|
|
669
|
+
#### Signing SP Metadata
|
|
670
|
+
|
|
671
|
+
You may add a `<ds:Signature>` digital signature element to your SP Metadata XML using the following setting:
|
|
672
|
+
|
|
673
|
+
```ruby
|
|
674
|
+
settings.certificate = "CERTIFICATE TEXT WITH BEGIN/END HEADER AND FOOTER"
|
|
675
|
+
settings.private_key = "PRIVATE KEY TEXT WITH BEGIN/END HEADER AND FOOTER"
|
|
598
676
|
|
|
599
|
-
|
|
600
|
-
# Note that metadata signature is always embedded regardless of this value.
|
|
601
|
-
settings.security[:embed_sign] = false
|
|
602
|
-
settings.security[:check_idp_cert_expiration] = false # Enable or not IdP x509 cert expiration check
|
|
603
|
-
settings.security[:check_sp_cert_expiration] = false # Enable or not SP x509 cert expiration check
|
|
677
|
+
settings.security[:metadata_signed] = true # Enable signature on Metadata
|
|
604
678
|
```
|
|
605
679
|
|
|
606
|
-
|
|
680
|
+
#### Signing SP SAML Messages
|
|
681
|
+
|
|
682
|
+
Ruby SAML supports SAML request signing. The Service Provider will sign the
|
|
683
|
+
request/responses with its private key. The Identity Provider will then validate the signature
|
|
684
|
+
of the received request/responses with the public X.509 cert of the Service Provider.
|
|
685
|
+
|
|
686
|
+
To enable, please first set your certificate and private key. This will add `<md:KeyDescriptor use="signing">`
|
|
687
|
+
to your SP Metadata XML, to be read by the IdP.
|
|
688
|
+
|
|
689
|
+
```ruby
|
|
690
|
+
settings.certificate = "CERTIFICATE TEXT WITH BEGIN/END HEADER AND FOOTER"
|
|
691
|
+
settings.private_key = "PRIVATE KEY TEXT WITH BEGIN/END HEADER AND FOOTER"
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
Next, you may specify the specific SP SAML messages you would like to sign:
|
|
695
|
+
|
|
696
|
+
```ruby
|
|
697
|
+
settings.security[:authn_requests_signed] = true # Enable signature on AuthNRequest
|
|
698
|
+
settings.security[:logout_requests_signed] = true # Enable signature on Logout Request
|
|
699
|
+
settings.security[:logout_responses_signed] = true # Enable signature on Logout Response
|
|
700
|
+
```
|
|
701
|
+
|
|
702
|
+
Signatures will be handled automatically for both `HTTP-Redirect` and `HTTP-POST` Binding.
|
|
703
|
+
Note that the RelayState parameter is used when creating the Signature on the `HTTP-Redirect` Binding.
|
|
607
704
|
Remember to provide it to the Signature builder if you are sending a `GET RelayState` parameter or the
|
|
608
705
|
signature validation process will fail at the Identity Provider.
|
|
609
706
|
|
|
610
|
-
|
|
611
|
-
The Identity Provider will validate the sign of the received request/responses with the public x509 cert of the
|
|
612
|
-
Service Provider.
|
|
707
|
+
#### Decrypting IdP SAML Assertions
|
|
613
708
|
|
|
614
|
-
|
|
709
|
+
Ruby SAML supports EncryptedAssertion. The Identity Provider will encrypt the Assertion with the
|
|
710
|
+
public cert of the Service Provider. The Service Provider will decrypt the EncryptedAssertion with its private key.
|
|
615
711
|
|
|
616
|
-
|
|
712
|
+
You may enable EncryptedAssertion as follows. This will add `<md:KeyDescriptor use="encryption">` to your
|
|
713
|
+
SP Metadata XML, to be read by the IdP.
|
|
617
714
|
|
|
618
|
-
|
|
715
|
+
```ruby
|
|
716
|
+
settings.certificate = "CERTIFICATE TEXT WITH BEGIN/END HEADER AND FOOTER"
|
|
717
|
+
settings.private_key = "PRIVATE KEY TEXT WITH BEGIN/END HEADER AND FOOTER"
|
|
619
718
|
|
|
620
|
-
|
|
719
|
+
settings.security[:want_assertions_encrypted] = true # Invalidate SAML messages without an EncryptedAssertion
|
|
720
|
+
```
|
|
721
|
+
|
|
722
|
+
#### Verifying Signature on IdP Assertions
|
|
621
723
|
|
|
622
|
-
|
|
724
|
+
You may require the IdP to sign its SAML Assertions using the following setting.
|
|
725
|
+
With will add `<md:SPSSODescriptor WantAssertionsSigned="true">` to your SP Metadata XML.
|
|
726
|
+
The signature will be checked against the `<md:KeyDescriptor use="signing">` element
|
|
727
|
+
present in the IdP's metadata.
|
|
623
728
|
|
|
624
729
|
```ruby
|
|
625
|
-
settings.
|
|
626
|
-
settings.private_key = "PRIVATE KEY TEXT WITH HEAD AND FOOT"
|
|
730
|
+
settings.security[:want_assertions_signed] = true # Require the IdP to sign its SAML Assertions
|
|
627
731
|
```
|
|
628
732
|
|
|
629
|
-
|
|
630
|
-
|
|
733
|
+
#### Certificate and Signature Validation
|
|
734
|
+
|
|
735
|
+
You may require SP and IdP certificates to be non-expired using the following settings:
|
|
736
|
+
|
|
737
|
+
```ruby
|
|
738
|
+
settings.security[:check_idp_cert_expiration] = true # Raise error if IdP X.509 cert is expired
|
|
739
|
+
settings.security[:check_sp_cert_expiration] = true # Raise error SP X.509 cert is expired
|
|
740
|
+
```
|
|
631
741
|
|
|
632
|
-
|
|
742
|
+
By default, Ruby SAML will raise a `OneLogin::RubySaml::ValidationError` if a signature or certificate
|
|
743
|
+
validation fails. You may disable such exceptions using the `settings.security[:soft]` parameter.
|
|
633
744
|
|
|
745
|
+
```ruby
|
|
746
|
+
settings.security[:soft] = true # Do not raise error on failed signature/certificate validations
|
|
747
|
+
```
|
|
634
748
|
|
|
635
|
-
|
|
749
|
+
#### Advanced SP Certificate Usage & Key Rollover
|
|
636
750
|
|
|
637
|
-
|
|
751
|
+
Ruby SAML provides the `settings.sp_cert_multi` parameter to enable the following
|
|
752
|
+
advanced usage scenarios:
|
|
753
|
+
- Rotating SP certificates and private keys without disruption of service.
|
|
754
|
+
- Specifying separate SP certificates for signing and encryption.
|
|
638
755
|
|
|
756
|
+
The `sp_cert_multi` parameter replaces `certificate` and `private_key`
|
|
757
|
+
(you may not specify both pparameters at the same time.) `sp_cert_multi` has the following shape:
|
|
758
|
+
|
|
759
|
+
```ruby
|
|
760
|
+
settings.sp_cert_multi = {
|
|
761
|
+
signing: [
|
|
762
|
+
{ certificate: cert1, private_key: private_key1 },
|
|
763
|
+
{ certificate: cert2, private_key: private_key2 }
|
|
764
|
+
],
|
|
765
|
+
encryption: [
|
|
766
|
+
{ certificate: cert1, private_key: private_key1 },
|
|
767
|
+
{ certificate: cert3, private_key: private_key1 }
|
|
768
|
+
],
|
|
769
|
+
}
|
|
770
|
+
```
|
|
771
|
+
|
|
772
|
+
Certificate rotation is acheived by inserting new certificates at the bottom of each list,
|
|
773
|
+
and then removing the old certificates from the top of the list once your IdPs have migrated.
|
|
774
|
+
A common practice is for apps to publish the current SP metadata at a URL endpoint and have
|
|
775
|
+
the IdP regularly poll for updates.
|
|
776
|
+
|
|
777
|
+
Note the following:
|
|
778
|
+
- You may re-use the same certificate and/or private key in multiple places, including for both signing and encryption.
|
|
779
|
+
- The IdP should attempt to verify signatures with *all* `:signing` certificates,
|
|
780
|
+
and permit if *any one* succeeds. When signing, Ruby SAML will use the first SP certificate
|
|
781
|
+
in the `sp_cert_multi[:signing]` array. This will be the first active/non-expired certificate
|
|
782
|
+
in the array if `settings.security[:check_sp_cert_expiration]` is true.
|
|
783
|
+
- The IdP may encrypt with any of the SP certificates in the `sp_cert_multi[:encryption]`
|
|
784
|
+
array. When decrypting, Ruby SAML attempt to decrypt with each SP private key in
|
|
785
|
+
`sp_cert_multi[:encryption]` until the decryption is successful. This will skip private
|
|
786
|
+
keys for inactive/expired certificates if `:check_sp_cert_expiration` is true.
|
|
787
|
+
- If `:check_sp_cert_expiration` is true, the generated SP metadata XML will not include
|
|
788
|
+
inactive/expired certificates. This avoids validation errors when the IdP reads the SP
|
|
789
|
+
metadata.
|
|
790
|
+
|
|
791
|
+
#### Audience Validation
|
|
792
|
+
|
|
793
|
+
A service provider should only consider a SAML response valid if the IdP includes an <AudienceRestriction>
|
|
794
|
+
element containing an <Audience> element that uniquely identifies the service provider. Unless you specify
|
|
795
|
+
the `skip_audience` option, Ruby SAML will validate that each SAML response includes an <Audience> element
|
|
796
|
+
whose contents matches `settings.sp_entity_id`.
|
|
797
|
+
|
|
798
|
+
By default, Ruby SAML considers an <AudienceRestriction> element containing only empty <Audience> elements
|
|
799
|
+
to be valid. That means an otherwise valid SAML response with a condition like this would be valid:
|
|
800
|
+
|
|
801
|
+
```xml
|
|
802
|
+
<AudienceRestriction>
|
|
803
|
+
<Audience />
|
|
804
|
+
</AudienceRestriction>
|
|
805
|
+
```
|
|
806
|
+
|
|
807
|
+
You may enforce that an <AudienceRestriction> element containing only empty <Audience> elements
|
|
808
|
+
is invalid using the `settings.security[:strict_audience_validation]` parameter.
|
|
809
|
+
|
|
810
|
+
```ruby
|
|
811
|
+
settings.security[:strict_audience_validation] = true
|
|
812
|
+
```
|
|
639
813
|
|
|
640
814
|
## Single Log Out
|
|
641
815
|
|
|
642
|
-
|
|
816
|
+
Ruby SAML supports SP-initiated Single Logout and IdP-Initiated Single Logout.
|
|
643
817
|
|
|
644
818
|
Here is an example that we could add to our previous controller to generate and send a SAML Logout Request to the IdP:
|
|
645
819
|
|
|
@@ -654,7 +828,7 @@ def sp_logout_request
|
|
|
654
828
|
delete_session
|
|
655
829
|
else
|
|
656
830
|
|
|
657
|
-
logout_request = OneLogin::RubySaml::Logoutrequest.new
|
|
831
|
+
logout_request = OneLogin::RubySaml::Logoutrequest.new
|
|
658
832
|
logger.info "New SP SLO for userid '#{session[:userid]}' transactionid '#{logout_request.uuid}'"
|
|
659
833
|
|
|
660
834
|
if settings.name_identifier_value.nil?
|
|
@@ -670,7 +844,7 @@ def sp_logout_request
|
|
|
670
844
|
session[:transaction_id] = logout_request.uuid
|
|
671
845
|
session[:logged_out_user] = logged_user
|
|
672
846
|
|
|
673
|
-
relayState =
|
|
847
|
+
relayState = url_for(controller: 'saml', action: 'index')
|
|
674
848
|
redirect_to(logout_request.create(settings, :RelayState => relayState))
|
|
675
849
|
end
|
|
676
850
|
end
|
|
@@ -717,7 +891,13 @@ Here is an example that we could add to our previous controller to process a SAM
|
|
|
717
891
|
# Method to handle IdP initiated logouts
|
|
718
892
|
def idp_logout_request
|
|
719
893
|
settings = Account.get_saml_settings
|
|
720
|
-
|
|
894
|
+
# ADFS URL-Encodes SAML data as lowercase, and the toolkit by default uses
|
|
895
|
+
# uppercase. Turn it True for ADFS compatibility on signature verification
|
|
896
|
+
settings.security[:lowercase_url_encoding] = true
|
|
897
|
+
|
|
898
|
+
logout_request = OneLogin::RubySaml::SloLogoutrequest.new(
|
|
899
|
+
params[:SAMLRequest], settings: settings
|
|
900
|
+
)
|
|
721
901
|
if !logout_request.is_valid?
|
|
722
902
|
logger.error "IdP initiated LogoutRequest was not valid!"
|
|
723
903
|
return render :inline => logger.error
|
|
@@ -752,38 +932,6 @@ def logout
|
|
|
752
932
|
end
|
|
753
933
|
```
|
|
754
934
|
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
## Service Provider Metadata
|
|
758
|
-
|
|
759
|
-
To form a trusted pair relationship with the IdP, the SP (you) need to provide metadata XML
|
|
760
|
-
to the IdP for various good reasons. (Caching, certificate lookups, relaying party permissions, etc)
|
|
761
|
-
|
|
762
|
-
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.
|
|
763
|
-
|
|
764
|
-
The metadata will be polled by the IdP every few minutes, so updating your settings should propagate
|
|
765
|
-
to the IdP settings.
|
|
766
|
-
|
|
767
|
-
```ruby
|
|
768
|
-
class SamlController < ApplicationController
|
|
769
|
-
# ... the rest of your controller definitions ...
|
|
770
|
-
def metadata
|
|
771
|
-
settings = Account.get_saml_settings
|
|
772
|
-
meta = OneLogin::RubySaml::Metadata.new
|
|
773
|
-
render :xml => meta.generate(settings), :content_type => "application/samlmetadata+xml"
|
|
774
|
-
end
|
|
775
|
-
end
|
|
776
|
-
```
|
|
777
|
-
|
|
778
|
-
You can add ValidUntil and CacheDuration to the XML Metadata using instead
|
|
779
|
-
```ruby
|
|
780
|
-
# Valid until => 2 days from now
|
|
781
|
-
# Cache duration = 604800s = 1 week
|
|
782
|
-
valid_until = Time.now + 172800
|
|
783
|
-
cache_duration = 604800
|
|
784
|
-
meta.generate(settings, false, valid_until, cache_duration)
|
|
785
|
-
```
|
|
786
|
-
|
|
787
935
|
## Clock Drift
|
|
788
936
|
|
|
789
937
|
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.
|
|
@@ -798,13 +946,33 @@ response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], :allowed_cloc
|
|
|
798
946
|
|
|
799
947
|
Make sure to keep the value as comfortably small as possible to keep security risks to a minimum.
|
|
800
948
|
|
|
949
|
+
## Deflation Limit
|
|
950
|
+
|
|
951
|
+
To protect against decompression bombs (a form of DoS attack), SAML messages are limited to 250,000 bytes by default.
|
|
952
|
+
Sometimes legitimate SAML messages will exceed this limit,
|
|
953
|
+
for example due to custom claims like including groups a user is a member of.
|
|
954
|
+
If you want to customize this limit, you need to provide a different setting when initializing the response object.
|
|
955
|
+
Example:
|
|
956
|
+
|
|
957
|
+
```ruby
|
|
958
|
+
def consume
|
|
959
|
+
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], { settings: saml_settings })
|
|
960
|
+
...
|
|
961
|
+
end
|
|
962
|
+
|
|
963
|
+
private
|
|
964
|
+
|
|
965
|
+
def saml_settings
|
|
966
|
+
OneLogin::RubySaml::Settings.new(message_max_bytesize: 500_000)
|
|
967
|
+
end
|
|
968
|
+
```
|
|
969
|
+
|
|
801
970
|
## Attribute Service
|
|
802
971
|
|
|
803
|
-
To request attributes from the IdP the SP
|
|
972
|
+
To request attributes from the IdP the SP must provide an attribute service within its metadata and reference the index in the assertion.
|
|
804
973
|
|
|
805
974
|
```ruby
|
|
806
975
|
settings = OneLogin::RubySaml::Settings.new
|
|
807
|
-
|
|
808
976
|
settings.attributes_index = 5
|
|
809
977
|
settings.attribute_consuming_service.configure do
|
|
810
978
|
service_name "Service"
|
|
@@ -815,3 +983,27 @@ end
|
|
|
815
983
|
```
|
|
816
984
|
|
|
817
985
|
The `attribute_value` option additionally accepts an array of possible values.
|
|
986
|
+
|
|
987
|
+
## Custom Metadata Fields
|
|
988
|
+
|
|
989
|
+
Some IdPs may require SPs to add additional fields (Organization, ContactPerson, etc.)
|
|
990
|
+
into the SP metadata. This can be achieved by extending the `OneLogin::RubySaml::Metadata`
|
|
991
|
+
class and overriding the `#add_extras` method as per the following example:
|
|
992
|
+
|
|
993
|
+
```ruby
|
|
994
|
+
class MyMetadata < OneLogin::RubySaml::Metadata
|
|
995
|
+
def add_extras(root, _settings)
|
|
996
|
+
org = root.add_element("md:Organization")
|
|
997
|
+
org.add_element("md:OrganizationName", 'xml:lang' => "en-US").text = 'ACME Inc.'
|
|
998
|
+
org.add_element("md:OrganizationDisplayName", 'xml:lang' => "en-US").text = 'ACME'
|
|
999
|
+
org.add_element("md:OrganizationURL", 'xml:lang' => "en-US").text = 'https://www.acme.com'
|
|
1000
|
+
|
|
1001
|
+
cp = root.add_element("md:ContactPerson", 'contactType' => 'technical')
|
|
1002
|
+
cp.add_element("md:GivenName").text = 'ACME SAML Team'
|
|
1003
|
+
cp.add_element("md:EmailAddress").text = 'saml@acme.com'
|
|
1004
|
+
end
|
|
1005
|
+
end
|
|
1006
|
+
|
|
1007
|
+
# Output XML with custom metadata
|
|
1008
|
+
MyMetadata.new.generate(settings)
|
|
1009
|
+
```
|