ruby-saml 0.8.18 → 0.9

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

Potentially problematic release.


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

Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.travis.yml +1 -6
  4. data/Gemfile +2 -12
  5. data/README.md +363 -35
  6. data/Rakefile +14 -0
  7. data/changelog.md +22 -9
  8. data/lib/onelogin/ruby-saml/attribute_service.rb +34 -0
  9. data/lib/onelogin/ruby-saml/attributes.rb +26 -64
  10. data/lib/onelogin/ruby-saml/authrequest.rb +47 -93
  11. data/lib/onelogin/ruby-saml/idp_metadata_parser.rb +87 -0
  12. data/lib/onelogin/ruby-saml/logoutrequest.rb +36 -100
  13. data/lib/onelogin/ruby-saml/logoutresponse.rb +25 -35
  14. data/lib/onelogin/ruby-saml/metadata.rb +46 -16
  15. data/lib/onelogin/ruby-saml/response.rb +63 -373
  16. data/lib/onelogin/ruby-saml/saml_message.rb +78 -0
  17. data/lib/onelogin/ruby-saml/settings.rb +54 -122
  18. data/lib/onelogin/ruby-saml/slo_logoutrequest.rb +25 -71
  19. data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +37 -102
  20. data/lib/onelogin/ruby-saml/utils.rb +32 -199
  21. data/lib/onelogin/ruby-saml/version.rb +1 -1
  22. data/lib/ruby-saml.rb +5 -2
  23. data/lib/schemas/{saml20assertion_schema.xsd → saml-schema-assertion-2.0.xsd} +283 -283
  24. data/lib/schemas/saml-schema-authn-context-2.0.xsd +23 -0
  25. data/lib/schemas/saml-schema-authn-context-types-2.0.xsd +821 -0
  26. data/lib/schemas/saml-schema-metadata-2.0.xsd +339 -0
  27. data/lib/schemas/{saml20protocol_schema.xsd → saml-schema-protocol-2.0.xsd} +302 -302
  28. data/lib/schemas/sstc-metadata-attr.xsd +35 -0
  29. data/lib/schemas/sstc-saml-attribute-ext.xsd +25 -0
  30. data/lib/schemas/sstc-saml-metadata-algsupport-v1.0.xsd +41 -0
  31. data/lib/schemas/sstc-saml-metadata-ui-v1.0.xsd +89 -0
  32. data/lib/schemas/{xenc_schema.xsd → xenc-schema.xsd} +1 -11
  33. data/lib/schemas/xml.xsd +287 -0
  34. data/lib/schemas/{xmldsig_schema.xsd → xmldsig-core-schema.xsd} +0 -9
  35. data/lib/xml_security.rb +83 -235
  36. data/ruby-saml.gemspec +1 -0
  37. data/test/idp_metadata_parser_test.rb +54 -0
  38. data/test/logoutrequest_test.rb +68 -155
  39. data/test/logoutresponse_test.rb +43 -32
  40. data/test/metadata_test.rb +87 -0
  41. data/test/request_test.rb +102 -99
  42. data/test/response_test.rb +181 -495
  43. data/test/responses/idp_descriptor.xml +3 -0
  44. data/test/responses/logoutresponse_fixtures.rb +7 -8
  45. data/test/responses/response_no_cert_and_encrypted_attrs.xml +29 -0
  46. data/test/responses/response_with_multiple_attribute_values.xml +1 -1
  47. data/test/responses/slo_request.xml +4 -0
  48. data/test/settings_test.rb +25 -112
  49. data/test/slo_logoutrequest_test.rb +40 -50
  50. data/test/slo_logoutresponse_test.rb +86 -185
  51. data/test/test_helper.rb +27 -102
  52. data/test/xml_security_test.rb +114 -337
  53. metadata +30 -81
  54. data/lib/onelogin/ruby-saml/setting_error.rb +0 -6
  55. data/test/certificates/certificate.der +0 -0
  56. data/test/certificates/formatted_certificate +0 -14
  57. data/test/certificates/formatted_chained_certificate +0 -42
  58. data/test/certificates/formatted_private_key +0 -12
  59. data/test/certificates/formatted_rsa_private_key +0 -12
  60. data/test/certificates/invalid_certificate1 +0 -1
  61. data/test/certificates/invalid_certificate2 +0 -1
  62. data/test/certificates/invalid_certificate3 +0 -12
  63. data/test/certificates/invalid_chained_certificate1 +0 -1
  64. data/test/certificates/invalid_private_key1 +0 -1
  65. data/test/certificates/invalid_private_key2 +0 -1
  66. data/test/certificates/invalid_private_key3 +0 -10
  67. data/test/certificates/invalid_rsa_private_key1 +0 -1
  68. data/test/certificates/invalid_rsa_private_key2 +0 -1
  69. data/test/certificates/invalid_rsa_private_key3 +0 -10
  70. data/test/certificates/ruby-saml-2.crt +0 -15
  71. data/test/requests/logoutrequest_fixtures.rb +0 -47
  72. data/test/responses/encrypted_new_attack.xml.base64 +0 -1
  73. data/test/responses/invalids/invalid_issuer_assertion.xml.base64 +0 -1
  74. data/test/responses/invalids/invalid_issuer_message.xml.base64 +0 -1
  75. data/test/responses/invalids/multiple_signed.xml.base64 +0 -1
  76. data/test/responses/invalids/no_signature.xml.base64 +0 -1
  77. data/test/responses/invalids/response_with_concealed_signed_assertion.xml +0 -51
  78. data/test/responses/invalids/response_with_doubled_signed_assertion.xml +0 -49
  79. data/test/responses/invalids/signature_wrapping_attack.xml.base64 +0 -1
  80. data/test/responses/response_node_text_attack.xml.base64 +0 -1
  81. data/test/responses/response_with_concealed_signed_assertion.xml +0 -51
  82. data/test/responses/response_with_doubled_signed_assertion.xml +0 -49
  83. data/test/responses/response_with_multiple_attribute_statements.xml +0 -72
  84. data/test/responses/response_with_signed_assertion_3.xml +0 -30
  85. data/test/responses/response_with_signed_message_and_assertion.xml +0 -34
  86. data/test/responses/response_with_undefined_recipient.xml.base64 +0 -1
  87. data/test/responses/response_wrapped.xml.base64 +0 -150
  88. data/test/responses/valid_response.xml.base64 +0 -1
  89. data/test/responses/valid_response_without_x509certificate.xml.base64 +0 -1
  90. data/test/utils_test.rb +0 -231
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f7f02b4fb1490e44140c1e24ea61bf0ef061f0da
4
- data.tar.gz: 3b2fec942140bb2fd2e49a17fc29dd0d0327d814
3
+ metadata.gz: 2a74680083b8b3cd4e4145de5e3358addac4fce7
4
+ data.tar.gz: 7fc603140154d73be347544795a7616ba1fb246e
5
5
  SHA512:
