ruby-saml 1.9.0 → 1.14.0

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

Potentially problematic release.


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

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