ruby-saml 1.1.2 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 [](http://travis-ci.org/onelogin/ruby-saml) [](https://coveralls.io/r/onelogin/ruby-saml?branch=master%0A) [](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)
|