6
- metadata.gz: a2dade6d6d672b213a3f8d3af088edfa208f3af4482aae033a40ff8ac9c500ca48fded7eee073e67dd6f37daa27786a6303777e0381a3338f987f87a63f460a2
7
- data.tar.gz: 0cc6bb2264de5c688545bcb24794313758a97cca3c9bfa5c69b07331ffbc719acb1a69039ec39aa2e5e6817e8917e7495da7c07a41eff120d57f30e9708962bc
6
+ metadata.gz: 3fbdd9922622ff8c2e62ab0956d319db1bc13311da52c969ff5307d53d2d2ed5eb6c2a6900452a3976478b552b7085af6b9338db2bcea4958be0dd809f412f34
7
+ data.tar.gz: 071f1f8f248e724b60b9870d5c1a4c7c3b9f2630cb289f762b11911392aa0d9d471c1f1fbf5c2e0cfe4e1d5eaf8c29a82eeee7a52845f1bcc77c10e2d75941d4
data/.gitignore CHANGED
@@ -10,3 +10,4 @@ test/Test.iml
10
10
  .rvmrc
11
11
  *.gem
12
12
  .bundle
13
+ *.patch
data/.travis.yml CHANGED
@@ -2,10 +2,5 @@ language: ruby
2
2
  rvm:
3
3
  - 1.8.7
4
4
  - 1.9.3
5
- - 2.0.0
6
- - 2.1.0
5
+ - 2.1.1
7
6
  - ree
8
-
9
- before_install:
10
- - gem update --system 2.1.11
11
- - gem install bundler -v 1.11.2
data/Gemfile CHANGED
@@ -5,19 +5,9 @@ source 'http://rubygems.org'
5
5
 
6
6
  gemspec
7
7
 
