ruby-saml 1.9.0 → 1.15.0

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