ruby-saml 0.9.4 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of ruby-saml might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/LICENSE +1 -1
- data/README.md +71 -15
- data/changelog.md +15 -6
- data/lib/onelogin/ruby-saml.rb +1 -0
- data/lib/onelogin/ruby-saml/attribute_service.rb +25 -2
- data/lib/onelogin/ruby-saml/attributes.rb +42 -23
- data/lib/onelogin/ruby-saml/authrequest.rb +33 -8
- data/lib/onelogin/ruby-saml/http_error.rb +7 -0
- data/lib/onelogin/ruby-saml/idp_metadata_parser.rb +65 -10
- data/lib/onelogin/ruby-saml/logging.rb +14 -10
- data/lib/onelogin/ruby-saml/logoutrequest.rb +39 -14
- data/lib/onelogin/ruby-saml/logoutresponse.rb +166 -39
- data/lib/onelogin/ruby-saml/metadata.rb +40 -23
- data/lib/onelogin/ruby-saml/response.rb +562 -88
- data/lib/onelogin/ruby-saml/saml_message.rb +80 -14
- data/lib/onelogin/ruby-saml/settings.rb +62 -23
- data/lib/onelogin/ruby-saml/slo_logoutrequest.rb +210 -20
- data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +44 -13
- data/lib/onelogin/ruby-saml/utils.rb +163 -40
- data/lib/onelogin/ruby-saml/version.rb +1 -1
- data/lib/schemas/saml-schema-metadata-2.0.xsd +0 -2
- data/lib/xml_security.rb +87 -29
- data/ruby-saml.gemspec +1 -0
- data/test/certificates/{r1_certificate2_base64 → certificate_without_head_foot} +0 -0
- data/test/certificates/formatted_certificate +14 -0
- data/test/certificates/formatted_private_key +12 -0
- data/test/certificates/formatted_rsa_private_key +12 -0
- data/test/certificates/invalid_certificate1 +1 -0
- data/test/certificates/invalid_certificate2 +1 -0
- data/test/certificates/invalid_certificate3 +12 -0
- data/test/certificates/invalid_private_key1 +1 -0
- data/test/certificates/invalid_private_key2 +1 -0
- data/test/certificates/invalid_private_key3 +10 -0
- data/test/certificates/invalid_rsa_private_key1 +1 -0
- data/test/certificates/invalid_rsa_private_key2 +1 -0
- data/test/certificates/invalid_rsa_private_key3 +10 -0
- data/test/idp_metadata_parser_test.rb +41 -4
- data/test/logging_test.rb +62 -0
- data/test/logout_requests/invalid_slo_request.xml +6 -0
- data/test/{responses → logout_requests}/slo_request.xml +0 -0
- data/test/logout_requests/slo_request.xml.base64 +1 -0
- data/test/logout_requests/slo_request_deflated.xml.base64 +1 -0
- data/test/logout_requests/slo_request_with_session_index.xml +5 -0
- data/test/{responses → logout_responses}/logoutresponse_fixtures.rb +6 -6
- data/test/logoutrequest_test.rb +79 -52
- data/test/logoutresponse_test.rb +206 -59
- data/test/metadata_test.rb +77 -7
- data/test/request_test.rb +80 -65
- data/test/response_test.rb +862 -189
- data/test/responses/attackxee.xml +13 -0
- data/test/responses/invalids/invalid_audience.xml.base64 +1 -0
- data/test/responses/invalids/invalid_issuer_assertion.xml.base64 +1 -0
- data/test/responses/invalids/invalid_issuer_message.xml.base64 +1 -0
- data/test/responses/invalids/invalid_signature_position.xml.base64 +1 -0
- data/test/responses/invalids/invalid_subjectconfirmation_inresponse.xml.base64 +1 -0
- data/test/responses/invalids/invalid_subjectconfirmation_nb.xml.base64 +1 -0
- data/test/responses/invalids/invalid_subjectconfirmation_noa.xml.base64 +1 -0
- data/test/responses/invalids/invalid_subjectconfirmation_recipient.xml.base64 +1 -0
- data/test/responses/invalids/multiple_assertions.xml.base64 +2 -0
- data/test/responses/invalids/multiple_signed.xml.base64 +1 -0
- data/test/responses/invalids/no_id.xml.base64 +1 -0
- data/test/responses/invalids/no_saml2.xml.base64 +1 -0
- data/test/responses/invalids/no_signature.xml.base64 +1 -0
- data/test/responses/invalids/no_status.xml.base64 +1 -0
- data/test/responses/invalids/no_status_code.xml.base64 +1 -0
- data/test/responses/invalids/no_subjectconfirmation_data.xml.base64 +1 -0
- data/test/responses/invalids/no_subjectconfirmation_method.xml.base64 +1 -0
- data/test/responses/invalids/response_encrypted_attrs.xml.base64 +1 -0
- data/test/responses/invalids/response_invalid_signed_element.xml.base64 +1 -0
- data/test/responses/invalids/status_code_responder.xml.base64 +1 -0
- data/test/responses/invalids/status_code_responer_and_msg.xml.base64 +1 -0
- data/test/responses/{response4.xml.base64 → response_assertion_wrapped.xml.base64} +0 -0
- data/test/responses/response_encrypted_nameid.xml.base64 +1 -0
- data/test/responses/response_unsigned_xml_base64 +1 -0
- data/test/responses/{response5.xml.base64 → response_with_saml2_namespace.xml.base64} +0 -0
- data/test/responses/{response3.xml.base64 → response_with_signed_assertion.xml.base64} +0 -0
- data/test/responses/{r1_response6.xml.base64 → response_with_signed_assertion_2.xml.base64} +0 -0
- data/test/responses/{response1.xml.base64 → response_with_undefined_recipient.xml.base64} +0 -0
- data/test/responses/{response2.xml.base64 → response_without_attributes.xml.base64} +0 -0
- data/test/responses/{wrapped_response_2.xml.base64 → response_wrapped.xml.base64} +0 -0
- data/test/responses/signed_message_encrypted_signed_assertion.xml.base64 +1 -0
- data/test/responses/signed_message_encrypted_unsigned_assertion.xml.base64 +1 -0
- data/test/responses/unsigned_message_aes128_encrypted_signed_assertion.xml.base64 +1 -0
- data/test/responses/unsigned_message_aes192_encrypted_signed_assertion.xml.base64 +1 -0
- data/test/responses/unsigned_message_aes256_encrypted_signed_assertion.xml.base64 +1 -0
- data/test/responses/unsigned_message_des192_encrypted_signed_assertion.xml.base64 +1 -0
- data/test/responses/unsigned_message_encrypted_assertion_without_saml_namespace.xml.base64 +1 -0
- data/test/responses/unsigned_message_encrypted_signed_assertion.xml.base64 +1 -0
- data/test/responses/unsigned_message_encrypted_unsigned_assertion.xml.base64 +1 -0
- data/test/responses/valid_response.xml.base64 +1 -0
- data/test/saml_message_test.rb +56 -0
- data/test/settings_test.rb +138 -1
- data/test/slo_logoutrequest_test.rb +239 -28
- data/test/slo_logoutresponse_test.rb +93 -71
- data/test/test_helper.rb +138 -31
- data/test/utils_test.rb +129 -25
- data/test/xml_security_test.rb +140 -71
- metadata +142 -25
- data/test/responses/response_node_text_attack.xml.base64 +0 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 381e8f35422cfa0bbb991272a777f7fdfcdd2081
|
4
|
+
data.tar.gz: 0371c2cbd05ee2b482fca1feda05a82162550295
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a3553e3802bba3fe72375cf5865dfce58a5bf46efa335a43e49f020ee12691bd099ecc3496be389a2556808ad4428bd79d4c4a51171c582df8ac74d66187e2a0
|
7
|
+
data.tar.gz: 473180d499b2ffd2648438ce2c4fd103a8b314ccfa18145cb72215edeecc0864f53dbf016ccd66f6025cdb45aca241df14ca3e9f7ec36731ba4a4004ef0b3a6f
|
data/.gitignore
CHANGED
data/LICENSE
CHANGED
data/README.md
CHANGED
@@ -1,7 +1,19 @@
|
|
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
|
+
|
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
|
+
|
3
15
|
## Updating from 0.8.x to 0.9.x
|
4
|
-
Version `0.9` adds many new features and improvements.
|
16
|
+
Version `0.9` adds many new features and improvements.
|
5
17
|
|
6
18
|
## Updating from 0.7.x to 0.8.x
|
7
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.
|
@@ -18,7 +30,7 @@ We created a demo project for Rails4 that uses the latest version of this librar
|
|
18
30
|
* 1.8.7
|
19
31
|
* 1.9.x
|
20
32
|
* 2.1.x
|
21
|
-
* 2.2.
|
33
|
+
* 2.2.x
|
22
34
|
|
23
35
|
## Adding Features, Pull Requests
|
24
36
|
* Fork the repository
|
@@ -35,7 +47,7 @@ Using `Gemfile`
|
|
35
47
|
|
36
48
|
```ruby
|
37
49
|
# latest stable
|
38
|
-
gem 'ruby-saml', '~> 0.
|
50
|
+
gem 'ruby-saml', '~> 1.0.0'
|
39
51
|
|
40
52
|
# or track master for bleeding-edge
|
41
53
|
gem 'ruby-saml', :github => 'onelogin/ruby-saml'
|
@@ -74,6 +86,19 @@ Using RubyGems
|
|
74
86
|
gem install nokogiri --version '~> 1.5.10'
|
75
87
|
````
|
76
88
|
|
89
|
+
### Configuring Logging
|
90
|
+
|
91
|
+
When troubleshooting SAML integration issues, you will find it extremely helpful to examine the
|
92
|
+
output of this gem's business logic. By default, log messages are emitted to RAILS_DEFAULT_LOGGER
|
93
|
+
when the gem is used in a Rails context, and to STDOUT when the gem is used outside of Rails.
|
94
|
+
|
95
|
+
To override the default behavior and control the destination of log messages, provide
|
96
|
+
a ruby Logger object to the gem's logging singleton:
|
97
|
+
|
98
|
+
```ruby
|
99
|
+
OneLogin::RubySaml::Logging.logger = Logger.new(File.open('/var/log/ruby-saml.log', 'w')
|
100
|
+
```
|
101
|
+
|
77
102
|
## The Initialization Phase
|
78
103
|
|
79
104
|
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):
|
@@ -89,13 +114,12 @@ Once you've redirected back to the identity provider, it will ensure that the us
|
|
89
114
|
|
90
115
|
```ruby
|
91
116
|
def consume
|
92
|
-
response
|
93
|
-
response.settings = saml_settings
|
117
|
+
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], :settings => saml_settings)
|
94
118
|
|
95
119
|
# We validate the SAML Response and check if the user already exists in the system
|
96
120
|
if response.is_valid?
|
97
121
|
# authorize_success, log the user
|
98
|
-
session[:userid] = response.
|
122
|
+
session[:userid] = response.nameid
|
99
123
|
session[:attributes] = response.attributes
|
100
124
|
else
|
101
125
|
authorize_failure # This method shows an error message
|
@@ -103,14 +127,24 @@ def consume
|
|
103
127
|
end
|
104
128
|
```
|
105
129
|
|
106
|
-
In the above there are a few assumptions in place, one being that the response.
|
130
|
+
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:
|
131
|
+
|
132
|
+
If the assertion of the SAMLResponse is not encrypted, you can initialize the Response without the :settings parameter and set it later,
|
133
|
+
|
134
|
+
```
|
135
|
+
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse])
|
136
|
+
response.settings = saml_settings
|
137
|
+
```
|
138
|
+
but if the SAMLResponse contains an encrypted assertion, you need to provide the settings in the
|
139
|
+
initialize method in order to be able to obtain the decrypted assertion, using the service provider private key in order to decrypt.
|
140
|
+
If you don't know what expect, use always the first proposed way (always set the settings on the initialize method).
|
107
141
|
|
108
142
|
```ruby
|
109
143
|
def saml_settings
|
110
144
|
settings = OneLogin::RubySaml::Settings.new
|
111
145
|
|
112
|
-
settings.assertion_consumer_service_url = "http://#{request.host}/saml/
|
113
|
-
settings.issuer = request.host
|
146
|
+
settings.assertion_consumer_service_url = "http://#{request.host}/saml/consume"
|
147
|
+
settings.issuer = "http://#{request.host}/saml/metadata"
|
114
148
|
settings.idp_sso_target_url = "https://app.onelogin.com/saml/metadata/#{OneLoginAppId}"
|
115
149
|
settings.idp_entity_id = "https://app.onelogin.com/saml/metadata/#{OneLoginAppId}"
|
116
150
|
settings.idp_sso_target_url = "https://app.onelogin.com/trust/saml2/http-post/sso/#{OneLoginAppId}"
|
@@ -124,7 +158,7 @@ def saml_settings
|
|
124
158
|
|
125
159
|
# Optional bindings (defaults to Redirect for logout POST for acs)
|
126
160
|
settings.assertion_consumer_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
|
127
|
-
settings.
|
161
|
+
settings.assertion_consumer_logout_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
|
128
162
|
|
129
163
|
settings
|
130
164
|
end
|
@@ -147,7 +181,7 @@ class SamlController < ApplicationController
|
|
147
181
|
# We validate the SAML Response and check if the user already exists in the system
|
148
182
|
if response.is_valid?
|
149
183
|
# authorize_success, log the user
|
150
|
-
session[:userid] = response.
|
184
|
+
session[:userid] = response.nameid
|
151
185
|
session[:attributes] = response.attributes
|
152
186
|
else
|
153
187
|
authorize_failure # This method shows an error message
|
@@ -160,7 +194,7 @@ class SamlController < ApplicationController
|
|
160
194
|
settings = OneLogin::RubySaml::Settings.new
|
161
195
|
|
162
196
|
settings.assertion_consumer_service_url = "http://#{request.host}/saml/consume"
|
163
|
-
settings.issuer = request.host
|
197
|
+
settings.issuer = "http://#{request.host}/saml/metadata"
|
164
198
|
settings.idp_sso_target_url = "https://app.onelogin.com/saml/signon/#{OneLoginAppId}"
|
165
199
|
settings.idp_cert_fingerprint = OneLoginAppCertFingerPrint
|
166
200
|
settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
|
@@ -196,7 +230,7 @@ def saml_settings
|
|
196
230
|
settings = idp_metadata_parser.parse_remote("https://example.com/auth/saml2/idp/metadata")
|
197
231
|
|
198
232
|
settings.assertion_consumer_service_url = "http://#{request.host}/saml/consume"
|
199
|
-
settings.issuer = request.host
|
233
|
+
settings.issuer = "http://#{request.host}/saml/metadata"
|
200
234
|
settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
|
201
235
|
# Optional for most SAML IdPs
|
202
236
|
settings.authn_context = "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport"
|
@@ -330,8 +364,8 @@ The Ruby Toolkit supports 2 different kinds of signature: Embeded and as GET par
|
|
330
364
|
In order to be able to sign we need first to define the private key and the public cert of the service provider
|
331
365
|
|
332
366
|
```ruby
|
333
|
-
settings.certificate = "CERTIFICATE TEXT WITH
|
334
|
-
settings.private_key = "PRIVATE KEY TEXT WITH
|
367
|
+
settings.certificate = "CERTIFICATE TEXT WITH HEAD AND FOOT"
|
368
|
+
settings.private_key = "PRIVATE KEY TEXT WITH HEAD AND FOOT"
|
335
369
|
```
|
336
370
|
|
337
371
|
The settings related to sign are stored in the `security` attribute of the settings:
|
@@ -354,6 +388,28 @@ Notice that the RelayState parameter is used when creating the Signature on the
|
|
354
388
|
remember to provide it to the Signature builder if you are sending a GET RelayState parameter or
|
355
389
|
Signature validation process will fail at the Identity Provider.
|
356
390
|
|
391
|
+
The Service Provider will sign the request/responses with its private key.
|
392
|
+
The Identity Provider will validate the sign of the received request/responses with the public x500 cert of the
|
393
|
+
Service Provider.
|
394
|
+
|
395
|
+
Notice that this toolkit uses 'settings.certificate' and 'settings.private_key' for the sign and the decrypt process.
|
396
|
+
|
397
|
+
|
398
|
+
## Decrypting
|
399
|
+
|
400
|
+
The Ruby Toolkit supports EncryptedAssertion.
|
401
|
+
|
402
|
+
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.
|
403
|
+
|
404
|
+
```ruby
|
405
|
+
settings.certificate = "CERTIFICATE TEXT WITH HEAD AND FOOT"
|
406
|
+
settings.private_key = "PRIVATE KEY TEXT WITH HEAD AND FOOT"
|
407
|
+
```
|
408
|
+
|
409
|
+
The Identity Provider will encrypt the Assertion with the public cert of the Service Provider.
|
410
|
+
The Service Provider will decrypt the EncryptedAssertion with its private key.
|
411
|
+
|
412
|
+
Notice that this toolkit uses 'settings.certificate' and 'settings.private_key' for the sign and the decrypt process.
|
357
413
|
|
358
414
|
## Single Log Out
|
359
415
|
|
data/changelog.md
CHANGED
@@ -1,10 +1,20 @@
|
|
1
1
|
# RubySaml Changelog
|
2
2
|
|
3
|
-
### 0.
|
4
|
-
*
|
5
|
-
|
6
|
-
|
7
|
-
*
|
3
|
+
### 1.0.0 (June 30, 2015)
|
4
|
+
* [#247](https://github.com/onelogin/ruby-saml/pull/247) Avoid entity expansion (XEE attacks)
|
5
|
+
* [#246](https://github.com/onelogin/ruby-saml/pull/246) Fix bug generating Logout Response (issuer was at wrong order)
|
6
|
+
* [#243](https://github.com/onelogin/ruby-saml/issues/243) and [#244](https://github.com/onelogin/ruby-saml/issues/244) Fix metadata builder errors. Fix metadata xsd.
|
7
|
+
* [#241](https://github.com/onelogin/ruby-saml/pull/241) Add decrypt support (EncryptID and EncryptedAssertion). Improve compatibility with namespaces.
|
8
|
+
* [#240](https://github.com/onelogin/ruby-saml/pull/240) and [#238](https://github.com/onelogin/ruby-saml/pull/238) Improve test coverage and refactor.
|
9
|
+
* [#239](https://github.com/onelogin/ruby-saml/pull/239) Improve security: Add more validations to SAMLResponse, LogoutRequest and LogoutResponse. Refactor code and improve tests coverage.
|
10
|
+
* [#237](https://github.com/onelogin/ruby-saml/pull/237) Don't pretty print metadata by default.
|
11
|
+
* [#235](https://github.com/onelogin/ruby-saml/pull/235) Remove the soft parameter from validation methods. Now can be configured on the settings and each class read it and store as an attribute of the class. Adding some validations and refactor old ones.
|
12
|
+
* [#232](https://github.com/onelogin/ruby-saml/pull/232) Improve validations: Store the causes in the errors array, code refactor
|
13
|
+
* [#231](https://github.com/onelogin/ruby-saml/pull/231) Refactor HTTP-Redirect Sign method, Move test data to right folder
|
14
|
+
* [#226](https://github.com/onelogin/ruby-saml/pull/226) Ensure IdP certificate is formatted properly
|
15
|
+
* [#225](https://github.com/onelogin/ruby-saml/pull/225) Add documentation to several methods. Fix xpath injection on xml_security.rb
|
16
|
+
* [#223](https://github.com/onelogin/ruby-saml/pull/223) Allow logging to be delegated to an arbitrary Logger
|
17
|
+
* [#222](https://github.com/onelogin/ruby-saml/pull/222) No more silent failure fetching idp metadata (OneLogin::RubySaml::HttpError raised).
|
8
18
|
|
9
19
|
### 0.9.2 (Apr 28, 2015)
|
10
20
|
* [#216](https://github.com/onelogin/ruby-saml/pull/216) Add fingerprint algorithm support
|
@@ -22,7 +32,6 @@
|
|
22
32
|
* [#179](https://github.com/onelogin/ruby-saml/pull/179) Add support for setting the entity ID and name ID format when parsing metadata
|
23
33
|
* [#175](https://github.com/onelogin/ruby-saml/pull/175) Introduce thread safety to SAML schema validation
|
24
34
|
* [#171](https://github.com/onelogin/ruby-saml/pull/171) Fix inconsistent results with using regex matches in decode_raw_saml
|
25
|
-
*
|
26
35
|
|
27
36
|
### 0.9.1 (Feb 10, 2015)
|
28
37
|
* [#194](https://github.com/onelogin/ruby-saml/pull/194) Relax nokogiri gem requirements
|
data/lib/onelogin/ruby-saml.rb
CHANGED
@@ -9,6 +9,7 @@ require 'onelogin/ruby-saml/slo_logoutresponse'
|
|
9
9
|
require 'onelogin/ruby-saml/response'
|
10
10
|
require 'onelogin/ruby-saml/settings'
|
11
11
|
require 'onelogin/ruby-saml/attribute_service'
|
12
|
+
require 'onelogin/ruby-saml/http_error'
|
12
13
|
require 'onelogin/ruby-saml/validation_error'
|
13
14
|
require 'onelogin/ruby-saml/metadata'
|
14
15
|
require 'onelogin/ruby-saml/idp_metadata_parser'
|
@@ -1,10 +1,15 @@
|
|
1
1
|
module OneLogin
|
2
2
|
module RubySaml
|
3
|
+
|
4
|
+
# SAML2 AttributeService. Auxiliary class to build the AttributeService of the SP Metadata
|
5
|
+
#
|
3
6
|
class AttributeService
|
4
7
|
attr_reader :attributes
|
5
8
|
attr_reader :name
|
6
9
|
attr_reader :index
|
7
10
|
|
11
|
+
# Initializes the AttributeService, set the index value as 1 and an empty array as attributes
|
12
|
+
#
|
8
13
|
def initialize
|
9
14
|
@index = "1"
|
10
15
|
@attributes = []
|
@@ -14,20 +19,38 @@ module OneLogin
|
|
14
19
|
instance_eval &block
|
15
20
|
end
|
16
21
|
|
22
|
+
# @return [Boolean] True if the AttributeService object has been initialized and set with the required values
|
23
|
+
# (has attributes and a name)
|
17
24
|
def configured?
|
18
25
|
@attributes.length > 0 && !@name.nil?
|
19
26
|
end
|
20
27
|
|
28
|
+
# Set a name to the service
|
29
|
+
# @param name [String] The service name
|
30
|
+
#
|
21
31
|
def service_name(name)
|
22
32
|
@name = name
|
23
33
|
end
|
24
34
|
|
35
|
+
# Set an index to the service
|
36
|
+
# @param index [Integer] An index
|
37
|
+
#
|
25
38
|
def service_index(index)
|
26
39
|
@index = index
|
27
40
|
end
|
28
|
-
|
41
|
+
|
42
|
+
# Add an AttributeService
|
43
|
+
# @param options [Hash] AttributeService option values
|
44
|
+
# add_attribute(
|
45
|
+
# :name => "Name",
|
46
|
+
# :name_format => "Name Format",
|
47
|
+
# :index => 1,
|
48
|
+
# :friendly_name => "Friendly Name",
|
49
|
+
# :attribute_value => "Attribute Value"
|
50
|
+
# )
|
51
|
+
#
|
29
52
|
def add_attribute(options={})
|
30
|
-
attributes << options
|
53
|
+
attributes << options
|
31
54
|
end
|
32
55
|
end
|
33
56
|
end
|
@@ -1,91 +1,110 @@
|
|
1
1
|
module OneLogin
|
2
2
|
module RubySaml
|
3
|
-
|
4
|
-
#
|
5
|
-
#
|
6
|
-
# Turn off compatibility to make it return all values as an array:
|
7
|
-
# Attributes.single_value_compatibility = false
|
3
|
+
|
4
|
+
# SAML2 Attributes. Parse the Attributes from the AttributeStatement of the SAML Response.
|
5
|
+
#
|
8
6
|
class Attributes
|
9
7
|
include Enumerable
|
10
8
|
|
9
|
+
attr_reader :attributes
|
10
|
+
|
11
11
|
# By default Attributes#[] is backwards compatible and
|
12
12
|
# returns only the first value for the attribute
|
13
13
|
# Setting this to `false` returns all values for an attribute
|
14
14
|
@@single_value_compatibility = true
|
15
15
|
|
16
|
-
# Get current status of backwards compatibility mode.
|
16
|
+
# @return [Boolean] Get current status of backwards compatibility mode.
|
17
|
+
#
|
17
18
|
def self.single_value_compatibility
|
18
19
|
@@single_value_compatibility
|
19
20
|
end
|
20
21
|
|
21
22
|
# Sets the backwards compatibility mode on/off.
|
23
|
+
# @param value [Boolean]
|
24
|
+
#
|
22
25
|
def self.single_value_compatibility=(value)
|
23
26
|
@@single_value_compatibility = value
|
24
27
|
end
|
25
28
|
|
26
|
-
#
|
27
|
-
#
|
28
|
-
# The +attrs+ must be a Hash with attribute names as keys and **arrays** as values:
|
29
|
+
# @param attrs [Hash] The +attrs+ must be a Hash with attribute names as keys and **arrays** as values:
|
29
30
|
# Attributes.new({
|
30
31
|
# 'name' => ['value1', 'value2'],
|
31
32
|
# 'mail' => ['value1'],
|
32
33
|
# })
|
34
|
+
#
|
33
35
|
def initialize(attrs = {})
|
34
36
|
@attributes = attrs
|
35
37
|
end
|
36
38
|
|
37
39
|
|
38
40
|
# Iterate over all attributes
|
41
|
+
#
|
39
42
|
def each
|
40
43
|
attributes.each{|name, values| yield name, values}
|
41
44
|
end
|
42
45
|
|
46
|
+
|
43
47
|
# Test attribute presence by name
|
48
|
+
# @param name [String] The attribute name to be checked
|
49
|
+
#
|
44
50
|
def include?(name)
|
45
51
|
attributes.has_key?(canonize_name(name))
|
46
52
|
end
|
47
53
|
|
48
54
|
# Return first value for an attribute
|
55
|
+
# @param name [String] The attribute name
|
56
|
+
# @return [String] The value (First occurrence)
|
57
|
+
#
|
49
58
|
def single(name)
|
50
59
|
attributes[canonize_name(name)].first if include?(name)
|
51
60
|
end
|
52
61
|
|
53
62
|
# Return all values for an attribute
|
63
|
+
# @param name [String] The attribute name
|
64
|
+
# @return [Array] Values of the attribute
|
65
|
+
#
|
54
66
|
def multi(name)
|
55
67
|
attributes[canonize_name(name)]
|
56
68
|
end
|
57
69
|
|
58
|
-
#
|
59
|
-
#
|
60
|
-
# Depending on the single value compatibility status this returns
|
61
|
-
#
|
62
|
-
#
|
70
|
+
# Retrieve attribute value(s)
|
71
|
+
# @param name [String] The attribute name
|
72
|
+
# @return [String|Array] Depending on the single value compatibility status this returns:
|
73
|
+
# - First value if single_value_compatibility = true
|
74
|
+
# response.attributes['mail'] # => 'user@example.com'
|
75
|
+
# - All values if single_value_compatibility = false
|
76
|
+
# response.attributes['mail'] # => ['user@example.com','user@example.net']
|
63
77
|
#
|
64
|
-
# Or all values:
|
65
|
-
# Attributes.single_value_compatibility = false
|
66
|
-
# response.attributes['mail'] # => ['user@example.com','user@example.net']
|
67
78
|
def [](name)
|
68
79
|
self.class.single_value_compatibility ? single(canonize_name(name)) : multi(canonize_name(name))
|
69
80
|
end
|
70
81
|
|
71
|
-
# Return all attributes as an array
|
82
|
+
# @return [Array] Return all attributes as an array
|
83
|
+
#
|
72
84
|
def all
|
73
85
|
attributes
|
74
86
|
end
|
75
87
|
|
76
|
-
#
|
88
|
+
# @param name [String] The attribute name
|
89
|
+
# @param values [Array] The values
|
90
|
+
#
|
77
91
|
def set(name, values)
|
78
92
|
attributes[canonize_name(name)] = values
|
79
93
|
end
|
80
94
|
alias_method :[]=, :set
|
81
95
|
|
82
|
-
#
|
96
|
+
# @param name [String] The attribute name
|
97
|
+
# @param values [Array] The values
|
98
|
+
#
|
83
99
|
def add(name, values = [])
|
84
100
|
attributes[canonize_name(name)] ||= []
|
85
101
|
attributes[canonize_name(name)] += Array(values)
|
86
102
|
end
|
87
103
|
|
88
104
|
# Make comparable to another Attributes collection based on attributes
|
105
|
+
# @param other [Attributes] An Attributes object to compare with
|
106
|
+
# @return [Boolean] True if are contains the same attributes and values
|
107
|
+
#
|
89
108
|
def ==(other)
|
90
109
|
if other.is_a?(Attributes)
|
91
110
|
all == other.all
|
@@ -97,13 +116,13 @@ module OneLogin
|
|
97
116
|
protected
|
98
117
|
|
99
118
|
# stringifies all names so both 'email' and :email return the same result
|
119
|
+
# @param name [String] The attribute name
|
120
|
+
# @return [String] stringified name
|
121
|
+
#
|
100
122
|
def canonize_name(name)
|
101
123
|
name.to_s
|
102
124
|
end
|
103
125
|
|
104
|
-
def attributes
|
105
|
-
@attributes
|
106
|
-
end
|
107
126
|
end
|
108
127
|
end
|
109
128
|
end
|
@@ -4,17 +4,30 @@ require "rexml/document"
|
|
4
4
|
require "onelogin/ruby-saml/logging"
|
5
5
|
require "onelogin/ruby-saml/saml_message"
|
6
6
|
|
7
|
+
# Only supports SAML 2.0
|
7
8
|
module OneLogin
|
8
9
|
module RubySaml
|
9
10
|
include REXML
|
11
|
+
|
12
|
+
# SAML2 Authentication. AuthNRequest (SSO SP initiated, Builder)
|
13
|
+
#
|
10
14
|
class Authrequest < SamlMessage
|
11
15
|
|
12
|
-
|
16
|
+
# AuthNRequest ID
|
17
|
+
attr_reader :uuid
|
13
18
|
|
19
|
+
# Initializes the AuthNRequest. An Authrequest Object that is an extension of the SamlMessage class.
|
20
|
+
# Asigns an ID, a random uuid.
|
21
|
+
#
|
14
22
|
def initialize
|
15
23
|
@uuid = "_" + UUID.new.generate
|
16
24
|
end
|
17
25
|
|
26
|
+
# Creates the AuthNRequest string.
|
27
|
+
# @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
|
28
|
+
# @param params [Hash] Some extra parameters to be added in the GET for example the RelayState
|
29
|
+
# @return [String] AuthNRequest string that includes the SAMLRequest
|
30
|
+
#
|
18
31
|
def create(settings, params = {})
|
19
32
|
params = create_params(settings, params)
|
20
33
|
params_prefix = (settings.idp_sso_target_url =~ /\?/) ? '&' : '?'
|
@@ -26,6 +39,11 @@ module OneLogin
|
|
26
39
|
@login_url = settings.idp_sso_target_url + request_params
|
27
40
|
end
|
28
41
|
|
42
|
+
# Creates the Get parameters for the request.
|
43
|
+
# @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
|
44
|
+
# @param params [Hash] Some extra parameters to be added in the GET for example the RelayState
|
45
|
+
# @return [Hash] Parameters
|
46
|
+
#
|
29
47
|
def create_params(settings, params={})
|
30
48
|
# The method expects :RelayState but sometimes we get 'RelayState' instead.
|
31
49
|
# Based on the HashWithIndifferentAccess value in Rails we could experience
|
@@ -46,11 +64,14 @@ module OneLogin
|
|
46
64
|
|
47
65
|
if settings.security[:authn_requests_signed] && !settings.security[:embed_sign] && settings.private_key
|
48
66
|
params['SigAlg'] = settings.security[:signature_method]
|
49
|
-
url_string
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
67
|
+
url_string = OneLogin::RubySaml::Utils.build_query(
|
68
|
+
:type => 'SAMLRequest',
|
69
|
+
:data => base64_request,
|
70
|
+
:relay_state => relay_state,
|
71
|
+
:sig_alg => params['SigAlg']
|
72
|
+
)
|
73
|
+
sign_algorithm = XMLSecurity::BaseDocument.new.algorithm(settings.security[:signature_method])
|
74
|
+
signature = settings.get_sp_key.sign(sign_algorithm.new, url_string)
|
54
75
|
params['Signature'] = encode(signature)
|
55
76
|
end
|
56
77
|
|
@@ -61,6 +82,10 @@ module OneLogin
|
|
61
82
|
request_params
|
62
83
|
end
|
63
84
|
|
85
|
+
# Creates the SAMLRequest String.
|
86
|
+
# @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
|
87
|
+
# @return [String] The SAMLRequest String.
|
88
|
+
#
|
64
89
|
def create_authentication_xml_doc(settings)
|
65
90
|
time = Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
|
66
91
|
|
@@ -118,8 +143,8 @@ module OneLogin
|
|
118
143
|
|
119
144
|
# embed signature
|
120
145
|
if settings.security[:authn_requests_signed] && settings.private_key && settings.certificate && settings.security[:embed_sign]
|
121
|
-
private_key = settings.get_sp_key
|
122
|
-
cert = settings.get_sp_cert
|
146
|
+
private_key = settings.get_sp_key
|
147
|
+
cert = settings.get_sp_cert
|
123
148
|
request_doc.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method])
|
124
149
|
end
|
125
150
|
|