8
- if RUBY_VERSION < '1.9'
9
- gem 'nokogiri', '~> 1.5.0'
10
- gem 'minitest', '~> 5.5', '<= 5.11.3'
11
- elsif RUBY_VERSION < '2.1'
12
- gem 'nokogiri', '>= 1.5.0', '<= 1.6.8.1'
13
- gem 'minitest', '~> 5.5'
14
- else
15
- gem 'nokogiri', '>= 1.5.0'
16
- gem 'minitest', '~> 5.5'
17
- end
18
-
19
8
  group :test do
20
9
  if RUBY_VERSION < '1.9'
10
+ gem 'nokogiri', '~> 1.5.0'
21
11
  gem 'ruby-debug', '~> 0.10.4'
22
12
  elsif RUBY_VERSION < '2.0'
23
13
  gem 'debugger-linecache', '~> 1.2.0'
@@ -32,6 +22,6 @@ group :test do
32
22
  gem 'rake', '~> 10'
33
23
  gem 'shoulda', '~> 2.11'
34
24
  gem 'systemu', '~> 2'
35
- gem 'test-unit', '~> 3.0.9'
25
+ gem 'test-unit', '~> 3'
36
26
  gem 'timecop', '<= 0.6.0'
37
27
  end
data/README.md CHANGED
@@ -1,17 +1,7 @@
1
1
  # Ruby SAML [![Build Status](https://secure.travis-ci.org/onelogin/ruby-saml.png)](http://travis-ci.org/onelogin/ruby-saml)
2
2
 
3
- # Updating from 0.8.8 to 0.8.9
4
- Version `0.8.9` deprecates the use of settings.issuer, use instead settings.sp_entity_id. Deprecates assertion_consumer_logout_service_url and assertion_consumer_logout_service_binding as well, use instead single_logout_service_url and single_logout_service_binding. Adds validate_audience.
5
-
6
- # Updating from 0.8.7 to 0.8.8
7
- Version `0.8.8` adds support for ForceAuthn and Subjects on AuthNRequests by the new name_identifier_value_requested setting
8
-
9
- ## Note on versions 0.8.6 and 0.8.7
10
- Version `0.8.6` introduced an incompatibility with regards to manipulating the `OneLogin::RubySaml::Response#attributes` property; in this version
11
- the `#attributes` property is a class (`OneLogin::RubySaml::Attributes`) which implements the `Enumerator` module, thus any non-overriden Hash method
12
- will throw a NoMethodError.
13
- Version `0.8.7` overrides the behavior of class `OneLogin::RubySaml::Attributes` by making any method not found on the class be tested on the
14
- internal hash structure, thus all methods available in the `Hash` class are now available in `OneLogin::RubySaml::Response#attributes`.
3
+ ## Updating from 0.8.x to 0.9
4
+ Version `0.9` adds many new features and improvements. It is a recommended update for all Ruby SAML users. For more details, please review [the changelog](changelog.md)
15
5
 
16
6
  ## Updating from 0.7.x to 0.8.x
17
7
  Version `0.8.x` changes the namespace of the gem from `OneLogin::Saml` to `OneLogin::RubySaml`. Please update your implementations of the gem accordingly.
@@ -22,7 +12,47 @@ The Ruby SAML library is for implementing the client side of a SAML authorizatio
22
12
 
23
13
  SAML authorization is a two step process and you are expected to implement support for both.
24
14
 
25
- ## The initialization phase
15
+ 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)
16
+
17
+ ## Adding Features, Pull Requests
18
+ * Fork the repository
19
+ * Make your feature addition or bug fix
20
+ * Add tests for your new features. This is important so we don't break any features in a future version unintentionally.
21
+ * Ensure all tests pass.
22
+ * Do not change rakefile, version, or history.
23
+ * Open a pull request, following [this template](https://gist.github.com/Lordnibbler/11002759).
24
+
25
+ ## Getting Started
26
+ 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:
27
+
28
+ Using `Gemfile`
29
+
30
+ ```ruby
31
+ # latest stable
32
+ gem 'ruby-saml', '~> 0.9'
33
+
34
+ # or track master for bleeding-edge
35
+ gem 'ruby-saml', :github => 'onelogin/ruby-saml'
36
+ ```
37
+
38
+ Using Bundler
39
+
40
+ ```sh
41
+ gem install ruby-saml
42
+ ```
43
+
44
+ When requiring the gem, you can add the whole toolkit
45
+ ```ruby
46
+ require 'onelogin/ruby-saml'
47
+ ```
48
+
49
+ or just the required components individually:
50
+
51
+ ```ruby
52
+ require 'onelogin/ruby-saml/authrequest'
53
+ ```
54
+
55
+ ## The Initialization Phase
26
56
 
27
57
  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):
