r-saml 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (140) hide show
  1. checksums.yaml +7 -0
  2. data/.document +5 -0
  3. data/.gitignore +14 -0
  4. data/.travis.yml +23 -0
  5. data/Gemfile +6 -0
  6. data/LICENSE +19 -0
  7. data/README.md +584 -0
  8. data/Rakefile +27 -0
  9. data/changelog.md +75 -0
  10. data/gemfiles/nokogiri-1.5.gemfile +5 -0
  11. data/lib/onelogin/ruby-saml.rb +17 -0
  12. data/lib/onelogin/ruby-saml/attribute_service.rb +57 -0
  13. data/lib/onelogin/ruby-saml/attributes.rb +128 -0
  14. data/lib/onelogin/ruby-saml/authrequest.rb +165 -0
  15. data/lib/onelogin/ruby-saml/http_error.rb +7 -0
  16. data/lib/onelogin/ruby-saml/idp_metadata_parser.rb +161 -0
  17. data/lib/onelogin/ruby-saml/logging.rb +30 -0
  18. data/lib/onelogin/ruby-saml/logoutrequest.rb +131 -0
  19. data/lib/onelogin/ruby-saml/logoutresponse.rb +241 -0
  20. data/lib/onelogin/ruby-saml/metadata.rb +123 -0
  21. data/lib/onelogin/ruby-saml/response.rb +735 -0
  22. data/lib/onelogin/ruby-saml/saml_message.rb +158 -0
  23. data/lib/onelogin/ruby-saml/settings.rb +165 -0
  24. data/lib/onelogin/ruby-saml/slo_logoutrequest.rb +258 -0
  25. data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +136 -0
  26. data/lib/onelogin/ruby-saml/utils.rb +172 -0
  27. data/lib/onelogin/ruby-saml/validation_error.rb +7 -0
  28. data/lib/onelogin/ruby-saml/version.rb +5 -0
  29. data/lib/ruby-saml.rb +1 -0
  30. data/lib/schemas/saml-schema-assertion-2.0.xsd +283 -0
  31. data/lib/schemas/saml-schema-authn-context-2.0.xsd +23 -0
  32. data/lib/schemas/saml-schema-authn-context-types-2.0.xsd +821 -0
  33. data/lib/schemas/saml-schema-metadata-2.0.xsd +337 -0
  34. data/lib/schemas/saml-schema-protocol-2.0.xsd +302 -0
  35. data/lib/schemas/sstc-metadata-attr.xsd +35 -0
  36. data/lib/schemas/sstc-saml-attribute-ext.xsd +25 -0
  37. data/lib/schemas/sstc-saml-metadata-algsupport-v1.0.xsd +41 -0
  38. data/lib/schemas/sstc-saml-metadata-ui-v1.0.xsd +89 -0
  39. data/lib/schemas/xenc-schema.xsd +136 -0
  40. data/lib/schemas/xml.xsd +287 -0
  41. data/lib/schemas/xmldsig-core-schema.xsd +309 -0
  42. data/lib/xml_security.rb +368 -0
  43. data/r-saml.gemspec +64 -0
  44. data/test/certificates/certificate1 +12 -0
  45. data/test/certificates/certificate_without_head_foot +1 -0
  46. data/test/certificates/formatted_certificate +14 -0
  47. data/test/certificates/formatted_private_key +12 -0
  48. data/test/certificates/formatted_rsa_private_key +12 -0
  49. data/test/certificates/invalid_certificate1 +1 -0
  50. data/test/certificates/invalid_certificate2 +1 -0
  51. data/test/certificates/invalid_certificate3 +12 -0
  52. data/test/certificates/invalid_private_key1 +1 -0
  53. data/test/certificates/invalid_private_key2 +1 -0
  54. data/test/certificates/invalid_private_key3 +10 -0
  55. data/test/certificates/invalid_rsa_private_key1 +1 -0
  56. data/test/certificates/invalid_rsa_private_key2 +1 -0
  57. data/test/certificates/invalid_rsa_private_key3 +10 -0
  58. data/test/certificates/ruby-saml.crt +14 -0
  59. data/test/certificates/ruby-saml.key +15 -0
  60. data/test/idp_metadata_parser_test.rb +95 -0
  61. data/test/logging_test.rb +62 -0
  62. data/test/logout_requests/invalid_slo_request.xml +6 -0
  63. data/test/logout_requests/slo_request.xml +4 -0
  64. data/test/logout_requests/slo_request.xml.base64 +1 -0
  65. data/test/logout_requests/slo_request_deflated.xml.base64 +1 -0
  66. data/test/logout_requests/slo_request_with_session_index.xml +5 -0
  67. data/test/logout_responses/logoutresponse_fixtures.rb +67 -0
  68. data/test/logoutrequest_test.rb +211 -0
  69. data/test/logoutresponse_test.rb +258 -0
  70. data/test/metadata_test.rb +203 -0
  71. data/test/request_test.rb +282 -0
  72. data/test/response_test.rb +1159 -0
  73. data/test/responses/adfs_response_sha1.xml +46 -0
  74. data/test/responses/adfs_response_sha256.xml +46 -0
  75. data/test/responses/adfs_response_sha384.xml +46 -0
  76. data/test/responses/adfs_response_sha512.xml +46 -0
  77. data/test/responses/adfs_response_xmlns.xml +45 -0
  78. data/test/responses/attackxee.xml +13 -0
  79. data/test/responses/idp_descriptor.xml +3 -0
  80. data/test/responses/invalids/invalid_audience.xml.base64 +1 -0
  81. data/test/responses/invalids/invalid_issuer_assertion.xml.base64 +1 -0
  82. data/test/responses/invalids/invalid_issuer_message.xml.base64 +1 -0
  83. data/test/responses/invalids/invalid_signature_position.xml.base64 +1 -0
  84. data/test/responses/invalids/invalid_subjectconfirmation_inresponse.xml.base64 +1 -0
  85. data/test/responses/invalids/invalid_subjectconfirmation_nb.xml.base64 +1 -0
  86. data/test/responses/invalids/invalid_subjectconfirmation_noa.xml.base64 +1 -0
  87. data/test/responses/invalids/invalid_subjectconfirmation_recipient.xml.base64 +1 -0
  88. data/test/responses/invalids/multiple_assertions.xml.base64 +2 -0
  89. data/test/responses/invalids/multiple_signed.xml.base64 +1 -0
  90. data/test/responses/invalids/no_id.xml.base64 +1 -0
  91. data/test/responses/invalids/no_saml2.xml.base64 +1 -0
  92. data/test/responses/invalids/no_signature.xml.base64 +1 -0
  93. data/test/responses/invalids/no_status.xml.base64 +1 -0
  94. data/test/responses/invalids/no_status_code.xml.base64 +1 -0
  95. data/test/responses/invalids/no_subjectconfirmation_data.xml.base64 +1 -0
  96. data/test/responses/invalids/no_subjectconfirmation_method.xml.base64 +1 -0
  97. data/test/responses/invalids/response_encrypted_attrs.xml.base64 +1 -0
  98. data/test/responses/invalids/response_invalid_signed_element.xml.base64 +1 -0
  99. data/test/responses/invalids/status_code_responder.xml.base64 +1 -0
  100. data/test/responses/invalids/status_code_responer_and_msg.xml.base64 +1 -0
  101. data/test/responses/no_signature_ns.xml +48 -0
  102. data/test/responses/open_saml_response.xml +56 -0
  103. data/test/responses/response_assertion_wrapped.xml.base64 +93 -0
  104. data/test/responses/response_encrypted_nameid.xml.base64 +1 -0
  105. data/test/responses/response_eval.xml +7 -0
  106. data/test/responses/response_no_cert_and_encrypted_attrs.xml +29 -0
  107. data/test/responses/response_unsigned_xml_base64 +1 -0
  108. data/test/responses/response_with_ampersands.xml +139 -0
  109. data/test/responses/response_with_ampersands.xml.base64 +93 -0
  110. data/test/responses/response_with_multiple_attribute_values.xml +67 -0
  111. data/test/responses/response_with_saml2_namespace.xml.base64 +102 -0
  112. data/test/responses/response_with_signed_assertion.xml.base64 +66 -0
  113. data/test/responses/response_with_signed_assertion_2.xml.base64 +1 -0
  114. data/test/responses/response_with_undefined_recipient.xml.base64 +1 -0
  115. data/test/responses/response_without_attributes.xml.base64 +79 -0
  116. data/test/responses/response_without_reference_uri.xml.base64 +1 -0
  117. data/test/responses/response_wrapped.xml.base64 +150 -0
  118. data/test/responses/signed_message_encrypted_signed_assertion.xml.base64 +1 -0
  119. data/test/responses/signed_message_encrypted_unsigned_assertion.xml.base64 +1 -0
  120. data/test/responses/signed_nameid_in_atts.xml +47 -0
  121. data/test/responses/signed_unqual_nameid_in_atts.xml +47 -0
  122. data/test/responses/simple_saml_php.xml +71 -0
  123. data/test/responses/starfield_response.xml.base64 +1 -0
  124. data/test/responses/test_sign.xml +43 -0
  125. data/test/responses/unsigned_message_aes128_encrypted_signed_assertion.xml.base64 +1 -0
  126. data/test/responses/unsigned_message_aes192_encrypted_signed_assertion.xml.base64 +1 -0
  127. data/test/responses/unsigned_message_aes256_encrypted_signed_assertion.xml.base64 +1 -0
  128. data/test/responses/unsigned_message_des192_encrypted_signed_assertion.xml.base64 +1 -0
  129. data/test/responses/unsigned_message_encrypted_assertion_without_saml_namespace.xml.base64 +1 -0
  130. data/test/responses/unsigned_message_encrypted_signed_assertion.xml.base64 +1 -0
  131. data/test/responses/unsigned_message_encrypted_unsigned_assertion.xml.base64 +1 -0
  132. data/test/responses/valid_response.xml.base64 +1 -0
  133. data/test/saml_message_test.rb +56 -0
  134. data/test/settings_test.rb +218 -0
  135. data/test/slo_logoutrequest_test.rb +275 -0
  136. data/test/slo_logoutresponse_test.rb +185 -0
  137. data/test/test_helper.rb +257 -0
  138. data/test/utils_test.rb +145 -0
  139. data/test/xml_security_test.rb +328 -0
  140. metadata +421 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9991b5296eef5848d2cd0c6ef137f326ab5f641d
