ruby-saml 1.1.2 → 1.2.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 +7 -0
- data/README.md +15 -6
- data/changelog.md +15 -1
- data/lib/onelogin/ruby-saml/authrequest.rb +2 -2
- data/lib/onelogin/ruby-saml/error_handling.rb +27 -0
- data/lib/onelogin/ruby-saml/idp_metadata_parser.rb +80 -16
- data/lib/onelogin/ruby-saml/logoutrequest.rb +3 -4
- data/lib/onelogin/ruby-saml/logoutresponse.rb +20 -22
- data/lib/onelogin/ruby-saml/metadata.rb +3 -4
- data/lib/onelogin/ruby-saml/response.rb +79 -50
- data/lib/onelogin/ruby-saml/saml_message.rb +3 -10
- data/lib/onelogin/ruby-saml/settings.rb +2 -0
- data/lib/onelogin/ruby-saml/slo_logoutrequest.rb +32 -32
- data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +3 -3
- data/lib/onelogin/ruby-saml/utils.rb +25 -9
- data/lib/onelogin/ruby-saml/version.rb +1 -1
- data/lib/xml_security.rb +29 -27
- data/ruby-saml.gemspec +4 -1
- data/test/idp_metadata_parser_test.rb +28 -0
- data/test/logoutrequest_test.rb +2 -1
- data/test/logoutresponse_test.rb +9 -0
- data/test/metadata_test.rb +14 -0
- data/test/response_test.rb +70 -6
- data/test/responses/idp_descriptor.xml +1 -1
- data/test/responses/response_with_retrieval_method.xml +26 -0
- data/test/responses/response_without_reference_uri.xml.base64 +1 -1
- data/test/settings_test.rb +1 -1
- data/test/slo_logoutrequest_test.rb +29 -2
- data/test/test_helper.rb +1 -1
- data/test/utils_test.rb +14 -1
- data/test/xml_security_test.rb +2 -0
- metadata +133 -177
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 162de30b9475ee4bc219f26c544304d051ab5c34
|
4
|
+
data.tar.gz: 685afabca84ac713ab43a2c848cfdb20dd92f579
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e5a3f645b2cf71452e7f22a2bbc853f393978516e1abc185b3cbbfe4aa19403b3b3760c839d72f81fd36128241e09c58dff8c84c5b1b2dc1b0177a85b235cf81
|
7
|
+
data.tar.gz: 7ef80e8936bda82946041ebc220002b7d7a745819a2a419f190c6a0980449cd00516338df8879dcb67b13dd882ba155a665ed31154113fc2826112629fbbedcc
|
data/README.md
CHANGED
@@ -1,12 +1,17 @@
|
|
1
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
2
|
|
3
|
+
## Updating from 1.1.x to 1.2.X
|
3
4
|
|
4
|
-
|
5
|
+
Version `1.2` adds IDP metadata parsing improvements, uuid deprecation in favour of SecureRandom, refactor error handling and some minor improvements
|
5
6
|
|
6
|
-
|
7
|
+
There is no compatibility issue detected.
|
7
8
|
|
8
9
|
For more details, please review [the changelog](changelog.md).
|
9
10
|
|
11
|
+
## Updating from 1.0.x to 1.1.X
|
12
|
+
|
13
|
+
Version `1.1` adds some improvements on signature validation and solves some namespace conflicts.
|
14
|
+
|
10
15
|
## Updating from 0.9.x to 1.0.X
|
11
16
|
|
12
17
|
Version `1.0` is a recommended update for all Ruby SAML users as it includes security fixes.
|
@@ -33,6 +38,7 @@ We created a demo project for Rails4 that uses the latest version of this librar
|
|
33
38
|
### Supported versions of Ruby
|
34
39
|
* 1.8.7
|
35
40
|
* 1.9.x
|
41
|
+
* 2.0.x
|
36
42
|
* 2.1.x
|
37
43
|
* 2.2.x
|
38
44
|
* JRuby 1.7.19
|
@@ -254,7 +260,7 @@ end
|
|
254
260
|
The following attributes are set:
|
255
261
|
* idp_sso_target_url
|
256
262
|
* idp_slo_target_url
|
257
|
-
*
|
263
|
+
* idp_cert_fingerprint
|
258
264
|
|
259
265
|
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
|
260
266
|
`single_value_compatibility` (when activate, only one value returned, the first one)
|
@@ -386,7 +392,10 @@ The settings related to sign are stored in the `security` attribute of the setti
|
|
386
392
|
```ruby
|
387
393
|
settings.security[:authn_requests_signed] = true # Enable or not signature on AuthNRequest
|
388
394
|
settings.security[:logout_requests_signed] = true # Enable or not signature on Logout Request
|
389
|
-
settings.security[:logout_responses_signed] = true # Enable or not
|
395
|
+
settings.security[:logout_responses_signed] = true # Enable or not
|
396
|
+
signature on Logout Response
|
397
|
+
settings.security[:want_assertions_signed] = true # Enable or not
|
398
|
+
the requirement of signed assertion
|
390
399
|
settings.security[:metadata_signed] = true # Enable or not signature on Metadata
|
391
400
|
|
392
401
|
settings.security[:digest_method] = XMLSecurity::Document::SHA1
|
@@ -466,8 +475,8 @@ and this method process the SAML Logout Response sent by the IdP as reply of the
|
|
466
475
|
def process_logout_response
|
467
476
|
settings = Account.get_saml_settings
|
468
477
|
|
469
|
-
if session.has_key? :
|
470
|
-
logout_response = OneLogin::RubySaml::Logoutresponse.new(params[:SAMLResponse], settings, :matches_request_id => session[:
|
478
|
+
if session.has_key? :transaction_id
|
479
|
+
logout_response = OneLogin::RubySaml::Logoutresponse.new(params[:SAMLResponse], settings, :matches_request_id => session[:transaction_id])
|
471
480
|
else
|
472
481
|
logout_response = OneLogin::RubySaml::Logoutresponse.new(params[:SAMLResponse], settings)
|
473
482
|
end
|
data/changelog.md
CHANGED
@@ -1,6 +1,20 @@
|
|
1
1
|
# RubySaml Changelog
|
2
2
|
|
3
|
-
### 1.
|
3
|
+
### 1.2.0 (April 29, 2016)
|
4
|
+
* [#269](https://github.com/onelogin/ruby-saml/pull/269) Refactor error handling; allow collect error messages when soft=true (normal validation stop after find first error)
|
5
|
+
* [#289](https://github.com/onelogin/ruby-saml/pull/289) Remove uuid gem in favor of SecureRandom
|
6
|
+
* [#297](https://github.com/onelogin/ruby-saml/pull/297) Implement EncryptedKey RetrievalMethod support
|
7
|
+
* [#298](https://github.com/onelogin/ruby-saml/pull/298) IDP metadata parsing improved: binding parsing, fingerprint_algorithm support)
|
8
|
+
* [#299](https://github.com/onelogin/ruby-saml/pull/299) Make 'signing' at KeyDescriptor optional
|
9
|
+
* [#308](https://github.com/onelogin/ruby-saml/pull/308) Support name_id_format on SAMLResponse
|
10
|
+
* [#315](https://github.com/onelogin/ruby-saml/pull/315) Support for canonicalization with comments
|
11
|
+
* [#316](https://github.com/onelogin/ruby-saml/pull/316) Fix Misspelling of transation_id to transaction_id
|
12
|
+
* [#321](https://github.com/onelogin/ruby-saml/pull/321) Support Attribute Names on IDPSSODescriptor parser
|
13
|
+
* Changes on empty URI of Signature reference management
|
14
|
+
* [#320](https://github.com/onelogin/ruby-saml/pull/320) Dont mutate document to fix lack of reference URI
|
15
|
+
* [#306](https://github.com/onelogin/ruby-saml/pull/306) Support WantAssertionsSigned
|
16
|
+
|
17
|
+
### 1.1.2 (February 15, 2016)
|
4
18
|
* Improve signature validation. Add tests.
|
5
19
|
[#302](https://github.com/onelogin/ruby-saml/pull/302) Add Destination validation.
|
6
20
|
* [#292](https://github.com/onelogin/ruby-saml/pull/292) Improve the error message when validating the audience.
|
@@ -1,8 +1,8 @@
|
|
1
|
-
require "uuid"
|
2
1
|
require "rexml/document"
|
3
2
|
|
4
3
|
require "onelogin/ruby-saml/logging"
|
5
4
|
require "onelogin/ruby-saml/saml_message"
|
5
|
+
require "onelogin/ruby-saml/utils"
|
6
6
|
|
7
7
|
# Only supports SAML 2.0
|
8
8
|
module OneLogin
|
@@ -20,7 +20,7 @@ module OneLogin
|
|
20
20
|
# Asigns an ID, a random uuid.
|
21
21
|
#
|
22
22
|
def initialize
|
23
|
-
@uuid =
|
23
|
+
@uuid = OneLogin::RubySaml::Utils.uuid
|
24
24
|
end
|
25
25
|
|
26
26
|
# Creates the AuthNRequest string.
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require "onelogin/ruby-saml/validation_error"
|
2
|
+
|
3
|
+
module OneLogin
|
4
|
+
module RubySaml
|
5
|
+
module ErrorHandling
|
6
|
+
attr_accessor :errors
|
7
|
+
|
8
|
+
# Append the cause to the errors array, and based on the value of soft, return false or raise
|
9
|
+
# an exception. soft_override is provided as a means of overriding the object's notion of
|
10
|
+
# soft for just this invocation.
|
11
|
+
def append_error(error_msg, soft_override = nil)
|
12
|
+
@errors << error_msg
|
13
|
+
|
14
|
+
unless soft_override.nil? ? soft : soft_override
|
15
|
+
raise ValidationError.new(error_msg)
|
16
|
+
end
|
17
|
+
|
18
|
+
false
|
19
|
+
end
|
20
|
+
|
21
|
+
# Reset the errors array
|
22
|
+
def reset_errors!
|
23
|
+
@errors = []
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -1,5 +1,4 @@
|
|
1
1
|
require "base64"
|
2
|
-
require "uuid"
|
3
2
|
require "zlib"
|
4
3
|
require "cgi"
|
5
4
|
require "net/http"
|
@@ -16,8 +15,10 @@ module OneLogin
|
|
16
15
|
#
|
17
16
|
class IdpMetadataParser
|
18
17
|
|
19
|
-
METADATA
|
20
|
-
DSIG
|
18
|
+
METADATA = "urn:oasis:names:tc:SAML:2.0:metadata"
|
19
|
+
DSIG = "http://www.w3.org/2000/09/xmldsig#"
|
20
|
+
NAME_FORMAT = "urn:oasis:names:tc:SAML:2.0:attrname-format:*"
|
21
|
+
SAML_ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion"
|
21
22
|
|
22
23
|
attr_reader :document
|
23
24
|
attr_reader :response
|
@@ -26,26 +27,30 @@ module OneLogin
|
|
26
27
|
# IdP values
|
27
28
|
#
|
28
29
|
# @param (see IdpMetadataParser#get_idp_metadata)
|
30
|
+
# @param options [Hash] :settings to provide the OneLogin::RubySaml::Settings object
|
29
31
|
# @return (see IdpMetadataParser#get_idp_metadata)
|
30
32
|
# @raise (see IdpMetadataParser#get_idp_metadata)
|
31
|
-
def parse_remote(url, validate_cert = true)
|
33
|
+
def parse_remote(url, validate_cert = true, options = {})
|
32
34
|
idp_metadata = get_idp_metadata(url, validate_cert)
|
33
|
-
parse(idp_metadata)
|
35
|
+
parse(idp_metadata, options)
|
34
36
|
end
|
35
37
|
|
36
38
|
# Parse the Identity Provider metadata and update the settings with the IdP values
|
37
39
|
# @param idp_metadata [String]
|
40
|
+
# @param options [Hash] :settings to provide the OneLogin::RubySaml::Settings object
|
38
41
|
#
|
39
|
-
def parse(idp_metadata)
|
42
|
+
def parse(idp_metadata, options = {})
|
40
43
|
@document = REXML::Document.new(idp_metadata)
|
41
44
|
|
42
|
-
OneLogin::RubySaml::Settings.new.tap do |settings|
|
45
|
+
(options[:settings] || OneLogin::RubySaml::Settings.new).tap do |settings|
|
43
46
|
settings.idp_entity_id = idp_entity_id
|
44
47
|
settings.name_identifier_format = idp_name_id_format
|
45
|
-
settings.idp_sso_target_url = single_signon_service_url
|
46
|
-
settings.idp_slo_target_url = single_logout_service_url
|
48
|
+
settings.idp_sso_target_url = single_signon_service_url(options)
|
49
|
+
settings.idp_slo_target_url = single_logout_service_url(options)
|
47
50
|
settings.idp_cert = certificate_base64
|
48
|
-
settings.idp_cert_fingerprint = fingerprint
|
51
|
+
settings.idp_cert_fingerprint = fingerprint(settings.idp_cert_fingerprint_algorithm)
|
52
|
+
settings.idp_attribute_names = attribute_names
|
53
|
+
settings.idp_cert_fingerprint = fingerprint(settings.idp_cert_fingerprint_algorithm)
|
49
54
|
end
|
50
55
|
end
|
51
56
|
|
@@ -112,23 +117,61 @@ module OneLogin
|
|
112
117
|
node.text if node
|
113
118
|
end
|
114
119
|
|
120
|
+
# @param binding_priority [Array]
|
121
|
+
# @return [String|nil] SingleSignOnService binding if exists
|
122
|
+
#
|
123
|
+
def single_signon_service_binding(binding_priority = nil)
|
124
|
+
nodes = REXML::XPath.match(
|
125
|
+
document,
|
126
|
+
"/md:EntityDescriptor/md:IDPSSODescriptor/md:SingleSignOnService/@Binding",
|
127
|
+
{ "md" => METADATA }
|
128
|
+
)
|
129
|
+
if binding_priority
|
130
|
+
values = nodes.map(&:value)
|
131
|
+
binding_priority.detect{ |binding| values.include? binding }
|
132
|
+
else
|
133
|
+
nodes.first.value if nodes.any?
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# @param options [Hash]
|
115
138
|
# @return [String|nil] SingleSignOnService endpoint if exists
|
116
139
|
#
|
117
|
-
def single_signon_service_url
|
140
|
+
def single_signon_service_url(options = {})
|
141
|
+
binding = options[:sso_binding] || "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
|
118
142
|
node = REXML::XPath.first(
|
119
143
|
document,
|
120
|
-
"/md:EntityDescriptor/md:IDPSSODescriptor/md:SingleSignOnService/@Location",
|
144
|
+
"/md:EntityDescriptor/md:IDPSSODescriptor/md:SingleSignOnService[@Binding=\"#{binding}\"]/@Location",
|
121
145
|
{ "md" => METADATA }
|
122
146
|
)
|
123
147
|
node.value if node
|
124
148
|
end
|
125
149
|
|
150
|
+
# @param binding_priority [Array]
|
151
|
+
# @return [String|nil] SingleLogoutService binding if exists
|
152
|
+
#
|
153
|
+
def single_logout_service_binding(binding_priority = nil)
|
154
|
+
nodes = REXML::XPath.match(
|
155
|
+
document,
|
156
|
+
"/md:EntityDescriptor/md:IDPSSODescriptor/md:SingleLogoutService/@Binding",
|
157
|
+
{ "md" => METADATA }
|
158
|
+
)
|
159
|
+
if binding_priority
|
160
|
+
values = nodes.map(&:value)
|
161
|
+
binding_priority.detect{ |binding| values.include? binding }
|
162
|
+
else
|
163
|
+
nodes.first.value if nodes.any?
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
# @param options [Hash]
|
126
168
|
# @return [String|nil] SingleLogoutService endpoint if exists
|
127
169
|
#
|
128
|
-
def single_logout_service_url
|
170
|
+
def single_logout_service_url(options = {})
|
171
|
+
binding = options[:slo_binding] || "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
|
129
172
|
node = REXML::XPath.first(
|
130
173
|
document,
|
131
|
-
"/md:EntityDescriptor/md:IDPSSODescriptor/md:SingleLogoutService/@Location",
|
174
|
+
"/md:EntityDescriptor/md:IDPSSODescriptor/md:SingleLogoutService[@Binding=\"#{binding}\"]/@Location",
|
132
175
|
{ "md" => METADATA }
|
133
176
|
)
|
134
177
|
node.value if node
|
@@ -143,6 +186,14 @@ module OneLogin
|
|
143
186
|
"/md:EntityDescriptor/md:IDPSSODescriptor/md:KeyDescriptor[@use='signing']/ds:KeyInfo/ds:X509Data/ds:X509Certificate",
|
144
187
|
{ "md" => METADATA, "ds" => DSIG }
|
145
188
|
)
|
189
|
+
|
190
|
+
unless node
|
191
|
+
node = REXML::XPath.first(
|
192
|
+
document,
|
193
|
+
"/md:EntityDescriptor/md:IDPSSODescriptor/md:KeyDescriptor/ds:KeyInfo/ds:X509Data/ds:X509Certificate",
|
194
|
+
{ "md" => METADATA, "ds" => DSIG }
|
195
|
+
)
|
196
|
+
end
|
146
197
|
node.text if node
|
147
198
|
end
|
148
199
|
end
|
@@ -158,14 +209,27 @@ module OneLogin
|
|
158
209
|
|
159
210
|
# @return [String|nil] the SHA-1 fingerpint of the X509Certificate if it exists
|
160
211
|
#
|
161
|
-
def fingerprint
|
212
|
+
def fingerprint(fingerprint_algorithm)
|
162
213
|
@fingerprint ||= begin
|
163
214
|
if certificate
|
164
215
|
cert = OpenSSL::X509::Certificate.new(certificate)
|
165
|
-
|
216
|
+
|
217
|
+
fingerprint_alg = XMLSecurity::BaseDocument.new.algorithm(fingerprint_algorithm).new
|
218
|
+
fingerprint_alg.hexdigest(cert.to_der).upcase.scan(/../).join(":")
|
166
219
|
end
|
167
220
|
end
|
168
221
|
end
|
222
|
+
|
223
|
+
# @return [Array] the names of all SAML attributes if any exist
|
224
|
+
#
|
225
|
+
def attribute_names
|
226
|
+
nodes = REXML::XPath.match(
|
227
|
+
document,
|
228
|
+
"/md:EntityDescriptor/md:IDPSSODescriptor/saml:Attribute/@Name",
|
229
|
+
{ "md" => METADATA, "NameFormat" => NAME_FORMAT, "saml" => SAML_ASSERTION }
|
230
|
+
)
|
231
|
+
nodes.map(&:value)
|
232
|
+
end
|
169
233
|
end
|
170
234
|
end
|
171
235
|
end
|
@@ -1,7 +1,6 @@
|
|
1
|
-
require "uuid"
|
2
|
-
|
3
1
|
require "onelogin/ruby-saml/logging"
|
4
2
|
require "onelogin/ruby-saml/saml_message"
|
3
|
+
require "onelogin/ruby-saml/utils"
|
5
4
|
|
6
5
|
# Only supports SAML 2.0
|
7
6
|
module OneLogin
|
@@ -18,7 +17,7 @@ module OneLogin
|
|
18
17
|
# Asigns an ID, a random uuid.
|
19
18
|
#
|
20
19
|
def initialize
|
21
|
-
@uuid =
|
20
|
+
@uuid = OneLogin::RubySaml::Utils.uuid
|
22
21
|
end
|
23
22
|
|
24
23
|
# Creates the Logout Request string.
|
@@ -108,7 +107,7 @@ module OneLogin
|
|
108
107
|
nameid.text = settings.name_identifier_value
|
109
108
|
else
|
110
109
|
# If no NameID is present in the settings we generate one
|
111
|
-
nameid.text =
|
110
|
+
nameid.text = OneLogin::RubySaml::Utils.uuid
|
112
111
|
nameid.attributes['Format'] = 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'
|
113
112
|
end
|
114
113
|
|
@@ -10,13 +10,11 @@ module OneLogin
|
|
10
10
|
# SAML2 Logout Response (SLO IdP initiated, Parser)
|
11
11
|
#
|
12
12
|
class Logoutresponse < SamlMessage
|
13
|
+
include ErrorHandling
|
13
14
|
|
14
15
|
# OneLogin::RubySaml::Settings Toolkit settings
|
15
16
|
attr_accessor :settings
|
16
17
|
|
17
|
-
# Array with the causes
|
18
|
-
attr_accessor :errors
|
19
|
-
|
20
18
|
attr_reader :document
|
21
19
|
attr_reader :response
|
22
20
|
attr_reader :options
|
@@ -47,18 +45,6 @@ module OneLogin
|
|
47
45
|
@document = XMLSecurity::SignedDocument.new(@response)
|
48
46
|
end
|
49
47
|
|
50
|
-
# Append the cause to the errors array, and based on the value of soft, return false or raise
|
51
|
-
# an exception
|
52
|
-
def append_error(error_msg)
|
53
|
-
@errors << error_msg
|
54
|
-
return soft ? false : validation_error(error_msg)
|
55
|
-
end
|
56
|
-
|
57
|
-
# Reset the errors array
|
58
|
-
def reset_errors!
|
59
|
-
@errors = []
|
60
|
-
end
|
61
|
-
|
62
48
|
# Checks if the Status has the "Success" code
|
63
49
|
# @return [Boolean] True if the StatusCode is Sucess
|
64
50
|
# @raise [ValidationError] if soft == false and validation fails
|
@@ -117,18 +103,30 @@ module OneLogin
|
|
117
103
|
end
|
118
104
|
|
119
105
|
# Aux function to validate the Logout Response
|
106
|
+
# @param collect_errors [Boolean] Stop validation when first error appears or keep validating. (if soft=true)
|
120
107
|
# @return [Boolean] TRUE if the SAML Response is valid
|
121
108
|
# @raise [ValidationError] if soft == false and validation fails
|
122
109
|
#
|
123
|
-
def validate
|
110
|
+
def validate(collect_errors = false)
|
124
111
|
reset_errors!
|
125
112
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
113
|
+
if collect_errors
|
114
|
+
valid_state?
|
115
|
+
validate_success_status
|
116
|
+
validate_structure
|
117
|
+
valid_in_response_to?
|
118
|
+
valid_issuer?
|
119
|
+
validate_signature
|
120
|
+
|
121
|
+
@errors.empty?
|
122
|
+
else
|
123
|
+
valid_state? &&
|
124
|
+
validate_success_status &&
|
125
|
+
validate_structure &&
|
126
|
+
valid_in_response_to? &&
|
127
|
+
valid_issuer? &&
|
128
|
+
validate_signature
|
129
|
+
end
|
132
130
|
end
|
133
131
|
|
134
132
|
private
|
@@ -1,7 +1,7 @@
|
|
1
1
|
require "uri"
|
2
|
-
require "uuid"
|
3
2
|
|
4
3
|
require "onelogin/ruby-saml/logging"
|
4
|
+
require "onelogin/ruby-saml/utils"
|
5
5
|
|
6
6
|
# Only supports SAML 2.0
|
7
7
|
module OneLogin
|
@@ -29,8 +29,7 @@ module OneLogin
|
|
29
29
|
sp_sso = root.add_element "md:SPSSODescriptor", {
|
30
30
|
"protocolSupportEnumeration" => "urn:oasis:names:tc:SAML:2.0:protocol",
|
31
31
|
"AuthnRequestsSigned" => settings.security[:authn_requests_signed],
|
32
|
-
|
33
|
-
"WantAssertionsSigned" => !!(settings.idp_cert_fingerprint || settings.idp_cert)
|
32
|
+
"WantAssertionsSigned" => settings.security[:want_assertions_signed],
|
34
33
|
}
|
35
34
|
|
36
35
|
# Add KeyDescriptor if messages will be signed / encrypted
|
@@ -50,7 +49,7 @@ module OneLogin
|
|
50
49
|
xc2.text = cert_text
|
51
50
|
end
|
52
51
|
|
53
|
-
root.attributes["ID"] =
|
52
|
+
root.attributes["ID"] = OneLogin::RubySaml::Utils.uuid
|
54
53
|
if settings.issuer
|
55
54
|
root.attributes["entityID"] = settings.issuer
|
56
55
|
end
|
@@ -11,6 +11,8 @@ module OneLogin
|
|
11
11
|
# SAML2 Authentication Response. SAML Response
|
12
12
|
#
|
13
13
|
class Response < SamlMessage
|
14
|
+
include ErrorHandling
|
15
|
+
|
14
16
|
ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion"
|
15
17
|
PROTOCOL = "urn:oasis:names:tc:SAML:2.0:protocol"
|
16
18
|
DSIG = "http://www.w3.org/2000/09/xmldsig#"
|
@@ -21,9 +23,6 @@ module OneLogin
|
|
21
23
|
# OneLogin::RubySaml::Settings Toolkit settings
|
22
24
|
attr_accessor :settings
|
23
25
|
|
24
|
-
# Array with the causes [Array of strings]
|
25
|
-
attr_accessor :errors
|
26
|
-
|
27
26
|
attr_reader :document
|
28
27
|
attr_reader :decrypted_document
|
29
28
|
attr_reader :response
|
@@ -39,16 +38,15 @@ module OneLogin
|
|
39
38
|
# or :matches_request_id that will validate that the response matches the ID of the request,
|
40
39
|
# or skip the subject confirmation validation with the :skip_subject_confirmation option
|
41
40
|
def initialize(response, options = {})
|
42
|
-
@errors = []
|
43
|
-
|
44
41
|
raise ArgumentError.new("Response cannot be nil") if response.nil?
|
45
|
-
@options = options
|
46
42
|
|
43
|
+
@errors = []
|
44
|
+
@options = options
|
47
45
|
@soft = true
|
48
|
-
|
46
|
+
unless options[:settings].nil?
|
49
47
|
@settings = options[:settings]
|
50
|
-
|
51
|
-
@soft =
|
48
|
+
unless @settings.soft.nil?
|
49
|
+
@soft = @settings.soft
|
52
50
|
end
|
53
51
|
end
|
54
52
|
|
@@ -60,41 +58,36 @@ module OneLogin
|
|
60
58
|
end
|
61
59
|
end
|
62
60
|
|
63
|
-
# Append the cause to the errors array, and based on the value of soft, return false or raise
|
64
|
-
# an exception
|
65
|
-
def append_error(error_msg)
|
66
|
-
@errors << error_msg
|
67
|
-
return soft ? false : validation_error(error_msg)
|
68
|
-
end
|
69
|
-
|
70
|
-
# Reset the errors array
|
71
|
-
def reset_errors!
|
72
|
-
@errors = []
|
73
|
-
end
|
74
|
-
|
75
61
|
# Validates the SAML Response with the default values (soft = true)
|
62
|
+
# @param collect_errors [Boolean] Stop validation when first error appears or keep validating. (if soft=true)
|
76
63
|
# @return [Boolean] TRUE if the SAML Response is valid
|
77
64
|
#
|
78
|
-
def is_valid?
|
79
|
-
validate
|
65
|
+
def is_valid?(collect_errors = false)
|
66
|
+
validate(collect_errors)
|
80
67
|
end
|
81
68
|
|
82
69
|
# @return [String] the NameID provided by the SAML response from the IdP.
|
83
70
|
#
|
84
71
|
def name_id
|
85
|
-
@name_id ||=
|
86
|
-
|
87
|
-
|
88
|
-
node = decrypt_nameid(encrypted_node)
|
89
|
-
else
|
90
|
-
node = xpath_first_from_signed_assertion('/a:Subject/a:NameID')
|
72
|
+
@name_id ||=
|
73
|
+
if name_id_node
|
74
|
+
name_id_node.text
|
91
75
|
end
|
92
|
-
node.nil? ? nil : node.text
|
93
|
-
end
|
94
76
|
end
|
95
77
|
|
96
78
|
alias_method :nameid, :name_id
|
97
79
|
|
80
|
+
# @return [String] the NameID Format provided by the SAML response from the IdP.
|
81
|
+
#
|
82
|
+
def name_id_format
|
83
|
+
@name_id_format ||=
|
84
|
+
if name_id_node && name_id_node.attribute("Format")
|
85
|
+
name_id_node.attribute("Format").value
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
alias_method :nameid_format, :name_id_format
|
90
|
+
|
98
91
|
|
99
92
|
# Gets the SessionIndex from the AuthnStatement.
|
100
93
|
# Could be used to be stored in the local session in order
|
@@ -291,28 +284,48 @@ module OneLogin
|
|
291
284
|
private
|
292
285
|
|
293
286
|
# Validates the SAML Response (calls several validation methods)
|
287
|
+
# @param collect_errors [Boolean] Stop validation when first error appears or keep validating. (if soft=true)
|
294
288
|
# @return [Boolean] True if the SAML Response is valid, otherwise False if soft=True
|
295
289
|
# @raise [ValidationError] if soft == false and validation fails
|
296
290
|
#
|
297
|
-
def validate
|
291
|
+
def validate(collect_errors = false)
|
298
292
|
reset_errors!
|
299
293
|
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
294
|
+
if collect_errors
|
295
|
+
return false unless validate_response_state
|
296
|
+
validate_version
|
297
|
+
validate_id
|
298
|
+
validate_success_status
|
299
|
+
validate_num_assertion
|
300
|
+
validate_no_encrypted_attributes
|
301
|
+
validate_signed_elements
|
302
|
+
validate_structure
|
303
|
+
validate_in_response_to
|
304
|
+
validate_conditions
|
305
|
+
validate_audience
|
306
|
+
validate_issuer
|
307
|
+
validate_session_expiration
|
308
|
+
validate_subject_confirmation
|
309
|
+
validate_signature
|
310
|
+
@errors.empty?
|
311
|
+
else
|
312
|
+
validate_response_state &&
|
313
|
+
validate_version &&
|
314
|
+
validate_id &&
|
315
|
+
validate_success_status &&
|
316
|
+
validate_num_assertion &&
|
317
|
+
validate_no_encrypted_attributes &&
|
318
|
+
validate_signed_elements &&
|
319
|
+
validate_structure &&
|
320
|
+
validate_in_response_to &&
|
321
|
+
validate_conditions &&
|
322
|
+
validate_audience &&
|
323
|
+
validate_destination &&
|
324
|
+
validate_issuer &&
|
325
|
+
validate_session_expiration &&
|
326
|
+
validate_subject_confirmation &&
|
327
|
+
validate_signature
|
328
|
+
end
|
316
329
|
end
|
317
330
|
|
318
331
|
|
@@ -442,6 +455,10 @@ module OneLogin
|
|
442
455
|
return append_error("Found an unexpected number of Signature Element. SAML Response rejected")
|
443
456
|
end
|
444
457
|
|
458
|
+
if settings.security[:want_assertions_signed] && !(signed_elements.include? "Assertion")
|
459
|
+
return append_error("The Assertion of the Response is not signed and the SP requires it")
|
460
|
+
end
|
461
|
+
|
445
462
|
true
|
446
463
|
end
|
447
464
|
|
@@ -638,6 +655,18 @@ module OneLogin
|
|
638
655
|
true
|
639
656
|
end
|
640
657
|
|
658
|
+
def name_id_node
|
659
|
+
@name_id_node ||=
|
660
|
+
begin
|
661
|
+
encrypted_node = xpath_first_from_signed_assertion('/a:Subject/a:EncryptedID')
|
662
|
+
if encrypted_node
|
663
|
+
node = decrypt_nameid(encrypted_node)
|
664
|
+
else
|
665
|
+
node = xpath_first_from_signed_assertion('/a:Subject/a:NameID')
|
666
|
+
end
|
667
|
+
end
|
668
|
+
end
|
669
|
+
|
641
670
|
# Extracts the first appearance that matchs the subelt (pattern)
|
642
671
|
# Search on any Assertion that is signed, or has a Response parent signed
|
643
672
|
# @param subelt [String] The XPath pattern
|
@@ -686,7 +715,7 @@ module OneLogin
|
|
686
715
|
#
|
687
716
|
def generate_decrypted_document
|
688
717
|
if settings.nil? || !settings.get_sp_key
|
689
|
-
|
718
|
+
raise ValidationError.new('An EncryptedAssertion found and no SP private key found on the settings to decrypt it. Be sure you provided the :settings parameter at the initialize method')
|
690
719
|
end
|
691
720
|
|
692
721
|
# Marshal at Ruby 1.8.7 throw an Exception
|
@@ -752,7 +781,7 @@ module OneLogin
|
|
752
781
|
#
|
753
782
|
def decrypt_element(encrypt_node, rgrex)
|
754
783
|
if settings.nil? || !settings.get_sp_key
|
755
|
-
|
784
|
+
raise ValidationError.new('An ' + encrypt_node.name + ' found and no SP private key found on the settings to decrypt it')
|
756
785
|
end
|
757
786
|
|
758
787
|
elem_plaintext = OneLogin::RubySaml::Utils.decrypt_data(encrypt_node, settings.get_sp_key)
|