28
58
 
@@ -40,10 +70,13 @@ def consume
40
70
  response = OneLogin::RubySaml::Response.new(params[:SAMLResponse])
41
71
  response.settings = saml_settings
42
72
 
43
- if response.is_valid? && user = current_account.users.find_by_email(response.name_id)
44
- authorize_success(user)
73
+ # We validate the SAML Response and check if the user already exists in the system
74
+ if response.is_valid?
75
+ # authorize_success, log the user
76
+ session[:userid] = response.name_id
77
+ session[:attributes] = response.attributes
45
78
  else
46
- authorize_failure(user)
79
+ authorize_failure # This method shows an error message
47
80
  end
48
81
  end
49
82
  ```
@@ -55,13 +88,21 @@ def saml_settings
55
88
  settings = OneLogin::RubySaml::Settings.new
56
89
 
57
90
  settings.assertion_consumer_service_url = "http://#{request.host}/saml/finalize"
58
- settings.sp_entity_id = request.host
59
- settings.idp_sso_target_url = "https://app.onelogin.com/saml/signon/#{OneLoginAppId}"
91
+ settings.issuer = request.host
92
+ settings.idp_sso_target_url = "https://app.onelogin.com/saml/metadata/#{OneLoginAppId}"
93
+ settings.idp_entity_id = "https://app.onelogin.com/saml/metadata/#{OneLoginAppId}"
94
+ settings.idp_sso_target_url = "https://app.onelogin.com/trust/saml2/http-post/sso/#{OneLoginAppId}"
95
+ settings.idp_slo_target_url = "https://app.onelogin.com/trust/saml2/http-redirect/slo/#{OneLoginAppId}"
60
96
  settings.idp_cert_fingerprint = OneLoginAppCertFingerPrint
61
97
  settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
98
+
62
99
  # Optional for most SAML IdPs
63
100
  settings.authn_context = "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport"
64
101
 
102
+ # Optional bindings (defaults to Redirect for logout POST for acs)
103
+ settings.assertion_consumer_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
104
+ settings.single_logout_service_url_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
105
+
65
106
  settings
66
107
  end
67
108
  ```
@@ -80,10 +121,13 @@ class SamlController < ApplicationController
80
121
  response = OneLogin::RubySaml::Response.new(params[:SAMLResponse])
81
122
  response.settings = saml_settings
82
123
 
83
- if response.is_valid? && user = current_account.users.find_by_email(response.name_id)
84
- authorize_success(user)
124
+ # We validate the SAML Response and check if the user already exists in the system
125
+ if response.is_valid?
126
+ # authorize_success, log the user
127
+ session[:userid] = response.name_id
128
+ session[:attributes] = response.attributes
85
129
  else
86
- authorize_failure(user)
130
+ authorize_failure # This method shows an error message
87
131
  end
88
132
  end
89
133
 
@@ -93,20 +137,57 @@ class SamlController < ApplicationController
93
137
  settings = OneLogin::RubySaml::Settings.new
94
138
 
95
139
  settings.assertion_consumer_service_url = "http://#{request.host}/saml/consume"
96
- settings.sp_entity_id = request.host
140
+ settings.issuer = request.host
97
141
  settings.idp_sso_target_url = "https://app.onelogin.com/saml/signon/#{OneLoginAppId}"
98
142
  settings.idp_cert_fingerprint = OneLoginAppCertFingerPrint
99
143
  settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
144
+
100
145
  # Optional for most SAML IdPs
101
146
  settings.authn_context = "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport"
102
147
 
148
+ # Optional. Describe according to IdP specification (if supported) which attributes the SP desires to receive in SAMLResponse.
149
+ settings.attributes_index = 5
150
+ # Optional. Describe an attribute consuming service for support of additional attributes.
151
+ settings.attribute_consuming_service.configure do
152
+ service_name "Service"
153
+ service_index 5
154
+ add_attribute :name => "Name", :name_format => "Name Format", :friendly_name => "Friendly Name"
155
+ end
156
+
103
157
  settings
104
158
  end
105
159
  end