4
+ data.tar.gz: 00324929a178778ff436582ab1ae54adea65a38c
5
+ SHA512:
6
+ metadata.gz: 902f050b0a053a86df16c405d72a98a47380aa048a97912dd6ba6db11e2d82500d3e76fca1669afd5742d1aa54fcc028907a786a7e513008ca11e1936aaaeced
7
+ data.tar.gz: 0917ac7ca9a81a2c2300d0dd058fdb587842d3dedb7cfc0a4510c9508413308bcc09fcdb9b56481555b970fc25f2b767d9b4bcaaef17d257689d2b5833584547
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
@@ -0,0 +1,14 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
6
+ Gemfile.lock
7
+ gemfiles/*.lock
8
+ .idea/*
9
+ lib/Lib.iml
10
+ test/Test.iml
11
+ .rvmrc
12
+ *.gem
13
+ .bundle
14
+ *.patch
@@ -0,0 +1,23 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.8.7
4
+ - 1.9.3
5
+ - 2.0.0
6
+ - 2.1.5
7
+ - 2.2.0
8
+ - ree
9
+ - jruby-1.7.21
10
+ - jruby-9.0.0.0
11
+ gemfile:
12
+ - Gemfile
13
+ - gemfiles/nokogiri-1.5.gemfile
14
+ matrix:
15
+ exclude:
16
+ - rvm: 1.8.7
17
+ gemfile: Gemfile
18
+ - rvm: ree
19
+ gemfile: Gemfile
20
+ - rvm: jruby-9.0.0.0
21
+ gemfile: gemfiles/nokogiri-1.5.gemfile
22
+ - rvm: jruby-1.7.21
23
+ gemfile: gemfiles/nokogiri-1.5.gemfile
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ #
2
+ # Please keep this file alphabetized and organized
3
+ #
4
+ source 'https://rubygems.org'
5
+
6
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2010-2015 OneLogin, LLC
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
@@ -0,0 +1,584 @@
1
+ # Ruby SAML [![Build Status](https://secure.travis-ci.org/onelogin/ruby-saml.png)](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)
2
+
3
+
4
+ ## Updating from 0.9.x to 1.0.X
5
+
6
+ Version `1.0` is a recommended update for all Ruby SAML users as it includes security fixes.
7
+
8
+ Version `1.0` adds security improvements like entity expansion limitation, more SAML message validations, and other important improvements like decrypt support.
9
+
10
+ For more details, please review [the changelog](changelog.md).
11
+
12
+ ### Important Changes
13
+ 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.
14
+
15
+ ## Updating from 0.8.x to 0.9.x
16
+ Version `0.9` adds many new features and improvements.
17
+
18
+ ## Updating from 0.7.x to 0.8.x
19
+ Version `0.8.x` changes the namespace of the gem from `OneLogin::Saml` to `OneLogin::RubySaml`. Please update your implementations of the gem accordingly.
20
+
21
+ ## Overview
22
+
23
+ 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.
24
+
25
+ SAML authorization is a two step process and you are expected to implement support for both.
26
+
27
+ 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)
28
+
29
+ ### Supported versions of Ruby
30
+ * 1.8.7
31
+ * 1.9.x
32
+ * 2.1.x
33
+ * 2.2.x
34
+ * JRuby 1.7.19
35
+ * JRuby 9.0.0.0
36
+
37
+ ## Adding Features, Pull Requests
38
+ * Fork the repository
39
+ * Make your feature addition or bug fix
40
+ * Add tests for your new features. This is important so we don't break any features in a future version unintentionally.
41
+ * Ensure all tests pass.
42
+ * Do not change rakefile, version, or history.
43
+ * Open a pull request, following [this template](https://gist.github.com/Lordnibbler/11002759).
44
+
45
+ ## Getting Started
46
+ 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:
47
+
48
+ Using `Gemfile`
49
+
50
+ ```ruby
51
+ # latest stable
52
+ gem 'ruby-saml', '~> 1.0.0'
53
+
54
+ # or track master for bleeding-edge
55
+ gem 'ruby-saml', :github => 'onelogin/ruby-saml'
56
+ ```
57
+
58
+ Using RubyGems
59
+
60
+ ```sh
61
+ gem install ruby-saml
62
+ ```
63
+
64
+ When requiring the gem, you can add the whole toolkit
65
+ ```ruby
66
+ require 'onelogin/ruby-saml'
67
+ ```
68
+
69
+ or just the required components individually:
70
+
71
+ ```ruby
72
+ require 'onelogin/ruby-saml/authrequest'
73
+ ```
74
+
75
+ ### Installation on Ruby 1.8.7
76
+
77
+ This gem has a dependency on Nokogiri, 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.
78
+
79
+ Using `Gemfile`
80
+
81
+ ```ruby
82
+ gem 'nokogiri', '~> 1.5.10'
83
+ ```
84
+
85
+ Using RubyGems
86
+
87
+ ```sh
88
+ gem install nokogiri --version '~> 1.5.10'
89
+ ````
90
+
91
+ ### Configuring Logging
92
+
93
+ When troubleshooting SAML integration issues, you will find it extremely helpful to examine the
94
+ output of this gem's business logic. By default, log messages are emitted to RAILS_DEFAULT_LOGGER
95
+ when the gem is used in a Rails context, and to STDOUT when the gem is used outside of Rails.
96
+
97
+ To override the default behavior and control the destination of log messages, provide
98
+ a ruby Logger object to the gem's logging singleton:
99
+
100
+ ```ruby
101
+ OneLogin::RubySaml::Logging.logger = Logger.new(File.open('/var/log/ruby-saml.log', 'w')
102
+ ```
103
+
104
+ ## The Initialization Phase
105
+
106
+ 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 being 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):
107
+
108
+ ```ruby
109
+ def init
110
+ request = OneLogin::RubySaml::Authrequest.new
111
+ redirect_to(request.create(saml_settings))
112
+ end
113
+ ```
114
+
115
+ 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 is can look something like this (the authorize_success and authorize_failure methods are specific to your application):
116
+
117
+ ```ruby
118
+ def consume
119
+ response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], :settings => saml_settings)
120
+
121
+ # We validate the SAML Response and check if the user already exists in the system
122
+ if response.is_valid?
123
+ # authorize_success, log the user
124
+ session[:userid] = response.nameid
125
+ session[:attributes] = response.attributes
126
+ else
127
+ authorize_failure # This method shows an error message
128
+ end
129
+ end
130
+ ```
131
+
132
+ In the above there are a few assumptions in place, one being that the 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:
133
+
134
+ If the assertion of the SAMLResponse is not encrypted, you can initialize the Response without the :settings parameter and set it later,
135
+
136
+ ```
137
+ response = OneLogin::RubySaml::Response.new(params[:SAMLResponse])
138
+ response.settings = saml_settings
139
+ ```
140
+ but if the SAMLResponse contains an encrypted assertion, you need to provide the settings in the
141
+ initialize method in order to be able to obtain the decrypted assertion, using the service provider private key in order to decrypt.
142
+ If you don't know what expect, use always the first proposed way (always set the settings on the initialize method).
143
+
144
+ ```ruby
145
+ def saml_settings
146
+ settings = OneLogin::RubySaml::Settings.new
147
+
148
+ settings.assertion_consumer_service_url = "http://#{request.host}/saml/consume"
149
+ settings.issuer = "http://#{request.host}/saml/metadata"
150
+ settings.idp_sso_target_url = "https://app.onelogin.com/saml/metadata/#{OneLoginAppId}"
151
+ settings.idp_entity_id = "https://app.onelogin.com/saml/metadata/#{OneLoginAppId}"
152
+ settings.idp_sso_target_url = "https://app.onelogin.com/trust/saml2/http-post/sso/#{OneLoginAppId}"
153
+ settings.idp_slo_target_url = "https://app.onelogin.com/trust/saml2/http-redirect/slo/#{OneLoginAppId}"
154
+ settings.idp_cert_fingerprint = OneLoginAppCertFingerPrint
155
+ settings.idp_cert_fingerprint_algorithm = "http://www.w3.org/2000/09/xmldsig#sha1"
156
+ settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
157
+
158
+ # Optional for most SAML IdPs
159
+ settings.authn_context = "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport"
160
+
161
+ # Optional bindings (defaults to Redirect for logout POST for acs)
162
+ settings.assertion_consumer_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
163
+ settings.assertion_consumer_logout_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
164
+
165
+ settings
166
+ end
167
+ ```
168
+
169
+ Some assertion validations can be skipped by passing parameters to OneLogin::RubySaml::Response.new(). For example, you can skip the Conditions validation or the SubjectConfirmation validations by initializing the response with different options:
170
+
171
+ ```ruby
172
+ response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], {skip_conditions: true}) # skips conditions
173
+ response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], {skip_subject_confirmation: true}) # skips subject confirmation
174
+ ```
175
+
176
+ What's left at this point, is to wrap it all up in a controller and point the initialization and consumption URLs in OneLogin at that. A full controller example could look like this:
177
+
178
+ ```ruby
179
+ # This controller expects you to use the URLs /saml/init and /saml/consume in your OneLogin application.
180
+ class SamlController < ApplicationController
181
+ def init
182
+ request = OneLogin::RubySaml::Authrequest.new
183
+ redirect_to(request.create(saml_settings))
184
+ end
185
+
186
+ def consume
187
+ response = OneLogin::RubySaml::Response.new(params[:SAMLResponse])
188
+ response.settings = saml_settings
189
+
190
+ # We validate the SAML Response and check if the user already exists in the system
191
+ if response.is_valid?
192
+ # authorize_success, log the user
193
+ session[:userid] = response.nameid
194
+ session[:attributes] = response.attributes
195
+ else
196
+ authorize_failure # This method shows an error message
197
+ end
198
+ end
199
+
200
+ private
201
+
202
+ def saml_settings
203
+ settings = OneLogin::RubySaml::Settings.new
204
+
205
+ settings.assertion_consumer_service_url = "http://#{request.host}/saml/consume"
206
+ settings.issuer = "http://#{request.host}/saml/metadata"
207
+ settings.idp_sso_target_url = "https://app.onelogin.com/saml/signon/#{OneLoginAppId}"
208
+ settings.idp_cert_fingerprint = OneLoginAppCertFingerPrint
209
+ settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
210
+
211
+ # Optional for most SAML IdPs
212
+ settings.authn_context = "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport"
213
+
214
+ # Optional. Describe according to IdP specification (if supported) which attributes the SP desires to receive in SAMLResponse.
215
+ settings.attributes_index = 5
216
+ # Optional. Describe an attribute consuming service for support of additional attributes.
217
+ settings.attribute_consuming_service.configure do
218
+ service_name "Service"
219
+ service_index 5
220
+ add_attribute :name => "Name", :name_format => "Name Format", :friendly_name => "Friendly Name"
221
+ end
222
+
223
+ settings
224
+ end
225
+ end
226
+ ```
227
+ ## Metadata Based Configuration
228
+
229
+ 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
230
+ key certificates which add to the trusted relationship. The IdP administrator can also configure custom settings for an SP based on the metadata.
231
+
232
+ Using ```idp_metadata_parser.parse_remote``` IdP metadata will be added to the settings withouth further ado.
233
+
234
+ ```ruby
235
+ def saml_settings
236
+
237
+ idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new
238
+ # Returns OneLogin::RubySaml::Settings prepopulated with idp metadata
239
+ settings = idp_metadata_parser.parse_remote("https://example.com/auth/saml2/idp/metadata")
240
+
241
+ settings.assertion_consumer_service_url = "http://#{request.host}/saml/consume"
242
+ settings.issuer = "http://#{request.host}/saml/metadata"
243
+ settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
244
+ # Optional for most SAML IdPs
245
+ settings.authn_context = "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport"
246
+
247
+ settings
248
+ end
249
+ ```
250
+ The following attributes are set:
251
+ * id_sso_target_url
252
+ * idp_slo_target_url
253
+ * id_cert_fingerpint
254
+
255
+ If you are using saml:AttributeStatement to transfer metadata, like the user name, you can access all the attributes through response.attributes. It contains all the saml:AttributeStatement with its 'Name' as a indifferent key the one/more saml:AttributeValue as value. The value returned depends on the value of the
256
+ `single_value_compatibility` (when activate, only one value returned, the first one)
257
+
258
+ ```ruby
259
+ response = OneLogin::RubySaml::Response.new(params[:SAMLResponse])
260
+ response.settings = saml_settings
261
+
262
+ response.attributes[:username]
263
+ ```
264
+
265
+ Imagine this saml:AttributeStatement
266
+
267
+ ```xml
268
+ <saml:AttributeStatement>
269
+ <saml:Attribute Name="uid">
270
+ <saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">demo</saml:AttributeValue>
271
+ </saml:Attribute>
272
+ <saml:Attribute Name="another_value">
273
+ <saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">value1</saml:AttributeValue>
274
+ <saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">value2</saml:AttributeValue>
275
+ </saml:Attribute>
276
+ <saml:Attribute Name="role">
277
+ <saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">role1</saml:AttributeValue>
278
+ </saml:Attribute>
279
+ <saml:Attribute Name="role">
280
+ <saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">role2</saml:AttributeValue>
281
+ <saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">role3</saml:AttributeValue>
282
+ </saml:Attribute>
283
+ <saml:Attribute Name="attribute_with_nil_value">
284
+ <saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>
285
+ </saml:Attribute>
286
+ <saml:Attribute Name="attribute_with_nils_and_empty_strings">
287
+ <saml:AttributeValue/>
288
+ <saml:AttributeValue>valuePresent</saml:AttributeValue>
289
+ <saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>
290
+ <saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="1"/>
291
+ </saml:Attribute>
292
+ </saml:AttributeStatement>
293
+ ```
294
+
295
+ ```ruby
296
+ pp(response.attributes) # is an OneLogin::RubySaml::Attributes object
297
+ # => @attributes=
298
+ {"uid"=>["demo"],
299
+ "another_value"=>["value1", "value2"],
300
+ "role"=>["role1", "role2", "role3"],
301
+ "attribute_with_nil_value"=>[nil],
302
+ "attribute_with_nils_and_empty_strings"=>["", "valuePresent", nil, nil]}>
303
+
304
+ # Active single_value_compatibility
305
+ OneLogin::RubySaml::Attributes.single_value_compatibility = true
306
+
307
+ pp(response.attributes[:uid])
308
+ # => "demo"
309
+
310
+ pp(response.attributes[:role])
311
+ # => "role1"
312
+
313
+ pp(response.attributes.single(:role))
314
+ # => "role1"
315
+
316
+ pp(response.attributes.multi(:role))
317
+ # => ["role1", "role2", "role3"]
318
+
319
+ pp(response.attributes[:attribute_with_nil_value])
320
+ # => nil
321
+
322
+ pp(response.attributes[:attribute_with_nils_and_empty_strings])
323
+ # => ""
324
+
325
+ pp(response.attributes[:not_exists])
326
+ # => nil
327
+
328
+ pp(response.attributes.single(:not_exists))
329
+ # => nil
330
+
331
+ pp(response.attributes.multi(:not_exists))
332
+ # => nil
333
+
334
+ # Deactive single_value_compatibility
335
+ OneLogin::RubySaml::Attributes.single_value_compatibility = false
336
+
337
+ pp(response.attributes[:uid])
338
+ # => ["demo"]
339
+
340
+ pp(response.attributes[:role])
341
+ # => ["role1", "role2", "role3"]
342
+
343
+ pp(response.attributes.single(:role))
344
+ # => "role1"
345
+
346
+ pp(response.attributes.multi(:role))
347
+ # => ["role1", "role2", "role3"]
348
+
349
+ pp(response.attributes[:attribute_with_nil_value])
350
+ # => [nil]
351
+
352
+ pp(response.attributes[:attribute_with_nils_and_empty_strings])
353
+ # => ["", "valuePresent", nil, nil]
354
+
355
+ pp(response.attributes[:not_exists])
356
+ # => nil
357
+
358
+ pp(response.attributes.single(:not_exists))
359
+ # => nil
360
+
361
+ pp(response.attributes.multi(:not_exists))
362
+ # => nil
363
+ ```
364
+
365
+ 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 the parameter `settings.authn_context_comparison` (the possible values are: 'exact', 'better', 'maximum' and 'minimum'), 'exact' is the default value.
366
+ If we want to add a saml:AuthnContextDeclRef, define a `settings.authn_context_decl_ref`.
367
+
368
+
369
+ ## Signing
370
+
371
+ The Ruby Toolkit supports 2 different kinds of signature: Embeded and as GET parameter
372
+
373
+ In order to be able to sign we need first to define the private key and the public cert of the service provider
374
+
375
+ ```ruby
376
+ settings.certificate = "CERTIFICATE TEXT WITH HEAD AND FOOT"
377
+ settings.private_key = "PRIVATE KEY TEXT WITH HEAD AND FOOT"
378
+ ```
379
+
380
+ The settings related to sign are stored in the `security` attribute of the settings:
381
+
382
+ ```ruby
383
+ settings.security[:authn_requests_signed] = true # Enable or not signature on AuthNRequest
384
+ settings.security[:logout_requests_signed] = true # Enable or not signature on Logout Request
385
+ settings.security[:logout_responses_signed] = true # Enable or not signature on Logout Response
386
+ settings.security[:metadata_signed] = true # Enable or not signature on Metadata
387
+
388
+ settings.security[:digest_method] = XMLSecurity::Document::SHA1
389
+ settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1
390
+
391
+ # Embeded signature or HTTP GET parameter signature
392
+ # Note that metadata signature is always embedded regardless of this value.
393
+ settings.security[:embed_sign] = false
394
+ ```
395
+
396
+ Notice that the RelayState parameter is used when creating the Signature on the HTTP-Redirect Binding,
397
+ remember to provide it to the Signature builder if you are sending a GET RelayState parameter or
398
+ Signature validation process will fail at the Identity Provider.
399
+
400
+ The Service Provider will sign the request/responses with its private key.
401
+ The Identity Provider will validate the sign of the received request/responses with the public x500 cert of the
402
+ Service Provider.
403
+
404
+ Notice that this toolkit uses 'settings.certificate' and 'settings.private_key' for the sign and the decrypt process.
405
+
406
+ Enable/disable the soft mode by the settings.soft parameter. When is set false, the saml validations errors will raise an exception.
407
+
408
+ ## Decrypting
409
+
410
+ The Ruby Toolkit supports EncryptedAssertion.
411
+
412
+ In order to be able to decrypt a SAML Response that contains a EncryptedAssertion we need first to define the private key and the public cert of the service provider, and share this with the Identity Provider.
413
+
414
+ ```ruby
415
+ settings.certificate = "CERTIFICATE TEXT WITH HEAD AND FOOT"
416
+ settings.private_key = "PRIVATE KEY TEXT WITH HEAD AND FOOT"
417
+ ```
418
+
419
+ The Identity Provider will encrypt the Assertion with the public cert of the Service Provider.
420
+ The Service Provider will decrypt the EncryptedAssertion with its private key.
421
+
422
+ Notice that this toolkit uses 'settings.certificate' and 'settings.private_key' for the sign and the decrypt process.
423
+
424
+ ## Single Log Out
425
+
426
+ The Ruby Toolkit supports SP-initiated Single Logout and IdP-Initiated Single Logout.
427
+
428
+ Here is an example that we could add to our previous controller to generate and send a SAML Logout Request to the IdP
429
+
430
+ ```ruby
431
+ # Create a SP initiated SLO
432
+ def sp_logout_request
433
+ # LogoutRequest accepts plain browser requests w/o paramters
434
+ settings = saml_settings
435
+
436
+ if settings.idp_slo_target_url.nil?
437
+ logger.info "SLO IdP Endpoint not found in settings, executing then a normal logout'"
438
+ delete_session
439
+ else
440
+
441
+ # Since we created a new SAML request, save the transaction_id
442
+ # to compare it with the response we get back
443
+ logout_request = OneLogin::RubySaml::Logoutrequest.new()
444
+ session[:transaction_id] = logout_request.uuid
445
+ logger.info "New SP SLO for userid '#{session[:userid]}' transactionid '#{session[:transaction_id]}'"
446
+
447
+ if settings.name_identifier_value.nil?
448
+ settings.name_identifier_value = session[:userid]
449
+ end
450
+
451
+ relayState = url_for controller: 'saml', action: 'index'
452
+ redirect_to(logout_request.create(settings, :RelayState => relayState))
453
+ end
454
+ end
455
+ ```
456
+
457
+ and this method process the SAML Logout Response sent by the IdP as reply of the SAML Logout Request
458
+
459
+ ```ruby
460
+ # After sending an SP initiated LogoutRequest to the IdP, we need to accept
461
+ # the LogoutResponse, verify it, then actually delete our session.
462
+ def process_logout_response
463
+ settings = Account.get_saml_settings
464
+
465
+ if session.has_key? :transation_id
466
+ logout_response = OneLogin::RubySaml::Logoutresponse.new(params[:SAMLResponse], settings, :matches_request_id => session[:transation_id])
467
+ else
468
+ logout_response = OneLogin::RubySaml::Logoutresponse.new(params[:SAMLResponse], settings)
469
+ end
470
+
471
+ logger.info "LogoutResponse is: #{logout_response.to_s}"
472
+
473
+ # Validate the SAML Logout Response
474
+ if not logout_response.validate
475
+ logger.error "The SAML Logout Response is invalid"
476
+ else
477
+ # Actually log out this session
478
+ if logout_response.success?
479
+ logger.info "Delete session for '#{session[:userid]}'"
480
+ delete_session
481
+ end
482
+ end
483
+ end
484
+
485
+ # Delete a user's session.
486
+ def delete_session
487
+ session[:userid] = nil
488
+ session[:attributes] = nil
489
+ end
490
+ ```
491
+
492
+ Here is an example that we could add to our previous controller to process a SAML Logout Request from the IdP and reply a SAML Logout Response to the IdP
493
+
494
+ ```ruby
495
+ # Method to handle IdP initiated logouts
496
+ def idp_logout_request
497
+ settings = Account.get_saml_settings
498
+ logout_request = OneLogin::RubySaml::SloLogoutrequest.new(params[:SAMLRequest])
499
+ if !logout_request.is_valid?
500
+ logger.error "IdP initiated LogoutRequest was not valid!"
501
+ render :inline => logger.error
502
+ end
503
+ logger.info "IdP initiated Logout for #{logout_request.name_id}"
504
+
505
+ # Actually log out this session
506
+ delete_session
507
+
508
+ # Generate a response to the IdP.
509
+ logout_request_id = logout_request.id
510
+ logout_response = OneLogin::RubySaml::SloLogoutresponse.new.create(settings, logout_request_id, nil, :RelayState => params[:RelayState])
511
+ redirect_to logout_response
512
+ end
513
+ ```
514
+
515
+ All the mentioned methods could be handled in a unique view:
516
+
517
+ ```ruby
518
+ # Trigger SP and IdP initiated Logout requests
519
+ def logout
520
+ # If we're given a logout request, handle it in the IdP logout initiated method
521
+ if params[:SAMLRequest]
522
+ return idp_logout_request
523
+ # We've been given a response back from the IdP, process it
524
+ elsif params[:SAMLResponse]
525
+ return process_logout_response
526
+ # Initiate SLO (send Logout Request)
527
+ else
528
+ return sp_logout_request
529
+ end
530
+ end
531
+ ```
532
+
533
+
534
+
535
+ ## Service Provider Metadata
536
+
537
+ To form a trusted pair relationship with the IdP, the SP (you) need to provide metadata XML
538
+ to the IdP for various good reasons. (Caching, certificate lookups, relaying party permissions, etc)
539
+
540
+ 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.
541
+
542
+ The metdata will be polled by the IdP every few minutes, so updating your settings should propagate
543
+ to the IdP settings.
544
+
545
+ ```ruby
546
+ class SamlController < ApplicationController
547
+ # ... the rest of your controller definitions ...
548
+ def metadata
549
+ settings = Account.get_saml_settings
550
+ meta = OneLogin::RubySaml::Metadata.new
551
+ render :xml => meta.generate(settings), :content_type => "application/samlmetadata+xml"
552
+ end
553
+ end
554
+ ```
555
+
556
+ ## Clock Drift
557
+
558
+ Server clocks tend to drift naturally. If during validation of the response you get the error "Current time is earlier than NotBefore condition" then this may be due to clock differences between your system and that of the Identity Provider.
559
+
560
+ First, ensure that both systems synchronize their clocks, using for example the industry standard [Network Time Protocol (NTP)](http://en.wikipedia.org/wiki/Network_Time_Protocol).
561
+
562
+ Even then you may experience intermittent issues though, because the clock of the Identity Provider may drift slightly ahead of your system clocks. To allow for a small amount of clock drift you can initialize the response passing in an option named `:allowed_clock_drift`. Its value must be given in a number (and/or fraction) of seconds. The value given is added to the current time at which the response is validated before it's tested against the `NotBefore` assertion. For example:
563
+
564
+ ```ruby
565
+ response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], :allowed_clock_drift => 1.second)
566
+ ```
567
+
568
+ Make sure to keep the value as comfortably small as possible to keep security risks to a minimum.
569
+
570
+ ## Attribute Service
571
+
572
+ 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.
573
+
574
+ ```ruby
575
+ settings = OneLogin::RubySaml::Settings.new
576
+
577
+ settings.attributes_index = 5
578
+ settings.attribute_consuming_service.configure do
579
+ service_name "Service"
580
+ service_index 5
581
+ add_attribute :name => "Name", :name_format => "Name Format", :friendly_name => "Friendly Name"
582
+ add_attribute :name => "Another Attribute", :name_format => "Name Format", :friendly_name => "Friendly Name", :attribute_value => "Attribute Value"
583
+ end
584
+ ```