ruby-saml 1.9.0 → 1.10.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/.travis.yml +2 -0
- data/README.md +24 -2
- data/changelog.md +5 -0
- data/lib/onelogin/ruby-saml/authrequest.rb +12 -0
- data/lib/onelogin/ruby-saml/idp_metadata_parser.rb +192 -173
- data/lib/onelogin/ruby-saml/logoutrequest.rb +11 -2
- data/lib/onelogin/ruby-saml/logoutresponse.rb +4 -7
- data/lib/onelogin/ruby-saml/saml_message.rb +6 -2
- data/lib/onelogin/ruby-saml/settings.rb +1 -0
- data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +11 -2
- data/lib/onelogin/ruby-saml/utils.rb +1 -1
- data/lib/onelogin/ruby-saml/version.rb +1 -1
- data/ruby-saml.gemspec +7 -3
- data/test/certificates/certificate.der +0 -0
- data/test/idp_metadata_parser_test.rb +9 -1
- data/test/logout_responses/logoutresponse_fixtures.rb +19 -0
- data/test/logoutrequest_test.rb +40 -6
- data/test/logoutresponse_test.rb +12 -5
- data/test/metadata/idp_multiple_descriptors.xml +6 -0
- data/test/metadata/idp_multiple_descriptors_2.xml +59 -0
- data/test/request_test.rb +17 -0
- data/test/settings_test.rb +1 -1
- data/test/slo_logoutresponse_test.rb +34 -0
- data/test/test_helper.rb +4 -0
- data/test/utils_test.rb +5 -0
- metadata +8 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8c5ca7da83f66d1548ef03b3ffe920eea1a07eff
|
4
|
+
data.tar.gz: f27e46ad0136bb5325d55189f30bc8dac861b5d6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 49ac1e001e3b676f080d5ca0d5955c18034cd6a7d7371c99840fece91cb5ac55ce2bd68abfb2ae901f9a192589c997db1e77b6050d9569b66b0aa7311c2875f3
|
7
|
+
data.tar.gz: b6a30ef8efe897fcf8e69eb638b1214077cfd5027f83ab438164b3436d7396a7a4ee1a89f04f055e96d57c0b589e437c48ef5fd658ac2a136560b6b8620fa040
|
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -1,5 +1,11 @@
|
|
1
1
|
# Ruby SAML [![Build Status](https://secure.travis-ci.org/onelogin/ruby-saml.svg)](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.9.0 to 1.10.0
|
4
|
+
Version `1.10.0` improves IdpMetadataParser to allow parse multiple IDPSSODescriptor, Add Subject support on AuthNRequest to allow SPs provide info to the IdP about the user to be authenticated and updates the format_cert method to accept certs with /\x0d/
|
5
|
+
|
6
|
+
## Updating from 1.8.0 to 1.9.0
|
7
|
+
Version `1.9.0` better supports Ruby 2.4+ and JRuby 9.2.0.0. `Settings` initialization now has a second parameter, `keep_security_settings` (default: false), which saves security settings attributes that are not explicitly overridden, if set to true.
|
8
|
+
|
3
9
|
## Updating from 1.7.X to 1.8.0
|
4
10
|
On Version `1.8.0`, creating AuthRequests/LogoutRequests/LogoutResponses with nil RelayState param will not generate a URL with an empty RelayState parameter anymore. It also changes the invalid audience error message.
|
5
11
|
|
@@ -99,8 +105,10 @@ We created a demo project for Rails4 that uses the latest version of this librar
|
|
99
105
|
* 2.2.x
|
100
106
|
* 2.3.x
|
101
107
|
* 2.4.x
|
108
|
+
* 2.5.x
|
102
109
|
* JRuby 1.7.19
|
103
110
|
* JRuby 9.0.0.0
|
111
|
+
* JRuby 9.2.0.0
|
104
112
|
|
105
113
|
## Adding Features, Pull Requests
|
106
114
|
* Fork the repository
|
@@ -121,7 +129,7 @@ Using `Gemfile`
|
|
121
129
|
|
122
130
|
```ruby
|
123
131
|
# latest stable
|
124
|
-
gem 'ruby-saml', '~> 1.
|
132
|
+
gem 'ruby-saml', '~> 1.9.0'
|
125
133
|
|
126
134
|
# or track master for bleeding-edge
|
127
135
|
gem 'ruby-saml', :github => 'onelogin/ruby-saml'
|
@@ -170,7 +178,7 @@ To override the default behavior and control the destination of log messages, pr
|
|
170
178
|
a ruby Logger object to the gem's logging singleton:
|
171
179
|
|
172
180
|
```ruby
|
173
|
-
OneLogin::RubySaml::Logging.logger = Logger.new(
|
181
|
+
OneLogin::RubySaml::Logging.logger = Logger.new('/var/log/ruby-saml.log')
|
174
182
|
```
|
175
183
|
|
176
184
|
## The Initialization Phase
|
@@ -184,6 +192,17 @@ def init
|
|
184
192
|
end
|
185
193
|
```
|
186
194
|
|
195
|
+
If the SP knows who should be authenticated in the IdP, then can provide that info as follows:
|
196
|
+
|
197
|
+
```ruby
|
198
|
+
def init
|
199
|
+
request = OneLogin::RubySaml::Authrequest.new
|
200
|
+
saml_settings.name_identifier_value_requested = "testuser@example.com"
|
201
|
+
saml_settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
|
202
|
+
redirect_to(request.create(saml_settings))
|
203
|
+
end
|
204
|
+
```
|
205
|
+
|
187
206
|
Once you've redirected back to the identity provider, it will ensure that the user has been authorized and redirect back to your application for final consumption. This can look something like this (the `authorize_success` and `authorize_failure` methods are specific to your application):
|
188
207
|
|
189
208
|
```ruby
|
@@ -498,6 +517,9 @@ pp(response.attributes.multi(:not_exists))
|
|
498
517
|
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 `settings.authn_context_comparison` parameter. Possible values include: 'exact', 'better', 'maximum' and 'minimum' (default value is 'exact').
|
499
518
|
To add a `saml:AuthnContextDeclRef`, define `settings.authn_context_decl_ref`.
|
500
519
|
|
520
|
+
In a SP-initiaited flow, the SP can indicate to the IdP the subject that should be authenticated. This is done by defining the `settings.name_identifier_value_requested` before
|
521
|
+
building the authrequest object.
|
522
|
+
|
501
523
|
|
502
524
|
## Signing
|
503
525
|
|
data/changelog.md
CHANGED
@@ -1,4 +1,9 @@
|
|
1
1
|
# RubySaml Changelog
|
2
|
+
### 1.10.0 (Mar 21, 2019)
|
3
|
+
* Add Subject support on AuthNRequest to allow SPs provide info to the IdP about the user to be authenticated
|
4
|
+
* Improves IdpMetadataParser to allow parse multiple IDPSSODescriptors
|
5
|
+
* Improves format_cert method to accept certs with /\x0d/
|
6
|
+
* Forces nokogiri >= 1.8.2 when possible
|
2
7
|
|
3
8
|
### 1.9.0 (Sept 03, 2018)
|
4
9
|
* [#458](https://github.com/onelogin/ruby-saml/pull/458) Remove ruby 2.4+ warnings
|
@@ -121,6 +121,18 @@ module OneLogin
|
|
121
121
|
issuer = root.add_element "saml:Issuer"
|
122
122
|
issuer.text = settings.issuer
|
123
123
|
end
|
124
|
+
|
125
|
+
if settings.name_identifier_value_requested != nil
|
126
|
+
subject = root.add_element "saml:Subject"
|
127
|
+
|
128
|
+
nameid = subject.add_element "saml:NameID"
|
129
|
+
nameid.attributes['Format'] = settings.name_identifier_format if settings.name_identifier_format
|
130
|
+
nameid.text = settings.name_identifier_value_requested
|
131
|
+
|
132
|
+
subject_confirmation = subject.add_element "saml:SubjectConfirmation"
|
133
|
+
subject_confirmation.attributes['Method'] = "urn:oasis:names:tc:SAML:2.0:cm:bearer"
|
134
|
+
end
|
135
|
+
|
124
136
|
if settings.name_identifier_format != nil
|
125
137
|
root.add_element "samlp:NameIDPolicy", {
|
126
138
|
# Might want to make AllowCreate a setting?
|
@@ -1,6 +1,4 @@
|
|
1
1
|
require "base64"
|
2
|
-
require "zlib"
|
3
|
-
require "cgi"
|
4
2
|
require "net/http"
|
5
3
|
require "net/https"
|
6
4
|
require "rexml/document"
|
@@ -14,12 +12,23 @@ module OneLogin
|
|
14
12
|
# Auxiliary class to retrieve and parse the Identity Provider Metadata
|
15
13
|
#
|
16
14
|
class IdpMetadataParser
|
15
|
+
module SamlMetadata
|
16
|
+
module Vocabulary
|
17
|
+
METADATA = "urn:oasis:names:tc:SAML:2.0:metadata"
|
18
|
+
DSIG = "http://www.w3.org/2000/09/xmldsig#"
|
19
|
+
NAME_FORMAT = "urn:oasis:names:tc:SAML:2.0:attrname-format:*"
|
20
|
+
SAML_ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion"
|
21
|
+
end
|
17
22
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
23
|
+
NAMESPACE = {
|
24
|
+
"md" => Vocabulary::METADATA,
|
25
|
+
"NameFormat" => Vocabulary::NAME_FORMAT,
|
26
|
+
"saml" => Vocabulary::SAML_ASSERTION,
|
27
|
+
"ds" => Vocabulary::DSIG
|
28
|
+
}
|
29
|
+
end
|
22
30
|
|
31
|
+
include SamlMetadata::Vocabulary
|
23
32
|
attr_reader :document
|
24
33
|
attr_reader :response
|
25
34
|
attr_reader :options
|
@@ -58,8 +67,25 @@ module OneLogin
|
|
58
67
|
#
|
59
68
|
# @raise [HttpError] Failure to fetch remote IdP metadata
|
60
69
|
def parse_remote_to_hash(url, validate_cert = true, options = {})
|
70
|
+
parse_remote_to_array(url, validate_cert, options)[0]
|
71
|
+
end
|
72
|
+
|
73
|
+
# Parse all Identity Provider metadata and return the results as Array
|
74
|
+
#
|
75
|
+
# @param url [String] Url where the XML of the Identity Provider Metadata is published.
|
76
|
+
# @param validate_cert [Boolean] If true and the URL is HTTPs, the cert of the domain is checked.
|
77
|
+
#
|
78
|
+
# @param options [Hash] options used for parsing the metadata
|
79
|
+
# @option options [Array<String>, nil] :sso_binding an ordered list of bindings to detect the single signon URL. The first binding in the list that is included in the metadata will be used.
|
80
|
+
# @option options [Array<String>, nil] :slo_binding an ordered list of bindings to detect the single logout URL. The first binding in the list that is included in the metadata will be used.
|
81
|
+
# @option options [String, nil] :entity_id when this is given, the entity descriptor for this ID is used. When ommitted, all found IdPs are returned.
|
82
|
+
#
|
83
|
+
# @return [Array<Hash>]
|
84
|
+
#
|
85
|
+
# @raise [HttpError] Failure to fetch remote IdP metadata
|
86
|
+
def parse_remote_to_array(url, validate_cert = true, options = {})
|
61
87
|
idp_metadata = get_idp_metadata(url, validate_cert)
|
62
|
-
|
88
|
+
parse_to_array(idp_metadata, options)
|
63
89
|
end
|
64
90
|
|
65
91
|
# Parse the Identity Provider metadata and update the settings with the IdP values
|
@@ -98,28 +124,29 @@ module OneLogin
|
|
98
124
|
#
|
99
125
|
# @return [Hash]
|
100
126
|
def parse_to_hash(idp_metadata, options = {})
|
127
|
+
parse_to_array(idp_metadata, options)[0]
|
128
|
+
end
|
129
|
+
|
130
|
+
# Parse all Identity Provider metadata and return the results as Array
|
131
|
+
#
|
132
|
+
# @param idp_metadata [String]
|
133
|
+
#
|
134
|
+
# @param options [Hash] options used for parsing the metadata and the returned Settings instance
|
135
|
+
# @option options [Array<String>, nil] :sso_binding an ordered list of bindings to detect the single signon URL. The first binding in the list that is included in the metadata will be used.
|
136
|
+
# @option options [Array<String>, nil] :slo_binding an ordered list of bindings to detect the single logout URL. The first binding in the list that is included in the metadata will be used.
|
137
|
+
# @option options [String, nil] :entity_id when this is given, the entity descriptor for this ID is used. When ommitted, all found IdPs are returned.
|
138
|
+
#
|
139
|
+
# @return [Array<Hash>]
|
140
|
+
def parse_to_array(idp_metadata, options = {})
|
101
141
|
@document = REXML::Document.new(idp_metadata)
|
102
142
|
@options = options
|
103
|
-
@entity_descriptor = nil
|
104
|
-
@certificates = nil
|
105
|
-
@fingerprint = nil
|
106
143
|
|
107
|
-
|
144
|
+
idpsso_descriptors = IdpMetadata::get_idps(@document, options[:entity_id])
|
145
|
+
if !idpsso_descriptors.any?
|
108
146
|
raise ArgumentError.new("idp_metadata must contain an IDPSSODescriptor element")
|
109
147
|
end
|
110
148
|
|
111
|
-
{
|
112
|
-
:idp_entity_id => idp_entity_id,
|
113
|
-
:name_identifier_format => idp_name_id_format,
|
114
|
-
:idp_sso_target_url => single_signon_service_url(options),
|
115
|
-
:idp_slo_target_url => single_logout_service_url(options),
|
116
|
-
:idp_attribute_names => attribute_names,
|
117
|
-
:idp_cert => nil,
|
118
|
-
:idp_cert_fingerprint => nil,
|
119
|
-
:idp_cert_multi => nil
|
120
|
-
}.tap do |response_hash|
|
121
|
-
merge_certificates_into(response_hash) unless certificates.nil?
|
122
|
-
end
|
149
|
+
return idpsso_descriptors.map{|id| IdpMetadata.new(id, id.parent.attributes["entityID"]).to_hash(options)}
|
123
150
|
end
|
124
151
|
|
125
152
|
private
|
@@ -147,7 +174,7 @@ module OneLogin
|
|
147
174
|
end
|
148
175
|
|
149
176
|
get = Net::HTTP::Get.new(uri.request_uri)
|
150
|
-
response = http.request(get)
|
177
|
+
@response = http.request(get)
|
151
178
|
return response.body if response.is_a? Net::HTTPSuccess
|
152
179
|
|
153
180
|
raise OneLogin::RubySaml::HttpError.new(
|
@@ -155,130 +182,129 @@ module OneLogin
|
|
155
182
|
)
|
156
183
|
end
|
157
184
|
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
def entity_descriptor_path
|
167
|
-
path = "//md:EntityDescriptor"
|
168
|
-
entity_id = options[:entity_id]
|
169
|
-
return path unless entity_id
|
170
|
-
path << "[@entityID=\"#{entity_id}\"]"
|
171
|
-
end
|
172
|
-
|
173
|
-
def idpsso_descriptor
|
174
|
-
unless entity_descriptor.nil?
|
175
|
-
return REXML::XPath.first(
|
176
|
-
entity_descriptor,
|
177
|
-
"md:IDPSSODescriptor",
|
178
|
-
namespace
|
185
|
+
class IdpMetadata
|
186
|
+
def self.get_idps(metadata_document, only_entity_id=nil)
|
187
|
+
path = "//md:EntityDescriptor#{only_entity_id && '[@entityID="' + only_entity_id + '"]'}/md:IDPSSODescriptor"
|
188
|
+
REXML::XPath.match(
|
189
|
+
metadata_document,
|
190
|
+
path,
|
191
|
+
SamlMetadata::NAMESPACE
|
179
192
|
)
|
180
193
|
end
|
181
|
-
end
|
182
|
-
|
183
|
-
# @return [String|nil] IdP Entity ID value if exists
|
184
|
-
#
|
185
|
-
def idp_entity_id
|
186
|
-
entity_descriptor.attributes["entityID"]
|
187
|
-
end
|
188
194
|
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
entity_descriptor,
|
194
|
-
"md:IDPSSODescriptor/md:NameIDFormat",
|
195
|
-
namespace
|
196
|
-
)
|
197
|
-
Utils.element_text(node)
|
198
|
-
end
|
195
|
+
def initialize(idpsso_descriptor, entity_id)
|
196
|
+
@idpsso_descriptor = idpsso_descriptor
|
197
|
+
@entity_id = entity_id
|
198
|
+
end
|
199
199
|
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
nodes.first.value if nodes.any?
|
200
|
+
def to_hash(options = {})
|
201
|
+
{
|
202
|
+
:idp_entity_id => @entity_id,
|
203
|
+
:name_identifier_format => idp_name_id_format,
|
204
|
+
:idp_sso_target_url => single_signon_service_url(options),
|
205
|
+
:idp_slo_target_url => single_logout_service_url(options),
|
206
|
+
:idp_attribute_names => attribute_names,
|
207
|
+
:idp_cert => nil,
|
208
|
+
:idp_cert_fingerprint => nil,
|
209
|
+
:idp_cert_multi => nil
|
210
|
+
}.tap do |response_hash|
|
211
|
+
merge_certificates_into(response_hash) unless certificates.nil?
|
212
|
+
end
|
214
213
|
end
|
215
|
-
end
|
216
214
|
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
def single_signon_service_url(options = {})
|
221
|
-
binding = single_signon_service_binding(options[:sso_binding])
|
222
|
-
unless binding.nil?
|
215
|
+
# @return [String|nil] IdP Name ID Format value if exists
|
216
|
+
#
|
217
|
+
def idp_name_id_format
|
223
218
|
node = REXML::XPath.first(
|
224
|
-
|
225
|
-
"md:
|
226
|
-
|
219
|
+
@idpsso_descriptor,
|
220
|
+
"md:NameIDFormat",
|
221
|
+
SamlMetadata::NAMESPACE
|
227
222
|
)
|
228
|
-
|
223
|
+
Utils.element_text(node)
|
229
224
|
end
|
230
|
-
end
|
231
225
|
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
226
|
+
# @param binding_priority [Array]
|
227
|
+
# @return [String|nil] SingleSignOnService binding if exists
|
228
|
+
#
|
229
|
+
def single_signon_service_binding(binding_priority = nil)
|
230
|
+
nodes = REXML::XPath.match(
|
231
|
+
@idpsso_descriptor,
|
232
|
+
"md:SingleSignOnService/@Binding",
|
233
|
+
SamlMetadata::NAMESPACE
|
234
|
+
)
|
235
|
+
if binding_priority
|
236
|
+
values = nodes.map(&:value)
|
237
|
+
binding_priority.detect{ |binding| values.include? binding }
|
238
|
+
else
|
239
|
+
nodes.first.value if nodes.any?
|
240
|
+
end
|
246
241
|
end
|
247
|
-
end
|
248
242
|
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
243
|
+
# @param options [Hash]
|
244
|
+
# @return [String|nil] SingleSignOnService endpoint if exists
|
245
|
+
#
|
246
|
+
def single_signon_service_url(options = {})
|
247
|
+
binding = single_signon_service_binding(options[:sso_binding])
|
248
|
+
return if binding.nil?
|
249
|
+
|
255
250
|
node = REXML::XPath.first(
|
256
|
-
|
257
|
-
"md:
|
258
|
-
|
251
|
+
@idpsso_descriptor,
|
252
|
+
"md:SingleSignOnService[@Binding=\"#{binding}\"]/@Location",
|
253
|
+
SamlMetadata::NAMESPACE
|
259
254
|
)
|
260
255
|
return node.value if node
|
261
256
|
end
|
262
|
-
end
|
263
257
|
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
"md:
|
271
|
-
|
258
|
+
# @param binding_priority [Array]
|
259
|
+
# @return [String|nil] SingleLogoutService binding if exists
|
260
|
+
#
|
261
|
+
def single_logout_service_binding(binding_priority = nil)
|
262
|
+
nodes = REXML::XPath.match(
|
263
|
+
@idpsso_descriptor,
|
264
|
+
"md:SingleLogoutService/@Binding",
|
265
|
+
SamlMetadata::NAMESPACE
|
272
266
|
)
|
267
|
+
if binding_priority
|
268
|
+
values = nodes.map(&:value)
|
269
|
+
binding_priority.detect{ |binding| values.include? binding }
|
270
|
+
else
|
271
|
+
nodes.first.value if nodes.any?
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
# @param options [Hash]
|
276
|
+
# @return [String|nil] SingleLogoutService endpoint if exists
|
277
|
+
#
|
278
|
+
def single_logout_service_url(options = {})
|
279
|
+
binding = single_logout_service_binding(options[:slo_binding])
|
280
|
+
return if binding.nil?
|
273
281
|
|
274
|
-
|
275
|
-
|
276
|
-
"md:
|
277
|
-
|
282
|
+
node = REXML::XPath.first(
|
283
|
+
@idpsso_descriptor,
|
284
|
+
"md:SingleLogoutService[@Binding=\"#{binding}\"]/@Location",
|
285
|
+
SamlMetadata::NAMESPACE
|
278
286
|
)
|
287
|
+
return node.value if node
|
288
|
+
end
|
289
|
+
|
290
|
+
# @return [String|nil] Unformatted Certificate if exists
|
291
|
+
#
|
292
|
+
def certificates
|
293
|
+
@certificates ||= begin
|
294
|
+
signing_nodes = REXML::XPath.match(
|
295
|
+
@idpsso_descriptor,
|
296
|
+
"md:KeyDescriptor[not(contains(@use, 'encryption'))]/ds:KeyInfo/ds:X509Data/ds:X509Certificate",
|
297
|
+
SamlMetadata::NAMESPACE
|
298
|
+
)
|
299
|
+
|
300
|
+
encryption_nodes = REXML::XPath.match(
|
301
|
+
@idpsso_descriptor,
|
302
|
+
"md:KeyDescriptor[not(contains(@use, 'signing'))]/ds:KeyInfo/ds:X509Data/ds:X509Certificate",
|
303
|
+
SamlMetadata::NAMESPACE
|
304
|
+
)
|
305
|
+
|
306
|
+
return nil if signing_nodes.empty? && encryption_nodes.empty?
|
279
307
|
|
280
|
-
certs = nil
|
281
|
-
unless signing_nodes.empty? && encryption_nodes.empty?
|
282
308
|
certs = {}
|
283
309
|
unless signing_nodes.empty?
|
284
310
|
certs['signing'] = []
|
@@ -293,71 +319,62 @@ module OneLogin
|
|
293
319
|
certs['encryption'] << Utils.element_text(cert_node)
|
294
320
|
end
|
295
321
|
end
|
322
|
+
certs
|
296
323
|
end
|
297
|
-
certs
|
298
324
|
end
|
299
|
-
end
|
300
325
|
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
326
|
+
# @return [String|nil] the fingerpint of the X509Certificate if it exists
|
327
|
+
#
|
328
|
+
def fingerprint(certificate, fingerprint_algorithm = XMLSecurity::Document::SHA1)
|
329
|
+
@fingerprint ||= begin
|
330
|
+
return unless certificate
|
331
|
+
|
306
332
|
cert = OpenSSL::X509::Certificate.new(Base64.decode64(certificate))
|
307
333
|
|
308
334
|
fingerprint_alg = XMLSecurity::BaseDocument.new.algorithm(fingerprint_algorithm).new
|
309
335
|
fingerprint_alg.hexdigest(cert.to_der).upcase.scan(/../).join(":")
|
310
336
|
end
|
311
337
|
end
|
312
|
-
end
|
313
338
|
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
def namespace
|
326
|
-
{
|
327
|
-
"md" => METADATA,
|
328
|
-
"NameFormat" => NAME_FORMAT,
|
329
|
-
"saml" => SAML_ASSERTION,
|
330
|
-
"ds" => DSIG
|
331
|
-
}
|
332
|
-
end
|
339
|
+
# @return [Array] the names of all SAML attributes if any exist
|
340
|
+
#
|
341
|
+
def attribute_names
|
342
|
+
nodes = REXML::XPath.match(
|
343
|
+
@idpsso_descriptor ,
|
344
|
+
"saml:Attribute/@Name",
|
345
|
+
SamlMetadata::NAMESPACE
|
346
|
+
)
|
347
|
+
nodes.map(&:value)
|
348
|
+
end
|
333
349
|
|
334
|
-
|
335
|
-
|
350
|
+
def merge_certificates_into(parsed_metadata)
|
351
|
+
if (certificates.size == 1 &&
|
336
352
|
(certificates_has_one('signing') || certificates_has_one('encryption'))) ||
|
337
353
|
(certificates_has_one('signing') && certificates_has_one('encryption') &&
|
338
354
|
certificates["signing"][0] == certificates["encryption"][0])
|
339
355
|
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
356
|
+
if certificates.key?("signing")
|
357
|
+
parsed_metadata[:idp_cert] = certificates["signing"][0]
|
358
|
+
parsed_metadata[:idp_cert_fingerprint] = fingerprint(
|
359
|
+
parsed_metadata[:idp_cert],
|
360
|
+
parsed_metadata[:idp_cert_fingerprint_algorithm]
|
361
|
+
)
|
362
|
+
else
|
363
|
+
parsed_metadata[:idp_cert] = certificates["encryption"][0]
|
364
|
+
parsed_metadata[:idp_cert_fingerprint] = fingerprint(
|
365
|
+
parsed_metadata[:idp_cert],
|
366
|
+
parsed_metadata[:idp_cert_fingerprint_algorithm]
|
367
|
+
)
|
368
|
+
end
|
346
369
|
else
|
347
|
-
|
348
|
-
parsed_metadata[:
|
349
|
-
parsed_metadata[:idp_cert],
|
350
|
-
parsed_metadata[:idp_cert_fingerprint_algorithm]
|
351
|
-
)
|
370
|
+
# symbolize keys of certificates and pass it on
|
371
|
+
parsed_metadata[:idp_cert_multi] = Hash[certificates.map { |k, v| [k.to_sym, v] }]
|
352
372
|
end
|
353
|
-
else
|
354
|
-
# symbolize keys of certificates and pass it on
|
355
|
-
parsed_metadata[:idp_cert_multi] = Hash[certificates.map { |k, v| [k.to_sym, v] }]
|
356
373
|
end
|
357
|
-
end
|
358
374
|
|
359
|
-
|
360
|
-
|
375
|
+
def certificates_has_one(key)
|
376
|
+
certificates.key?(key) && certificates[key].size == 1
|
377
|
+
end
|
361
378
|
end
|
362
379
|
|
363
380
|
def merge_parsed_metadata_into(settings, parsed_metadata)
|
@@ -367,6 +384,8 @@ module OneLogin
|
|
367
384
|
|
368
385
|
settings
|
369
386
|
end
|
387
|
+
|
388
|
+
private_constant :SamlMetadata, :IdpMetadata
|
370
389
|
end
|
371
390
|
end
|
372
391
|
end
|
@@ -89,6 +89,11 @@ module OneLogin
|
|
89
89
|
# @return [String] The SAMLRequest String.
|
90
90
|
#
|
91
91
|
def create_logout_request_xml_doc(settings)
|
92
|
+
document = create_xml_document(settings)
|
93
|
+
sign_document(document, settings)
|
94
|
+
end
|
95
|
+
|
96
|
+
def create_xml_document(settings)
|
92
97
|
time = Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
|
93
98
|
|
94
99
|
request_doc = XMLSecurity::Document.new
|
@@ -122,14 +127,18 @@ module OneLogin
|
|
122
127
|
sessionindex.text = settings.sessionindex
|
123
128
|
end
|
124
129
|
|
130
|
+
request_doc
|
131
|
+
end
|
132
|
+
|
133
|
+
def sign_document(document, settings)
|
125
134
|
# embed signature
|
126
135
|
if settings.security[:logout_requests_signed] && settings.private_key && settings.certificate && settings.security[:embed_sign]
|
127
136
|
private_key = settings.get_sp_key
|
128
137
|
cert = settings.get_sp_cert
|
129
|
-
|
138
|
+
document.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method])
|
130
139
|
end
|
131
140
|
|
132
|
-
|
141
|
+
document
|
133
142
|
end
|
134
143
|
end
|
135
144
|
end
|
@@ -52,10 +52,7 @@ module OneLogin
|
|
52
52
|
# @raise [ValidationError] if soft == false and validation fails
|
53
53
|
#
|
54
54
|
def success?
|
55
|
-
|
56
|
-
return append_error("Bad status code. Expected <urn:oasis:names:tc:SAML:2.0:status:Success>, but was: <#@status_code>")
|
57
|
-
end
|
58
|
-
true
|
55
|
+
return status_code == "urn:oasis:names:tc:SAML:2.0:status:Success"
|
59
56
|
end
|
60
57
|
|
61
58
|
# @return [String|nil] Gets the InResponseTo attribute from the Logout Response if exists.
|
@@ -65,7 +62,7 @@ module OneLogin
|
|
65
62
|
node = REXML::XPath.first(
|
66
63
|
document,
|
67
64
|
"/p:LogoutResponse",
|
68
|
-
{ "p" => PROTOCOL
|
65
|
+
{ "p" => PROTOCOL }
|
69
66
|
)
|
70
67
|
node.nil? ? nil : node.attributes['InResponseTo']
|
71
68
|
end
|
@@ -88,7 +85,7 @@ module OneLogin
|
|
88
85
|
#
|
89
86
|
def status_code
|
90
87
|
@status_code ||= begin
|
91
|
-
node = REXML::XPath.first(document, "/p:LogoutResponse/p:Status/p:StatusCode", { "p" => PROTOCOL
|
88
|
+
node = REXML::XPath.first(document, "/p:LogoutResponse/p:Status/p:StatusCode", { "p" => PROTOCOL })
|
92
89
|
node.nil? ? nil : node.attributes["Value"]
|
93
90
|
end
|
94
91
|
end
|
@@ -98,7 +95,7 @@ module OneLogin
|
|
98
95
|
node = REXML::XPath.first(
|
99
96
|
document,
|
100
97
|
"/p:LogoutResponse/p:Status/p:StatusMessage",
|
101
|
-
{ "p" => PROTOCOL
|
98
|
+
{ "p" => PROTOCOL }
|
102
99
|
)
|
103
100
|
Utils.element_text(node)
|
104
101
|
end
|
@@ -105,7 +105,7 @@ module OneLogin
|
|
105
105
|
def encode_raw_saml(saml, settings)
|
106
106
|
saml = deflate(saml) if settings.compress_request
|
107
107
|
|
108
|
-
CGI.escape(
|
108
|
+
CGI.escape(encode(saml))
|
109
109
|
end
|
110
110
|
|
111
111
|
# Base 64 decode method
|
@@ -121,7 +121,11 @@ module OneLogin
|
|
121
121
|
# @return [String] The encoded string
|
122
122
|
#
|
123
123
|
def encode(string)
|
124
|
-
Base64.
|
124
|
+
if Base64.respond_to?('strict_encode64')
|
125
|
+
Base64.strict_encode64(string)
|
126
|
+
else
|
127
|
+
Base64.encode64(string).gsub(/\n/, "")
|
128
|
+
end
|
125
129
|
end
|
126
130
|
|
127
131
|
# Check if a string is base64 encoded
|
@@ -45,6 +45,7 @@ module OneLogin
|
|
45
45
|
attr_accessor :sp_name_qualifier
|
46
46
|
attr_accessor :name_identifier_format
|
47
47
|
attr_accessor :name_identifier_value
|
48
|
+
attr_accessor :name_identifier_value_requested
|
48
49
|
attr_accessor :sessionindex
|
49
50
|
attr_accessor :compress_request
|
50
51
|
attr_accessor :compress_response
|
@@ -97,6 +97,11 @@ module OneLogin
|
|
97
97
|
# @return [String] The SAMLResponse String.
|
98
98
|
#
|
99
99
|
def create_logout_response_xml_doc(settings, request_id = nil, logout_message = nil)
|
100
|
+
document = create_xml_document(settings, request_id, logout_message)
|
101
|
+
sign_document(document, settings)
|
102
|
+
end
|
103
|
+
|
104
|
+
def create_xml_document(settings, request_id = nil, logout_message = nil)
|
100
105
|
time = Time.now.utc.strftime('%Y-%m-%dT%H:%M:%SZ')
|
101
106
|
|
102
107
|
response_doc = XMLSecurity::Document.new
|
@@ -126,14 +131,18 @@ module OneLogin
|
|
126
131
|
status_message = status.add_element 'samlp:StatusMessage'
|
127
132
|
status_message.text = logout_message
|
128
133
|
|
134
|
+
response_doc
|
135
|
+
end
|
136
|
+
|
137
|
+
def sign_document(document, settings)
|
129
138
|
# embed signature
|
130
139
|
if settings.security[:logout_responses_signed] && settings.private_key && settings.certificate && settings.security[:embed_sign]
|
131
140
|
private_key = settings.get_sp_key
|
132
141
|
cert = settings.get_sp_cert
|
133
|
-
|
142
|
+
document.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method])
|
134
143
|
end
|
135
144
|
|
136
|
-
|
145
|
+
document
|
137
146
|
end
|
138
147
|
|
139
148
|
end
|
@@ -22,7 +22,7 @@ module OneLogin
|
|
22
22
|
#
|
23
23
|
def self.format_cert(cert)
|
24
24
|
# don't try to format an encoded certificate or if is empty or nil
|
25
|
-
return cert if cert.nil? || cert.empty? || cert.
|
25
|
+
return cert if cert.nil? || cert.empty? || !cert.ascii_only?
|
26
26
|
|
27
27
|
if cert.scan(/BEGIN CERTIFICATE/).length > 1
|
28
28
|
formatted_cert = []
|
data/ruby-saml.gemspec
CHANGED
@@ -29,15 +29,19 @@ Gem::Specification.new do |s|
|
|
29
29
|
# Nokogiri's version dependent on the Ruby version, even though we would
|
30
30
|
# have liked to constrain Ruby 1.8.7 to install only the 1.5.x versions.
|
31
31
|
if defined?(JRUBY_VERSION)
|
32
|
-
|
33
|
-
|
32
|
+
if JRUBY_VERSION < '9.2.0.0'
|
33
|
+
s.add_runtime_dependency('nokogiri', '>= 1.8.2', '<= 1.8.5')
|
34
|
+
s.add_runtime_dependency('jruby-openssl', '>= 0.9.8')
|
35
|
+
else
|
36
|
+
s.add_runtime_dependency('nokogiri', '>= 1.8.2')
|
37
|
+
end
|
34
38
|
elsif RUBY_VERSION < '1.9'
|
35
39
|
s.add_runtime_dependency('uuid')
|
36
40
|
s.add_runtime_dependency('nokogiri', '<= 1.5.11')
|
37
41
|
elsif RUBY_VERSION < '2.1'
|
38
42
|
s.add_runtime_dependency('nokogiri', '>= 1.5.10', '<= 1.6.8.1')
|
39
43
|
else
|
40
|
-
s.add_runtime_dependency('nokogiri', '>= 1.
|
44
|
+
s.add_runtime_dependency('nokogiri', '>= 1.8.2')
|
41
45
|
end
|
42
46
|
|
43
47
|
s.add_development_dependency('minitest', '~> 5.5')
|
Binary file
|
@@ -319,7 +319,7 @@ class IdpMetadataParserTest < Minitest::Test
|
|
319
319
|
describe "parsing metadata with many entity descriptors" do
|
320
320
|
before do
|
321
321
|
@idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new
|
322
|
-
@idp_metadata =
|
322
|
+
@idp_metadata = idp_metadata_multiple_descriptors2
|
323
323
|
@settings = @idp_metadata_parser.parse(@idp_metadata)
|
324
324
|
end
|
325
325
|
|
@@ -342,6 +342,14 @@ class IdpMetadataParserTest < Minitest::Test
|
|
342
342
|
assert_equal "https://hello.example.com/access/saml/logout", @settings.idp_slo_target_url
|
343
343
|
assert_equal ["AuthToken", "SSOStartPage"], @settings.idp_attribute_names
|
344
344
|
end
|
345
|
+
|
346
|
+
it "should handle multiple descriptors at once" do
|
347
|
+
settings = @idp_metadata_parser.parse_to_array(@idp_metadata)
|
348
|
+
assert_equal "https://foo.example.com/access/saml/idp.xml", settings.first[:idp_entity_id]
|
349
|
+
assert_equal "F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72", settings.first[:idp_cert_fingerprint]
|
350
|
+
assert_equal "https://bar.example.com/access/saml/idp.xml", settings.last[:idp_entity_id]
|
351
|
+
assert_equal "08:EB:6E:60:A2:14:4E:89:EC:FA:05:74:9D:72:BF:5D:BE:54:F0:1A", settings.last[:idp_cert_fingerprint]
|
352
|
+
end
|
345
353
|
end
|
346
354
|
|
347
355
|
describe "parsing metadata with no IDPSSODescriptor element" do
|
@@ -44,6 +44,25 @@ def unsuccessful_logout_response_document(opts = {})
|
|
44
44
|
</samlp:LogoutResponse>"
|
45
45
|
end
|
46
46
|
|
47
|
+
def unsuccessful_logout_response_with_message_document(opts = {})
|
48
|
+
opts = default_logout_response_opts.merge(opts)
|
49
|
+
|
50
|
+
"<samlp:LogoutResponse
|
51
|
+
xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\"
|
52
|
+
ID=\"#{random_id}\" Version=\"2.0\"
|
53
|
+
IssueInstant=\"#{opts[:issue_instant]}\"
|
54
|
+
Destination=\"#{opts[:settings].single_logout_service_url}\"
|
55
|
+
InResponseTo=\"#{opts[:uuid]}\">
|
56
|
+
<saml:Issuer xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\">#{opts[:settings].issuer}</saml:Issuer>
|
57
|
+
<samlp:Status xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\">
|
58
|
+
<samlp:StatusCode xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\"
|
59
|
+
Value=\"urn:oasis:names:tc:SAML:2.0:status:Requester\">
|
60
|
+
</samlp:StatusCode>
|
61
|
+
<samlp:StatusMessage>Logoutrequest expired</samlp:StatusMessage>
|
62
|
+
</samlp:Status>
|
63
|
+
</samlp:LogoutResponse>"
|
64
|
+
end
|
65
|
+
|
47
66
|
def invalid_xml_logout_response_document
|
48
67
|
"<samlp:SomethingAwful
|
49
68
|
xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\"
|
data/test/logoutrequest_test.rb
CHANGED
@@ -104,6 +104,40 @@ class RequestTest < Minitest::Test
|
|
104
104
|
settings.private_key = ruby_saml_key_text
|
105
105
|
end
|
106
106
|
|
107
|
+
it "doesn't sign through create_xml_document" do
|
108
|
+
unauth_req = OneLogin::RubySaml::Logoutrequest.new
|
109
|
+
inflated = unauth_req.create_xml_document(settings).to_s
|
110
|
+
|
111
|
+
refute_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], inflated
|
112
|
+
refute_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1'/>], inflated
|
113
|
+
refute_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2000/09/xmldsig#sha1'/>], inflated
|
114
|
+
end
|
115
|
+
|
116
|
+
it "sign unsigned request" do
|
117
|
+
unauth_req = OneLogin::RubySaml::Logoutrequest.new
|
118
|
+
unauth_req_doc = unauth_req.create_xml_document(settings)
|
119
|
+
inflated = unauth_req_doc.to_s
|
120
|
+
|
121
|
+
refute_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], inflated
|
122
|
+
refute_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1'/>], inflated
|
123
|
+
refute_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2000/09/xmldsig#sha1'/>], inflated
|
124
|
+
|
125
|
+
inflated = unauth_req.sign_document(unauth_req_doc, settings).to_s
|
126
|
+
|
127
|
+
assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], inflated
|
128
|
+
assert_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1'/>], inflated
|
129
|
+
assert_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2000/09/xmldsig#sha1'/>], inflated
|
130
|
+
end
|
131
|
+
|
132
|
+
it "signs through create_logout_request_xml_doc" do
|
133
|
+
unauth_req = OneLogin::RubySaml::Logoutrequest.new
|
134
|
+
inflated = unauth_req.create_logout_request_xml_doc(settings).to_s
|
135
|
+
|
136
|
+
assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], inflated
|
137
|
+
assert_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1'/>], inflated
|
138
|
+
assert_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2000/09/xmldsig#sha1'/>], inflated
|
139
|
+
end
|
140
|
+
|
107
141
|
it "created a signed logout request" do
|
108
142
|
settings.compress_request = true
|
109
143
|
|
@@ -185,8 +219,8 @@ class RequestTest < Minitest::Test
|
|
185
219
|
query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}"
|
186
220
|
|
187
221
|
signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg'])
|
188
|
-
assert_equal signature_algorithm, OpenSSL::Digest::SHA256
|
189
|
-
assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string)
|
222
|
+
assert_equal signature_algorithm, OpenSSL::Digest::SHA256
|
223
|
+
assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string)
|
190
224
|
end
|
191
225
|
|
192
226
|
it "create a signature parameter with RSA_SHA384 / SHA384 and validate it" do
|
@@ -201,8 +235,8 @@ class RequestTest < Minitest::Test
|
|
201
235
|
query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}"
|
202
236
|
|
203
237
|
signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg'])
|
204
|
-
assert_equal signature_algorithm, OpenSSL::Digest::SHA384
|
205
|
-
assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string)
|
238
|
+
assert_equal signature_algorithm, OpenSSL::Digest::SHA384
|
239
|
+
assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string)
|
206
240
|
end
|
207
241
|
|
208
242
|
it "create a signature parameter with RSA_SHA512 / SHA512 and validate it" do
|
@@ -217,8 +251,8 @@ class RequestTest < Minitest::Test
|
|
217
251
|
query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}"
|
218
252
|
|
219
253
|
signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg'])
|
220
|
-
assert_equal signature_algorithm, OpenSSL::Digest::SHA512
|
221
|
-
assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string)
|
254
|
+
assert_equal signature_algorithm, OpenSSL::Digest::SHA512
|
255
|
+
assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string)
|
222
256
|
end
|
223
257
|
|
224
258
|
end
|
data/test/logoutresponse_test.rb
CHANGED
@@ -107,15 +107,22 @@ class RubySamlTest < Minitest::Test
|
|
107
107
|
assert_includes logoutresponse.errors, "The InResponseTo of the Logout Response: #{logoutresponse.in_response_to}, does not match the ID of the Logout Request sent by the SP: #{expected_request_id}"
|
108
108
|
end
|
109
109
|
|
110
|
-
it "invalidate logout response with
|
110
|
+
it "invalidate logout response with unexpected request status" do
|
111
111
|
logoutresponse = OneLogin::RubySaml::Logoutresponse.new(unsuccessful_logout_response_document, settings)
|
112
112
|
|
113
113
|
assert !logoutresponse.success?
|
114
114
|
assert !logoutresponse.validate
|
115
|
-
assert_includes logoutresponse.errors, "Bad status code. Expected <urn:oasis:names:tc:SAML:2.0:status:Success>, but was: <urn:oasis:names:tc:SAML:2.0:status:Requester>"
|
116
115
|
assert_includes logoutresponse.errors, "The status code of the Logout Response was not Success, was Requester"
|
117
116
|
end
|
118
117
|
|
118
|
+
it "invalidate logout response with unexpected request status and status message" do
|
119
|
+
logoutresponse = OneLogin::RubySaml::Logoutresponse.new(unsuccessful_logout_response_with_message_document, settings)
|
120
|
+
|
121
|
+
assert !logoutresponse.success?
|
122
|
+
assert !logoutresponse.validate
|
123
|
+
assert_includes logoutresponse.errors, "The status code of the Logout Response was not Success, was Requester -> Logoutrequest expired"
|
124
|
+
end
|
125
|
+
|
119
126
|
it "invalidate logout response when in lack of issuer setting" do
|
120
127
|
bad_settings = settings
|
121
128
|
bad_settings.issuer = nil
|
@@ -137,7 +144,7 @@ class RubySamlTest < Minitest::Test
|
|
137
144
|
logoutresponse = OneLogin::RubySaml::Logoutresponse.new(unsuccessful_logout_response_document, settings)
|
138
145
|
collect_errors = true
|
139
146
|
assert !logoutresponse.validate(collect_errors)
|
140
|
-
assert_includes logoutresponse.errors, "
|
147
|
+
assert_includes logoutresponse.errors, "The status code of the Logout Response was not Success, was Requester"
|
141
148
|
assert_includes logoutresponse.errors, "Doesn't match the issuer, expected: <#{logoutresponse.settings.idp_entity_id}>, but was: <http://app.muda.no>"
|
142
149
|
end
|
143
150
|
|
@@ -185,14 +192,14 @@ class RubySamlTest < Minitest::Test
|
|
185
192
|
logoutresponse = OneLogin::RubySaml::Logoutresponse.new(unsuccessful_logout_response_document, settings)
|
186
193
|
|
187
194
|
assert_raises(OneLogin::RubySaml::ValidationError) { logoutresponse.validate }
|
188
|
-
assert_includes logoutresponse.errors, "
|
195
|
+
assert_includes logoutresponse.errors, "The status code of the Logout Response was not Success, was Requester"
|
189
196
|
end
|
190
197
|
|
191
198
|
it "raise validation error when in bad state" do
|
192
199
|
# no settings
|
193
200
|
logoutresponse = OneLogin::RubySaml::Logoutresponse.new(unsuccessful_logout_response_document, settings)
|
194
201
|
assert_raises(OneLogin::RubySaml::ValidationError) { logoutresponse.validate }
|
195
|
-
assert_includes logoutresponse.errors, "
|
202
|
+
assert_includes logoutresponse.errors, "The status code of the Logout Response was not Success, was Requester"
|
196
203
|
end
|
197
204
|
|
198
205
|
it "raise validation error when in lack of issuer setting" do
|
@@ -1,5 +1,11 @@
|
|
1
1
|
<?xml version="1.0" encoding="UTF-8"?>
|
2
2
|
<md:EntitiesDescriptor validUntil="2014-04-17T18:02:33.910Z" xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata">
|
3
|
+
<md:EntityDescriptor entityID="https://biz.example.com/app" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="md:EntityDescriptorType">
|
4
|
+
<md:SPSSODescriptor AuthnRequestsSigned="true" WantAssertionsSigned="false" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
|
5
|
+
<md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</md:NameIDFormat>
|
6
|
+
<md:AssertionConsumerService isDefault="true" index="1" binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" location="https://biz.example.com/app/sso"></md:AssertionConsumerService>
|
7
|
+
</md:SPSSODescriptor>
|
8
|
+
</md:EntityDescriptor>
|
3
9
|
<md:EntityDescriptor entityID="https://foo.example.com/access/saml/idp.xml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="md:EntityDescriptorType">
|
4
10
|
<md:IDPSSODescriptor WantAuthnRequestsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
|
5
11
|
<md:KeyDescriptor use="signing">
|
@@ -0,0 +1,59 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<md:EntitiesDescriptor validUntil="2014-04-17T18:02:33.910Z" xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata">
|
3
|
+
<md:EntityDescriptor entityID="https://biz.example.com/app" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="md:EntityDescriptorType">
|
4
|
+
<md:SPSSODescriptor AuthnRequestsSigned="true" WantAssertionsSigned="false" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
|
5
|
+
<md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</md:NameIDFormat>
|
6
|
+
<md:AssertionConsumerService isDefault="true" index="1" binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" location="https://biz.example.com/app/sso"></md:AssertionConsumerService>
|
7
|
+
</md:SPSSODescriptor>
|
8
|
+
</md:EntityDescriptor>
|
9
|
+
<md:EntityDescriptor entityID="https://foo.example.com/access/saml/idp.xml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="md:EntityDescriptorType">
|
10
|
+
<md:IDPSSODescriptor WantAuthnRequestsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
|
11
|
+
<md:KeyDescriptor use="signing">
|
12
|
+
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
|
13
|
+
<ds:X509Data>
|
14
|
+
<ds:X509Certificate>LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURxekNDQXhTZ0F3SUJBZ0lCQVRBTkJna3Foa2lHOXcwQkFRc0ZBRENCaGpFTE1Ba0dBMVVFQmhNQ1FWVXgKRERBS0JnTlZCQWdUQTA1VFZ6RVBNQTBHQTFVRUJ4TUdVM2xrYm1WNU1Rd3dDZ1lEVlFRS0RBTlFTVlF4Q1RBSApCZ05WQkFzTUFERVlNQllHQTFVRUF3d1BiR0YzY21WdVkyVndhWFF1WTI5dE1TVXdJd1lKS29aSWh2Y05BUWtCCkRCWnNZWGR5Wlc1alpTNXdhWFJBWjIxaGFXd3VZMjl0TUI0WERURXlNRFF4T1RJeU5UUXhPRm9YRFRNeU1EUXgKTkRJeU5UUXhPRm93Z1lZeEN6QUpCZ05WQkFZVEFrRlZNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVApCbE41Wkc1bGVURU1NQW9HQTFVRUNnd0RVRWxVTVFrd0J3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psCmJtTmxjR2wwTG1OdmJURWxNQ01HQ1NxR1NJYjNEUUVKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnYKYlRDQm56QU5CZ2txaGtpRzl3MEJBUUVGQUFPQmpRQXdnWWtDZ1lFQXFqaWUzUjJvaStwRGFldndJeXMvbWJVVApubkdsa3h0ZGlrcnExMXZleHd4SmlQTmhtaHFSVzNtVXVKRXpsbElkVkw2RW14R1lUcXBxZjkzSGxoa3NhZUowCjhVZ2pQOVVtTVlyaFZKdTFqY0ZXVjdmei9yKzIxL2F3VG5EVjlzTVlRcXVJUllZeTdiRzByMU9iaXdkb3ZudGsKN2dGSTA2WjB2WmFjREU1Ym9xVUNBd0VBQWFPQ0FTVXdnZ0VoTUFrR0ExVWRFd1FDTUFBd0N3WURWUjBQQkFRRApBZ1VnTUIwR0ExVWREZ1FXQkJTUk9OOEdKOG8rOGpnRnRqa3R3WmRxeDZCUnlUQVRCZ05WSFNVRUREQUtCZ2dyCkJnRUZCUWNEQVRBZEJnbGdoa2dCaHZoQ0FRMEVFQllPVkdWemRDQllOVEE1SUdObGNuUXdnYk1HQTFVZEl3U0IKcXpDQnFJQVVrVGpmQmlmS1B2STRCYlk1TGNHWGFzZWdVY21oZ1l5a2dZa3dnWVl4Q3pBSkJnTlZCQVlUQWtGVgpNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVEJsTjVaRzVsZVRFTU1Bb0dBMVVFQ2d3RFVFbFVNUWt3CkJ3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psYm1ObGNHbDBMbU52YlRFbE1DTUdDU3FHU0liM0RRRUoKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnZiWUlCQVRBTkJna3Foa2lHOXcwQkFRc0ZBQU9CZ1FDRQpUQWVKVERTQVc2ejFVRlRWN1FyZWg0VUxGT1JhajkrZUN1RjNLV0RIYyswSVFDajlyZG5ERzRRL3dmNy9yYVEwCkpuUFFDU0NkclBMSmV5b1BIN1FhVHdvYUY3ZHpWdzRMQ3N5TkpURld4NGNNNTBWdzZSNWZET2dpQzhic2ZmUzgKQkptb3VscnJaRE5OVmpHOG1XNmNMeHJZdlZRT3JSVmVjQ0ZJZ3NzQ2JBPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=</ds:X509Certificate>
|
15
|
+
</ds:X509Data>
|
16
|
+
</ds:KeyInfo>
|
17
|
+
</md:KeyDescriptor>
|
18
|
+
<md:KeyDescriptor use="encryption">
|
19
|
+
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
|
20
|
+
<ds:X509Data>
|
21
|
+
<ds:X509Certificate>LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURxekNDQXhTZ0F3SUJBZ0lCQVRBTkJna3Foa2lHOXcwQkFRc0ZBRENCaGpFTE1Ba0dBMVVFQmhNQ1FWVXgKRERBS0JnTlZCQWdUQTA1VFZ6RVBNQTBHQTFVRUJ4TUdVM2xrYm1WNU1Rd3dDZ1lEVlFRS0RBTlFTVlF4Q1RBSApCZ05WQkFzTUFERVlNQllHQTFVRUF3d1BiR0YzY21WdVkyVndhWFF1WTI5dE1TVXdJd1lKS29aSWh2Y05BUWtCCkRCWnNZWGR5Wlc1alpTNXdhWFJBWjIxaGFXd3VZMjl0TUI0WERURXlNRFF4T1RJeU5UUXhPRm9YRFRNeU1EUXgKTkRJeU5UUXhPRm93Z1lZeEN6QUpCZ05WQkFZVEFrRlZNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVApCbE41Wkc1bGVURU1NQW9HQTFVRUNnd0RVRWxVTVFrd0J3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psCmJtTmxjR2wwTG1OdmJURWxNQ01HQ1NxR1NJYjNEUUVKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnYKYlRDQm56QU5CZ2txaGtpRzl3MEJBUUVGQUFPQmpRQXdnWWtDZ1lFQXFqaWUzUjJvaStwRGFldndJeXMvbWJVVApubkdsa3h0ZGlrcnExMXZleHd4SmlQTmhtaHFSVzNtVXVKRXpsbElkVkw2RW14R1lUcXBxZjkzSGxoa3NhZUowCjhVZ2pQOVVtTVlyaFZKdTFqY0ZXVjdmei9yKzIxL2F3VG5EVjlzTVlRcXVJUllZeTdiRzByMU9iaXdkb3ZudGsKN2dGSTA2WjB2WmFjREU1Ym9xVUNBd0VBQWFPQ0FTVXdnZ0VoTUFrR0ExVWRFd1FDTUFBd0N3WURWUjBQQkFRRApBZ1VnTUIwR0ExVWREZ1FXQkJTUk9OOEdKOG8rOGpnRnRqa3R3WmRxeDZCUnlUQVRCZ05WSFNVRUREQUtCZ2dyCkJnRUZCUWNEQVRBZEJnbGdoa2dCaHZoQ0FRMEVFQllPVkdWemRDQllOVEE1SUdObGNuUXdnYk1HQTFVZEl3U0IKcXpDQnFJQVVrVGpmQmlmS1B2STRCYlk1TGNHWGFzZWdVY21oZ1l5a2dZa3dnWVl4Q3pBSkJnTlZCQVlUQWtGVgpNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVEJsTjVaRzVsZVRFTU1Bb0dBMVVFQ2d3RFVFbFVNUWt3CkJ3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psYm1ObGNHbDBMbU52YlRFbE1DTUdDU3FHU0liM0RRRUoKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnZiWUlCQVRBTkJna3Foa2lHOXcwQkFRc0ZBQU9CZ1FDRQpUQWVKVERTQVc2ejFVRlRWN1FyZWg0VUxGT1JhajkrZUN1RjNLV0RIYyswSVFDajlyZG5ERzRRL3dmNy9yYVEwCkpuUFFDU0NkclBMSmV5b1BIN1FhVHdvYUY3ZHpWdzRMQ3N5TkpURld4NGNNNTBWdzZSNWZET2dpQzhic2ZmUzgKQkptb3VscnJaRE5OVmpHOG1XNmNMeHJZdlZRT3JSVmVjQ0ZJZ3NzQ2JBPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=</ds:X509Certificate>
|
22
|
+
</ds:X509Data>
|
23
|
+
</ds:KeyInfo>
|
24
|
+
</md:KeyDescriptor>
|
25
|
+
<md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://hello.example.com/access/saml/logout" ResponseLocation="https://hello.example.com/access/saml/logout"/>
|
26
|
+
<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat>
|
27
|
+
<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</md:NameIDFormat>
|
28
|
+
<md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</md:NameIDFormat>
|
29
|
+
<md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://hello.example.com/access/saml/login"/>
|
30
|
+
<saml:Attribute Name="AuthToken" NameFormat="urn:oasis:names:tc:SAML:2.0:att rname-format:basic" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"/>
|
31
|
+
<saml:Attribute Name="SSOStartPage" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"/>
|
32
|
+
</md:IDPSSODescriptor>
|
33
|
+
</md:EntityDescriptor>
|
34
|
+
<md:EntityDescriptor entityID="https://bar.example.com/access/saml/idp.xml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="md:EntityDescriptorType">
|
35
|
+
<md:IDPSSODescriptor WantAuthnRequestsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
|
36
|
+
<md:KeyDescriptor use="signing">
|
37
|
+
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
|
38
|
+
<ds:X509Data>
|
39
|
+
<ds:X509Certificate>MIICfDCCAeWgAwIBAgIBADANBgkqhkiG9w0BAQsFADBbMQswCQYDVQQGEwJ1czEYMBYGA1UECAwPYmFyLmV4YW1wbGUuY29tMRgwFgYDVQQKDA9iYXIuZXhhbXBsZS5jb20xGDAWBgNVBAMMD2Jhci5leGFtcGxlLmNvbTAeFw0xOTAzMjExMzAzMTJaFw0yOTAzMTgxMzAzMTJaMFsxCzAJBgNVBAYTAnVzMRgwFgYDVQQIDA9iYXIuZXhhbXBsZS5jb20xGDAWBgNVBAoMD2Jhci5leGFtcGxlLmNvbTEYMBYGA1UEAwwPYmFyLmV4YW1wbGUuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDv7Xe9pxNYjxLhXhjQy7L6BbU0IVwQS9pbWqXYDtH3z1fIy+tWMEnE+XhuSFLilRcfn8Ksk0VeFLByQw2HfxAHY3O2dsCv5yLHye3/5bLKa+/pYu7r9ltE5JNjELAH1Yo0/SvAWX9Nuw6Ovw7D6frXUxBPhJaEQ+1VnatuZBUOfQIDAQABo1AwTjAdBgNVHQ4EFgQU6xbFedBYUDFTdXoQvRrELtH4jP8wHwYDVR0jBBgwFoAU6xbFedBYUDFTdXoQvRrELtH4jP8wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOBgQBvDqYSONEhlDB4+k2+hMg03XCwINLh0UHWd+QfFO1yT6iZK+Duaq9pDBl97RO9sN6qHfOFSQXYBOj6S1Xf+6Orq1yeep/Uh+UrL7wzWYMzPryZxTYn8B1PbdXePRIZCzU7d7ueMnEfUoBj9HqhQphRYD9WgXP9/xzvu9JQjTNk9g==</ds:X509Certificate>
|
40
|
+
</ds:X509Data>
|
41
|
+
</ds:KeyInfo>
|
42
|
+
</md:KeyDescriptor>
|
43
|
+
<md:KeyDescriptor use="encryption">
|
44
|
+
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
|
45
|
+
<ds:X509Data>
|
46
|
+
<ds:X509Certificate>MIICfDCCAeWgAwIBAgIBADANBgkqhkiG9w0BAQsFADBbMQswCQYDVQQGEwJ1czEYMBYGA1UECAwPYmFyLmV4YW1wbGUuY29tMRgwFgYDVQQKDA9iYXIuZXhhbXBsZS5jb20xGDAWBgNVBAMMD2Jhci5leGFtcGxlLmNvbTAeFw0xOTAzMjExMzAzMTJaFw0yOTAzMTgxMzAzMTJaMFsxCzAJBgNVBAYTAnVzMRgwFgYDVQQIDA9iYXIuZXhhbXBsZS5jb20xGDAWBgNVBAoMD2Jhci5leGFtcGxlLmNvbTEYMBYGA1UEAwwPYmFyLmV4YW1wbGUuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDv7Xe9pxNYjxLhXhjQy7L6BbU0IVwQS9pbWqXYDtH3z1fIy+tWMEnE+XhuSFLilRcfn8Ksk0VeFLByQw2HfxAHY3O2dsCv5yLHye3/5bLKa+/pYu7r9ltE5JNjELAH1Yo0/SvAWX9Nuw6Ovw7D6frXUxBPhJaEQ+1VnatuZBUOfQIDAQABo1AwTjAdBgNVHQ4EFgQU6xbFedBYUDFTdXoQvRrELtH4jP8wHwYDVR0jBBgwFoAU6xbFedBYUDFTdXoQvRrELtH4jP8wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOBgQBvDqYSONEhlDB4+k2+hMg03XCwINLh0UHWd+QfFO1yT6iZK+Duaq9pDBl97RO9sN6qHfOFSQXYBOj6S1Xf+6Orq1yeep/Uh+UrL7wzWYMzPryZxTYn8B1PbdXePRIZCzU7d7ueMnEfUoBj9HqhQphRYD9WgXP9/xzvu9JQjTNk9g==</ds:X509Certificate>
|
47
|
+
</ds:X509Data>
|
48
|
+
</ds:KeyInfo>
|
49
|
+
</md:KeyDescriptor>
|
50
|
+
<md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://hello.example.com/access/saml/logout" ResponseLocation="https://hello.example.com/access/saml/logout"/>
|
51
|
+
<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat>
|
52
|
+
<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</md:NameIDFormat>
|
53
|
+
<md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</md:NameIDFormat>
|
54
|
+
<md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://hello.example.com/access/saml/login"/>
|
55
|
+
<saml:Attribute Name="AuthToken" NameFormat="urn:oasis:names:tc:SAML:2.0:att rname-format:basic" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"/>
|
56
|
+
<saml:Attribute Name="SSOStartPage" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"/>
|
57
|
+
</md:IDPSSODescriptor>
|
58
|
+
</md:EntityDescriptor>
|
59
|
+
</md:EntitiesDescriptor>
|
data/test/request_test.rb
CHANGED
@@ -121,6 +121,23 @@ class RequestTest < Minitest::Test
|
|
121
121
|
assert_match /<samlp:NameIDPolicy[^<]* Format='urn:oasis:names:tc:SAML:2.0:nameid-format:transient'/, inflated
|
122
122
|
end
|
123
123
|
|
124
|
+
it "create the SAMLRequest URL parameter with Subject" do
|
125
|
+
settings.name_identifier_value_requested = "testuser@example.com"
|
126
|
+
settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
|
127
|
+
auth_url = OneLogin::RubySaml::Authrequest.new.create(settings)
|
128
|
+
assert_match /^http:\/\/example\.com\?SAMLRequest=/, auth_url
|
129
|
+
payload = CGI.unescape(auth_url.split("=").last)
|
130
|
+
decoded = Base64.decode64(payload)
|
131
|
+
zstream = Zlib::Inflate.new(-Zlib::MAX_WBITS)
|
132
|
+
inflated = zstream.inflate(decoded)
|
133
|
+
zstream.finish
|
134
|
+
zstream.close
|
135
|
+
|
136
|
+
assert inflated.include?('<saml:Subject>')
|
137
|
+
assert inflated.include?("<saml:NameID Format='urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress'>testuser@example.com</saml:NameID>")
|
138
|
+
assert inflated.include?("<saml:SubjectConfirmation Method='urn:oasis:names:tc:SAML:2.0:cm:bearer'/>")
|
139
|
+
end
|
140
|
+
|
124
141
|
it "accept extra parameters" do
|
125
142
|
auth_url = OneLogin::RubySaml::Authrequest.new.create(settings, { :hello => "there" })
|
126
143
|
assert_match /&hello=there$/, auth_url
|
data/test/settings_test.rb
CHANGED
@@ -15,7 +15,7 @@ class SettingsTest < Minitest::Test
|
|
15
15
|
:idp_cert, :idp_cert_fingerprint, :idp_cert_fingerprint_algorithm, :idp_cert_multi,
|
16
16
|
:idp_attribute_names, :issuer, :assertion_consumer_service_url, :assertion_consumer_service_binding,
|
17
17
|
:single_logout_service_url, :single_logout_service_binding,
|
18
|
-
:sp_name_qualifier, :name_identifier_format, :name_identifier_value,
|
18
|
+
:sp_name_qualifier, :name_identifier_format, :name_identifier_value, :name_identifier_value_requested,
|
19
19
|
:sessionindex, :attributes_index, :passive, :force_authn,
|
20
20
|
:compress_request, :double_quote_xml_attribute_values, :protocol_binding,
|
21
21
|
:security, :certificate, :private_key,
|
@@ -73,6 +73,40 @@ class SloLogoutresponseTest < Minitest::Test
|
|
73
73
|
settings.security[:embed_sign] = true
|
74
74
|
end
|
75
75
|
|
76
|
+
it "doesn't sign through create_xml_document" do
|
77
|
+
unauth_res = OneLogin::RubySaml::SloLogoutresponse.new
|
78
|
+
inflated = unauth_res.create_xml_document(settings).to_s
|
79
|
+
|
80
|
+
refute_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], inflated
|
81
|
+
refute_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1'/>], inflated
|
82
|
+
refute_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2000/09/xmldsig#sha1'/>], inflated
|
83
|
+
end
|
84
|
+
|
85
|
+
it "sign unsigned request" do
|
86
|
+
unauth_res = OneLogin::RubySaml::SloLogoutresponse.new
|
87
|
+
unauth_res_doc = unauth_res.create_xml_document(settings)
|
88
|
+
inflated = unauth_res_doc.to_s
|
89
|
+
|
90
|
+
refute_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], inflated
|
91
|
+
refute_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1'/>], inflated
|
92
|
+
refute_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2000/09/xmldsig#sha1'/>], inflated
|
93
|
+
|
94
|
+
inflated = unauth_res.sign_document(unauth_res_doc, settings).to_s
|
95
|
+
|
96
|
+
assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], inflated
|
97
|
+
assert_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1'/>], inflated
|
98
|
+
assert_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2000/09/xmldsig#sha1'/>], inflated
|
99
|
+
end
|
100
|
+
|
101
|
+
it "signs through create_logout_response_xml_doc" do
|
102
|
+
unauth_res = OneLogin::RubySaml::SloLogoutresponse.new
|
103
|
+
inflated = unauth_res.create_logout_response_xml_doc(settings).to_s
|
104
|
+
|
105
|
+
assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], inflated
|
106
|
+
assert_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1'/>], inflated
|
107
|
+
assert_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2000/09/xmldsig#sha1'/>], inflated
|
108
|
+
end
|
109
|
+
|
76
110
|
it "create a signed logout response" do
|
77
111
|
logout_request.settings = settings
|
78
112
|
params = OneLogin::RubySaml::SloLogoutresponse.new.create_params(settings, logout_request.id, "Custom Logout Message")
|
data/test/test_helper.rb
CHANGED
@@ -178,6 +178,10 @@ class Minitest::Test
|
|
178
178
|
@idp_metadata_multiple_descriptors ||= File.read(File.join(File.dirname(__FILE__), 'metadata', 'idp_multiple_descriptors.xml'))
|
179
179
|
end
|
180
180
|
|
181
|
+
def idp_metadata_multiple_descriptors2
|
182
|
+
@idp_metadata_multiple_descriptors2 ||= File.read(File.join(File.dirname(__FILE__), 'metadata', 'idp_multiple_descriptors_2.xml'))
|
183
|
+
end
|
184
|
+
|
181
185
|
def idp_metadata_multiple_certs
|
182
186
|
@idp_metadata_multiple_descriptors ||= File.read(File.join(File.dirname(__FILE__), 'metadata', 'idp_metadata_multi_certs.xml'))
|
183
187
|
end
|
data/test/utils_test.rb
CHANGED
@@ -29,6 +29,11 @@ class UtilsTest < Minitest::Test
|
|
29
29
|
assert_equal formatted_certificate, OneLogin::RubySaml::Utils.format_cert(invalid_certificate2)
|
30
30
|
end
|
31
31
|
|
32
|
+
it "returns the cert when it's encoded" do
|
33
|
+
encoded_certificate = read_certificate("certificate.der")
|
34
|
+
assert_equal encoded_certificate, OneLogin::RubySaml::Utils.format_cert(encoded_certificate)
|
35
|
+
end
|
36
|
+
|
32
37
|
it "reformats the certificate when there line breaks and no headers" do
|
33
38
|
invalid_certificate3 = read_certificate("invalid_certificate3")
|
34
39
|
assert_equal formatted_certificate, OneLogin::RubySaml::Utils.format_cert(invalid_certificate3)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-saml
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.10.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- OneLogin LLC
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2019-03-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: nokogiri
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 1.
|
19
|
+
version: 1.8.2
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 1.
|
26
|
+
version: 1.8.2
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: minitest
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -187,6 +187,7 @@ files:
|
|
187
187
|
- lib/schemas/xmldsig-core-schema.xsd
|
188
188
|
- lib/xml_security.rb
|
189
189
|
- ruby-saml.gemspec
|
190
|
+
- test/certificates/certificate.der
|
190
191
|
- test/certificates/certificate1
|
191
192
|
- test/certificates/certificate_without_head_foot
|
192
193
|
- test/certificates/formatted_certificate
|
@@ -226,6 +227,7 @@ files:
|
|
226
227
|
- test/metadata/idp_metadata_multi_signing_certs.xml
|
227
228
|
- test/metadata/idp_metadata_same_sign_and_encrypt_cert.xml
|
228
229
|
- test/metadata/idp_multiple_descriptors.xml
|
230
|
+
- test/metadata/idp_multiple_descriptors_2.xml
|
229
231
|
- test/metadata/no_idp_descriptor.xml
|
230
232
|
- test/metadata_test.rb
|
231
233
|
- test/request_test.rb
|
@@ -348,6 +350,7 @@ signing_key:
|
|
348
350
|
specification_version: 4
|
349
351
|
summary: SAML Ruby Tookit
|
350
352
|
test_files:
|
353
|
+
- test/certificates/certificate.der
|
351
354
|
- test/certificates/certificate1
|
352
355
|
- test/certificates/certificate_without_head_foot
|
353
356
|
- test/certificates/formatted_certificate
|
@@ -387,6 +390,7 @@ test_files:
|
|
387
390
|
- test/metadata/idp_metadata_multi_signing_certs.xml
|
388
391
|
- test/metadata/idp_metadata_same_sign_and_encrypt_cert.xml
|
389
392
|
- test/metadata/idp_multiple_descriptors.xml
|
393
|
+
- test/metadata/idp_multiple_descriptors_2.xml
|
390
394
|
- test/metadata/no_idp_descriptor.xml
|
391
395
|
- test/metadata_test.rb
|
392
396
|
- test/request_test.rb
|