106
160
  ```
161
+ ## Metadata Based Configuration
107
162
 
108
- If are using saml:AttributeStatement to transfare metadata, like the user name, you can access all the attributes through response.attributes. It
109
- contains all the saml:AttributeStatement with its 'Name' as a indifferent key and the one saml:AttributeValue as value.
163
+ 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
164
+ key certificates which add to the trusted relationship. The IdP administrator can also configure custom settings for an SP based on the metadata.
165
+
166
+ Using ```idp_metadata_parser.parse_remote``` IdP metadata will be added to the settings withouth further ado.
167
+
168
+ ```ruby
169
+ def saml_settings
170
+
171
+ idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new
172
+ # Returns OneLogin::RubySaml::Settings prepopulated with idp metadata
173
+ settings = idp_metadata_parser.parse_remote("https://example.com/auth/saml2/idp/metadata")
174
+
175
+ settings.assertion_consumer_service_url = "http://#{request.host}/saml/consume"
176
+ settings.issuer = request.host
177
+ settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
178
+ # Optional for most SAML IdPs
179
+ settings.authn_context = "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport"
180
+
181
+ settings
182
+ end
183
+ ```
184
+ The following attributes are set:
185
+ * id_sso_target_url
186
+ * idp_slo_target_url
187
+ * id_cert_fingerpint
188
+
189
+ If 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
190
+ `single_value_compatibility` (when activate, only one value returned, the first one)
110
191
 
111
192
  ```ruby
112
193
  response = OneLogin::RubySaml::Response.new(params[:SAMLResponse])
@@ -115,13 +196,253 @@ response.settings = saml_settings
115
196
  response.attributes[:username]
116
197
  ```
117
198
 
199
+ Imagine this saml:AttributeStatement
200
+
201
+ ```xml
202
+ <saml:AttributeStatement>
203
+ <saml:Attribute Name="uid">
204
+ <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>
205
+ </saml:Attribute>
206
+ <saml:Attribute Name="another_value">
207
+ <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>
208
+ <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>
209
+ </saml:Attribute>
210
+ <saml:Attribute Name="role">
211
+ <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>
212
+ </saml:Attribute>
213
+ <saml:Attribute Name="role">
214
+ <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>
215
+ <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>
216
+ </saml:Attribute>
217
+ <saml:Attribute Name="attribute_with_nil_value">
218
+ <saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>
219
+ </saml:Attribute>
220
+ <saml:Attribute Name="attribute_with_nils_and_empty_strings">
221
+ <saml:AttributeValue/>
222
+ <saml:AttributeValue>valuePresent</saml:AttributeValue>
223
+ <saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>
224
+ <saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="1"/>
225
+ </saml:Attribute>
226
+ </saml:AttributeStatement>
227
+ ```
228
+
229
+ ```ruby
230
+ pp(response.attributes) # is an OneLogin::RubySaml::Attributes object
231
+ # => @attributes=
232
+ {"uid"=>["demo"],
233
+ "another_value"=>["value1", "value2"],
234
+ "role"=>["role1", "role2", "role3"],
235
+ "attribute_with_nil_value"=>[nil],
236
+ "attribute_with_nils_and_empty_strings"=>["", "valuePresent", nil, nil]}>
237
+
238
+ # Active single_value_compatibility
239
+ OneLogin::RubySaml::Attributes.single_value_compatibility = true
240
+
241
+ pp(response.attributes[:uid])
242
+ # => "demo"
243
+
244
+ pp(response.attributes[:role])
245
+ # => "role1"
246
+
247
+ pp(response.attributes.single(:role))
248
+ # => "role1"
249
+
250
+ pp(response.attributes.multi(:role))
251
+ # => ["role1", "role2", "role3"]
252
+
253
+ pp(response.attributes[:attribute_with_nil_value])
254
+ # => nil
255
+
256
+ pp(response.attributes[:attribute_with_nils_and_empty_strings])
257
+ # => ""
258
+
259
+ pp(response.attributes[:not_exists])
260
+ # => nil
261
+
262
+ pp(response.attributes.single(:not_exists))
263
+ # => nil
264
+
265
+ pp(response.attributes.multi(:not_exists))
266
+ # => nil
267
+
268
+ # Deactive single_value_compatibility
269
+ OneLogin::RubySaml::Attributes.single_value_compatibility = false
270
+
271
+ pp(response.attributes[:uid])
272
+ # => ["demo"]
273
+
274
+ pp(response.attributes[:role])
275
+ # => ["role1", "role2", "role3"]
276
+
277
+ pp(response.attributes.single(:role))
278
+ # => "role1"
279
+
280
+ pp(response.attributes.multi(:role))
281
+ # => ["role1", "role2", "role3"]
282
+
283
+ pp(response.attributes[:attribute_with_nil_value])
284
+ # => [nil]
285
+
286
+ pp(response.attributes[:attribute_with_nils_and_empty_strings])
287
+ # => ["", "valuePresent", nil, nil]
288
+
289
+ pp(response.attributes[:not_exists])
290
+ # => nil
291
+
292
+ pp(response.attributes.single(:not_exists))
293
+ # => nil
294
+
295
+ pp(response.attributes.multi(:not_exists))
296
+ # => nil
297
+ ```
298
+
299
+ 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.
300
+ If we want to add a saml:AuthnContextDeclRef, define a `settings.authn_context_decl_ref`.
301
+
302
+
303
+ ## Signing
304
+
305
+ The Ruby Toolkit supports 2 different kinds of signature: Embeded and as GET parameter
306
+
307
+ In order to be able to sign we need first to define the private key and the public cert of the service provider
308
+
309
+ ```ruby
310
+ settings.certificate = "CERTIFICATE TEXT WITH HEADS"
311
+ settings.private_key = "PRIVATE KEY TEXT WITH HEADS"
312
+ ```
313
+
314
+ The settings related to sign are stored in the `security` attribute of the settings:
315
+
316
+ ```ruby
317
+ settings.security[:authn_requests_signed] = true # Enable or not signature on AuthNRequest
318
+ settings.security[:logout_requests_signed] = true # Enable or not signature on Logout Request
319
+ settings.security[:logout_responses_signed] = true # Enable or not signature on Logout Response
320
+
321
+ settings.security[:digest_method] = XMLSecurity::Document::SHA1
322
+ settings.security[:signature_method] = XMLSecurity::Document::SHA1
323
+
324
+ settings.security[:embed_sign] = false # Embeded signature or HTTP GET parameter Signature
325
+ ```
326
+
327
+
328
+ ## Single Log Out
329
+
330
+ The Ruby Toolkit supports SP-initiated Single Logout and IdP-Initiated Single Logout.
331
+
332
+ Here is an example that we could add to our previous controller to generate and send a SAML Logout Request to the IdP
333
+
334
+ ```ruby
335
+ # Create a SP initiated SLO
336
+ def sp_logout_request
337
+ # LogoutRequest accepts plain browser requests w/o paramters
338
+ settings = saml_settings
339
+
340
+ if settings.idp_slo_target_url.nil?
341
+ logger.info "SLO IdP Endpoint not found in settings, executing then a normal logout'"
342
+ delete_session
343
+ else
344
+
345
+ # Since we created a new SAML request, save the transaction_id
346
+ # to compare it with the response we get back
347
+ logout_request = OneLogin::RubySaml::Logoutrequest.new()
348
+ session[:transaction_id] = logout_request.uuid
349
+ logger.info "New SP SLO for userid '#{session[:userid]}' transactionid '#{session[:transaction_id]}'"
350
+
351
+ if settings.name_identifier_value.nil?
352
+ settings.name_identifier_value = session[:userid]
353
+ end
354
+
355
+ relayState = url_for controller: 'saml', action: 'index'
356
+ redirect_to(logout_request.create(settings, :RelayState => relayState))
357
+ end
358
+ end
359
+ ```
360
+
361
+ and this method process the SAML Logout Response sent by the IdP as reply of the SAML Logout Request
362
+
363
+ ```ruby
364
+ # After sending an SP initiated LogoutRequest to the IdP, we need to accept
365
+ # the LogoutResponse, verify it, then actually delete our session.
366
+ def process_logout_response
367
+ settings = Account.get_saml_settings
368
+
369
+ if session.has_key? :transation_id
370
+ logout_response = OneLogin::RubySaml::Logoutresponse.new(params[:SAMLResponse], settings, :matches_request_id => session[:transation_id])
371
+ else
372
+ logout_response = OneLogin::RubySaml::Logoutresponse.new(params[:SAMLResponse], settings)
373
+ end
374
+
375
+ logger.info "LogoutResponse is: #{logout_response.to_s}"
376
+
377
+ # Validate the SAML Logout Response
378
+ if not logout_response.validate
379
+ logger.error "The SAML Logout Response is invalid"
380
+ else
381
+ # Actually log out this session
382
+ if logout_response.success?
383
+ logger.info "Delete session for '#{session[:userid]}'"
384
+ delete_session
385
+ end
386
+ end
387
+ end
388
+
389
+ # Delete a user's session.
390
+ def delete_session
391
+ session[:userid] = nil
392
+ session[:attributes] = nil
393
+ end
394
+ ```
395
+
396
+ 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
397
+
398
+ ```ruby
399
+ # Method to handle IdP initiated logouts
400
+ def idp_logout_request
401
+ settings = Account.get_saml_settings
402
+ logout_request = OneLogin::RubySaml::SloLogoutrequest.new(params[:SAMLRequest])
403
+ if !logout_request.is_valid?
404
+ logger.error "IdP initiated LogoutRequest was not valid!"
405
+ render :inline => logger.error
406
+ end
407
+ logger.info "IdP initiated Logout for #{logout_request.name_id}"
408
+
409
+ # Actually log out this session
410
+ delete_session
411
+
412
+ # Generate a response to the IdP.
413
+ logout_request_id = logout_request.id
414
+ logout_response = OneLogin::RubySaml::SloLogoutresponse.new.create(settings, logout_request_id, nil, :RelayState => params[:RelayState])
415
+ redirect_to logout_response
416
+ end
417
+ ```
418
+
419
+ All the mentioned methods could be handled in a unique view:
420
+
421
+ ```ruby
422
+ # Trigger SP and IdP initiated Logout requests
423
+ def logout
424
+ # If we're given a logout request, handle it in the IdP logout initiated method
425
+ if params[:SAMLRequest]
426
+ return idp_logout_request
427
+ # We've been given a response back from the IdP, process it
428
+ elsif params[:SAMLResponse]
429
+ return process_logout_response
430
+ # Initiate SLO (send Logout Request)
431
+ else
432
+ return sp_logout_request
433
+ end
434
+ end
435
+ ```
436
+
437
+
438
+
118
439
  ## Service Provider Metadata
119
440
 
120
441
  To form a trusted pair relationship with the IdP, the SP (you) need to provide metadata XML
121
442
  to the IdP for various good reasons. (Caching, certificate lookups, relaying party permissions, etc)
122
443
 
123
- The class OneLogin::RubySaml::Metadata takes care of this by reading the Settings and returning XML. All
124
- you have to do is add a controller to return the data, then give this URL to the IdP administrator.
444
+ 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.
445
+
125
446
  The metdata will be polled by the IdP every few minutes, so updating your settings should propagate
126
447
  to the IdP settings.
127
448
 
@@ -131,7 +452,7 @@ class SamlController < ApplicationController
131
452
  def metadata
132
453
  settings = Account.get_saml_settings
133
454
  meta = OneLogin::RubySaml::Metadata.new
134
- render :xml => meta.generate(settings)
455
+ render :xml => meta.generate(settings), :content_type => "application/samlmetadata+xml"
135
456
  end
136
457
  end
137
458
  ```
@@ -145,16 +466,23 @@ First, ensure that both systems synchronize their clocks, using for example the
145
466
  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:
146
467
 
147
468
  ```ruby
148
- response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], :allowed_clock_drift => 1)
469
+ response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], :allowed_clock_drift => 1.second)
149
470
  ```
150
471
 
151
472
  Make sure to keep the value as comfortably small as possible to keep security risks to a minimum.
152
473
 
153
- ## Note on Patches/Pull Requests
474
+ ## Attribute Service
154
475
 
155
- * Fork the project.
156
- * Make your feature addition or bug fix.
157
- * Add tests for it. This is important so I don't break it in a
158
- future version unintentionally.
159
- * Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
160
- * Send me a pull request. Bonus points for topic branches.
476
+ 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.
477
+
478
+ ```ruby
479
+ settings = OneLogin::RubySaml::Settings.new
480
+
481
+ settings.attributes_index = 5
482
+ settings.attribute_consuming_service.configure do
483
+ service_name "Service"
484
+ service_index 5
485
+ add_attribute :name => "Name", :name_format => "Name Format", :friendly_name => "Friendly Name"
486
+ add_attribute :name => "Another Attribute", :name_format => "Name Format", :friendly_name => "Friendly Name", :attribute_value => "Attribute Value"
487
+ end
488
+ ```
data/Rakefile CHANGED
@@ -25,3 +25,17 @@ end
25
25
  task :test
26
26
 
27
27
  task :default => :test
28
+
29
+ # require 'rake/rdoctask'
30
+ # Rake::RDocTask.new do |rdoc|
31
+ # if File.exist?('VERSION')
32
+ # version = File.read('VERSION')
33
+ # else
34
+ # version = ""
35
+ # end
36
+
37
+ # rdoc.rdoc_dir = 'rdoc'
38
+ # rdoc.title = "ruby-saml #{version}"
39
+ # rdoc.rdoc_files.include('README*')
40
+ # rdoc.rdoc_files.include('lib/**/*.rb')
41
+ #end
data/changelog.md CHANGED
@@ -1,13 +1,26 @@
1
1
  # RubySaml Changelog
2
-
3
- ### 0.8.4 (March 5, 2018)
4
- * Improve the fix for CVE-2017-11428 to parse CDATA properly
5
-
6
- ### 0.8.3 (Feb 27, 2018)
7
- * Fix vulnerability CVE-2017-11428. Process text of nodes properly, ignoring comments
8
- * Fix DigestMethod lookup bug #144
9
-
10
- ### 0.8.2 (Jan 26, 2014)
2
+ ### 0.9 (Jan 26, 2015)
3
+ * [#169](https://github.com/onelogin/ruby-saml/pull/169) WantAssertionSigned should be either true or false
4
+ * [#167](https://github.com/onelogin/ruby-saml/pull/167) (doc update) make unit of clock drift obvious
5
+ * [#160](https://github.com/onelogin/ruby-saml/pull/160) Extended solution for Attributes method [] can raise NoMethodError
6
+ * [#158](https://github.com/onelogin/ruby-saml/pull/1) Added ability to specify attribute services in metadata
7
+ * [#154](https://github.com/onelogin/ruby-saml/pull/154) Fix incorrect gem declaration statement
8
+ * [#152](https://github.com/onelogin/ruby-saml/pull/152) Fix the PR #99
9
+ * [#150](https://github.com/onelogin/ruby-saml/pull/150) Nokogiri already in gemspec
10
+ * [#147](https://github.com/onelogin/ruby-saml/pull/147) Fix LogoutResponse issuer validation and implement SAML Response issuer validation.
11
+ * [#144](https://github.com/onelogin/ruby-saml/pull/144) Fix DigestMethod lookup bug
12
+ * [#139](https://github.com/onelogin/ruby-saml/pull/139) Fixes handling of some soft and hard validation failures
13
+ * [#138](https://github.com/onelogin/ruby-saml/pull/138) Change logoutrequest.rb to UTC time
14
+ * [#136](https://github.com/onelogin/ruby-saml/pull/136) Remote idp metadata
15
+ * [#135](https://github.com/onelogin/ruby-saml/pull/135) Restored support for NIL as well as empty AttributeValues
16
+ * [#134](https://github.com/onelogin/ruby-saml/pull/134) explicitly require "onelogin/ruby-saml/logging"
17
+ * [#133](https://github.com/onelogin/ruby-saml/pull/133) Added license to gemspec
18
+ * [#132](https://github.com/onelogin/ruby-saml/pull/132) Support AttributeConsumingServiceIndex in AuthnRequest
19
+ * [#131](https://github.com/onelogin/ruby-saml/pull/131) Add ruby 2.1.1 to .travis.yml
20
+ * [#122](https://github.com/onelogin/ruby-saml/pull/122) Fixes #112 and #117 in a backwards compatible manner
21
+ * [#119](https://github.com/onelogin/ruby-saml/pull/119) Add support for extracting IdP details from metadata xml
22
+
23
+ ### 0.8.2 (Jan 26, 2015)
11
24
  * [#183](https://github.com/onelogin/ruby-saml/pull/183) Resolved a security vulnerability where string interpolation in a `REXML::XPath.first()` method call allowed for arbitrary code execution.
12
25
 
13
26
  ### 0.8.0 (Feb 21, 2014)
@@ -0,0 +1,34 @@
1
+ module OneLogin
2
+ module RubySaml
3
+ class AttributeService
4
+ attr_reader :attributes
5
+ attr_reader :name
6
+ attr_reader :index
7
+
8
+ def initialize
9
+ @index = "1"
10
+ @attributes = []
11
+ end
12
+
13
+ def configure(&block)
14
+ instance_eval &block
15
+ end
16
+
17
+ def configured?
18
+ @attributes.length > 0 && !@name.nil?
19
+ end
20
+
21
+ def service_name(name)
22
+ @name = name
23
+ end
24
+
25
+ def service_index(index)
26
+ @index = index
27
+ end
28
+
29
+ def add_attribute(options={})
30
+ attributes << options
31
+ end
32
+ end
33
+ end
34
+ end