ruby-saml 0.8.14 → 0.8.18
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/lib/onelogin/ruby-saml/authrequest.rb +5 -1
- data/lib/onelogin/ruby-saml/logoutrequest.rb +7 -2
- data/lib/onelogin/ruby-saml/logoutresponse.rb +19 -27
- data/lib/onelogin/ruby-saml/response.rb +104 -15
- data/lib/onelogin/ruby-saml/settings.rb +28 -10
- data/lib/onelogin/ruby-saml/slo_logoutrequest.rb +112 -0
- data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +21 -13
- data/lib/onelogin/ruby-saml/utils.rb +27 -0
- data/lib/onelogin/ruby-saml/version.rb +1 -1
- data/lib/ruby-saml.rb +1 -0
- data/lib/xml_security.rb +5 -1
- data/test/certificates/certificate.der +0 -0
- data/test/certificates/formatted_certificate +14 -0
- data/test/certificates/formatted_chained_certificate +42 -0
- data/test/certificates/formatted_private_key +12 -0
- data/test/certificates/formatted_rsa_private_key +12 -0
- data/test/certificates/invalid_certificate1 +1 -0
- data/test/certificates/invalid_certificate2 +1 -0
- data/test/certificates/invalid_certificate3 +12 -0
- data/test/certificates/invalid_chained_certificate1 +1 -0
- data/test/certificates/invalid_private_key1 +1 -0
- data/test/certificates/invalid_private_key2 +1 -0
- data/test/certificates/invalid_private_key3 +10 -0
- data/test/certificates/invalid_rsa_private_key1 +1 -0
- data/test/certificates/invalid_rsa_private_key2 +1 -0
- data/test/certificates/invalid_rsa_private_key3 +10 -0
- data/test/logoutrequest_test.rb +11 -0
- data/test/logoutresponse_test.rb +10 -17
- data/test/request_test.rb +10 -0
- data/test/requests/logoutrequest_fixtures.rb +47 -0
- data/test/response_test.rb +60 -0
- data/test/responses/invalids/invalid_issuer_assertion.xml.base64 +1 -0
- data/test/responses/invalids/invalid_issuer_message.xml.base64 +1 -0
- data/test/responses/logoutresponse_fixtures.rb +7 -6
- data/test/settings_test.rb +106 -0
- data/test/slo_logoutrequest_test.rb +73 -0
- data/test/slo_logoutresponse_test.rb +19 -0
- data/test/utils_test.rb +191 -1
- data/test/xml_security_test.rb +5 -0
- metadata +57 -24
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f7f02b4fb1490e44140c1e24ea61bf0ef061f0da
|
4
|
+
data.tar.gz: 3b2fec942140bb2fd2e49a17fc29dd0d0327d814
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a2dade6d6d672b213a3f8d3af088edfa208f3af4482aae033a40ff8ac9c500ca48fded7eee073e67dd6f37daa27786a6303777e0381a3338f987f87a63f460a2
|
7
|
+
data.tar.gz: 0cc6bb2264de5c688545bcb24794313758a97cca3c9bfa5c69b07331ffbc719acb1a69039ec39aa2e5e6817e8917e7495da7c07a41eff120d57f30e9708962bc
|
@@ -9,7 +9,7 @@ module OneLogin
|
|
9
9
|
|
10
10
|
class Authrequest
|
11
11
|
# AuthNRequest ID
|
12
|
-
|
12
|
+
attr_accessor :uuid
|
13
13
|
|
14
14
|
# Initializes the AuthNRequest. An Authrequest Object.
|
15
15
|
# Asigns an ID, a random uuid.
|
@@ -18,6 +18,10 @@ module OneLogin
|
|
18
18
|
@uuid = OneLogin::RubySaml::Utils.uuid
|
19
19
|
end
|
20
20
|
|
21
|
+
def request_id
|
22
|
+
@uuid
|
23
|
+
end
|
24
|
+
|
21
25
|
def create(settings, params = {})
|
22
26
|
params = create_params(settings, params)
|
23
27
|
params_prefix = (settings.idp_sso_target_url =~ /\?/) ? '&' : '?'
|
@@ -10,12 +10,16 @@ module OneLogin
|
|
10
10
|
|
11
11
|
class Logoutrequest
|
12
12
|
|
13
|
-
|
13
|
+
attr_accessor :uuid
|
14
14
|
|
15
15
|
def initialize
|
16
16
|
@uuid = OneLogin::RubySaml::Utils.uuid
|
17
17
|
end
|
18
18
|
|
19
|
+
def request_id
|
20
|
+
@uuid
|
21
|
+
end
|
22
|
+
|
19
23
|
def create(settings, params={})
|
20
24
|
params = create_params(settings, params)
|
21
25
|
params_prefix = (settings.idp_slo_target_url =~ /\?/) ? '&' : '?'
|
@@ -114,7 +118,8 @@ module OneLogin
|
|
114
118
|
|
115
119
|
if settings.name_identifier_value
|
116
120
|
name_id = root.add_element "saml:NameID", { "xmlns:saml" => "urn:oasis:names:tc:SAML:2.0:assertion" }
|
117
|
-
|
121
|
+
nameid.attributes['NameQualifier'] = settings.idp_name_qualifier if settings.idp_name_qualifier
|
122
|
+
nameid.attributes['SPNameQualifier'] = settings.sp_name_qualifier if settings.sp_name_qualifier
|
118
123
|
name_id.attributes['Format'] = settings.name_identifier_format if settings.name_identifier_format
|
119
124
|
name_id.text = settings.name_identifier_value
|
120
125
|
end
|
@@ -29,7 +29,18 @@ module OneLogin
|
|
29
29
|
|
30
30
|
@options = options
|
31
31
|
@response = OneLogin::RubySaml::Utils.decode_raw_saml(response)
|
32
|
-
@document = XMLSecurity::SignedDocument.new(response)
|
32
|
+
@document = XMLSecurity::SignedDocument.new(@response)
|
33
|
+
end
|
34
|
+
|
35
|
+
def response_id
|
36
|
+
@response_id ||= begin
|
37
|
+
node = REXML::XPath.first(
|
38
|
+
document,
|
39
|
+
"/p:LogoutResponse",
|
40
|
+
{ "p" => PROTOCOL }
|
41
|
+
)
|
42
|
+
node.nil? ? nil : node.attributes['ID']
|
43
|
+
end
|
33
44
|
end
|
34
45
|
|
35
46
|
def validate!
|
@@ -37,7 +48,7 @@ module OneLogin
|
|
37
48
|
end
|
38
49
|
|
39
50
|
def validate(soft = true)
|
40
|
-
return false unless
|
51
|
+
return false unless validate_structure(soft)
|
41
52
|
|
42
53
|
valid_in_response_to?(soft) && valid_issuer?(soft) && success?(soft)
|
43
54
|
end
|
@@ -51,7 +62,7 @@ module OneLogin
|
|
51
62
|
|
52
63
|
def in_response_to
|
53
64
|
@in_response_to ||= begin
|
54
|
-
node = REXML::XPath.first(document, "/p:LogoutResponse", { "p" => PROTOCOL
|
65
|
+
node = REXML::XPath.first(document, "/p:LogoutResponse", { "p" => PROTOCOL })
|
55
66
|
node.nil? ? nil : node.attributes['InResponseTo']
|
56
67
|
end
|
57
68
|
end
|
@@ -59,7 +70,6 @@ module OneLogin
|
|
59
70
|
def issuer
|
60
71
|
@issuer ||= begin
|
61
72
|
node = REXML::XPath.first(document, "/p:LogoutResponse/a:Issuer", { "p" => PROTOCOL, "a" => ASSERTION })
|
62
|
-
node ||= REXML::XPath.first(document, "/p:LogoutResponse/a:Assertion/a:Issuer", { "p" => PROTOCOL, "a" => ASSERTION })
|
63
73
|
Utils.element_text(node)
|
64
74
|
end
|
65
75
|
end
|
@@ -73,7 +83,7 @@ module OneLogin
|
|
73
83
|
|
74
84
|
private
|
75
85
|
|
76
|
-
def
|
86
|
+
def validate_structure(soft = true)
|
77
87
|
Dir.chdir(File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'schemas'))) do
|
78
88
|
@schema = Nokogiri::XML::Schema(IO.read('saml20protocol_schema.xsd'))
|
79
89
|
@xml = Nokogiri::XML(self.document.to_s)
|
@@ -85,26 +95,6 @@ module OneLogin
|
|
85
95
|
end
|
86
96
|
end
|
87
97
|
|
88
|
-
def valid_state?(soft = true)
|
89
|
-
if response.empty?
|
90
|
-
return soft ? false : validation_error("Blank response")
|
91
|
-
end
|
92
|
-
|
93
|
-
if settings.nil?
|
94
|
-
return soft ? false : validation_error("No settings on response")
|
95
|
-
end
|
96
|
-
|
97
|
-
if settings.sp_entity_id.nil?
|
98
|
-
return soft ? false : validation_error("No sp_entity_id in settings")
|
99
|
-
end
|
100
|
-
|
101
|
-
if settings.idp_cert_fingerprint.nil? && settings.idp_cert.nil?
|
102
|
-
return soft ? false : validation_error("No fingerprint or certificate on settings")
|
103
|
-
end
|
104
|
-
|
105
|
-
true
|
106
|
-
end
|
107
|
-
|
108
98
|
def valid_in_response_to?(soft = true)
|
109
99
|
return true unless self.options.has_key? :matches_request_id
|
110
100
|
|
@@ -116,8 +106,10 @@ module OneLogin
|
|
116
106
|
end
|
117
107
|
|
118
108
|
def valid_issuer?(soft = true)
|
119
|
-
|
120
|
-
|
109
|
+
return true if settings.nil? || settings.idp_entity_id.nil? || issuer.nil?
|
110
|
+
|
111
|
+
unless OneLogin::RubySaml::Utils.uri_match?(issuer, settings.idp_entity_id)
|
112
|
+
return soft ? false : validation_error("Doesn't match the issuer, expected: <#{self.settings.idp_entity_id}>, but was: <#{issuer}>")
|
121
113
|
end
|
122
114
|
true
|
123
115
|
end
|
@@ -27,6 +27,24 @@ module OneLogin
|
|
27
27
|
@document = XMLSecurity::SignedDocument.new(@response)
|
28
28
|
end
|
29
29
|
|
30
|
+
def response_id
|
31
|
+
@response_id ||= begin
|
32
|
+
node = REXML::XPath.first(
|
33
|
+
document,
|
34
|
+
"/p:Response",
|
35
|
+
{ "p" => PROTOCOL }
|
36
|
+
)
|
37
|
+
node.nil? ? nil : node.attributes['ID']
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def assertion_id
|
42
|
+
@assertion_id ||= begin
|
43
|
+
node = xpath_first_from_signed_assertion("")
|
44
|
+
node.nil? ? nil : node.attributes['ID']
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
30
48
|
def is_valid?
|
31
49
|
validate
|
32
50
|
end
|
@@ -35,16 +53,48 @@ module OneLogin
|
|
35
53
|
validate(false)
|
36
54
|
end
|
37
55
|
|
38
|
-
|
39
|
-
def name_id
|
56
|
+
def name_id_node
|
40
57
|
@name_id ||= begin
|
41
|
-
|
42
|
-
Utils.element_text(node)
|
58
|
+
xpath_first_from_signed_assertion('/a:Subject/a:NameID')
|
43
59
|
end
|
44
60
|
end
|
45
61
|
|
62
|
+
# The value of the user identifier as designated by the initialization request response
|
63
|
+
def name_id
|
64
|
+
@name_id ||= Utils.element_text(name_id_node)
|
65
|
+
end
|
66
|
+
|
46
67
|
alias nameid name_id
|
47
68
|
|
69
|
+
# @return [String] the NameID Format provided by the SAML response from the IdP.
|
70
|
+
#
|
71
|
+
def name_id_format
|
72
|
+
@name_id_format ||=
|
73
|
+
if name_id_node && name_id_node.attribute("Format")
|
74
|
+
name_id_node.attribute("Format").value
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
alias_method :nameid_format, :name_id_format
|
79
|
+
|
80
|
+
# @return [String] the NameID SPNameQualifier provided by the SAML response from the IdP.
|
81
|
+
#
|
82
|
+
def name_id_spnamequalifier
|
83
|
+
@name_id_spnamequalifier ||=
|
84
|
+
if name_id_node && name_id_node.attribute("SPNameQualifier")
|
85
|
+
name_id_node.attribute("SPNameQualifier").value
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# @return [String] the NameID NameQualifier provided by the SAML response from the IdP.
|
90
|
+
#
|
91
|
+
def name_id_namequalifier
|
92
|
+
@name_id_namequalifier ||=
|
93
|
+
if name_id_node && name_id_node.attribute("NameQualifier")
|
94
|
+
name_id_node.attribute("NameQualifier").value
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
48
98
|
def sessionindex
|
49
99
|
@sessionindex ||= begin
|
50
100
|
node = xpath_first_from_signed_assertion('/a:AuthnStatement')
|
@@ -134,6 +184,34 @@ module OneLogin
|
|
134
184
|
end
|
135
185
|
end
|
136
186
|
|
187
|
+
# Gets the Issuers (from Response and Assertion).
|
188
|
+
# (returns the first node that matches the supplied xpath from the Response and from the Assertion)
|
189
|
+
# @return [Array] Array with the Issuers (REXML::Element)
|
190
|
+
#
|
191
|
+
def issuers
|
192
|
+
@issuers ||= begin
|
193
|
+
issuer_response_nodes = REXML::XPath.match(
|
194
|
+
document,
|
195
|
+
"/p:Response/a:Issuer",
|
196
|
+
{ "p" => PROTOCOL, "a" => ASSERTION }
|
197
|
+
)
|
198
|
+
|
199
|
+
unless issuer_response_nodes.size == 1
|
200
|
+
error_msg = "Issuer of the Response not found or multiple."
|
201
|
+
raise ValidationError.new(error_msg)
|
202
|
+
end
|
203
|
+
|
204
|
+
issuer_assertion_nodes = xpath_from_signed_assertion("/a:Issuer")
|
205
|
+
unless issuer_assertion_nodes.size == 1
|
206
|
+
error_msg = "Issuer of the Assertion not found or multiple."
|
207
|
+
raise ValidationError.new(error_msg)
|
208
|
+
end
|
209
|
+
|
210
|
+
nodes = issuer_response_nodes + issuer_assertion_nodes
|
211
|
+
nodes.map { |node| Utils.element_text(node) }.compact.uniq
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
137
215
|
# @return [Array] The Audience elements from the Contitions of the SAML Response.
|
138
216
|
#
|
139
217
|
def audiences
|
@@ -157,6 +235,7 @@ module OneLogin
|
|
157
235
|
validate_response_state(soft) &&
|
158
236
|
validate_conditions(soft) &&
|
159
237
|
validate_audience(soft) &&
|
238
|
+
validate_issuer(soft) &&
|
160
239
|
validate_signature(soft) &&
|
161
240
|
success?
|
162
241
|
end
|
@@ -373,15 +452,6 @@ module OneLogin
|
|
373
452
|
))
|
374
453
|
end
|
375
454
|
|
376
|
-
def get_fingerprint
|
377
|
-
if settings.idp_cert
|
378
|
-
cert = OpenSSL::X509::Certificate.new(settings.idp_cert)
|
379
|
-
Digest::SHA1.hexdigest(cert.to_der).upcase.scan(/../).join(":")
|
380
|
-
else
|
381
|
-
settings.idp_cert_fingerprint
|
382
|
-
end
|
383
|
-
end
|
384
|
-
|
385
455
|
def validate_conditions(soft = true)
|
386
456
|
return true if conditions.nil?
|
387
457
|
return true if options[:skip_conditions]
|
@@ -399,6 +469,25 @@ module OneLogin
|
|
399
469
|
true
|
400
470
|
end
|
401
471
|
|
472
|
+
def validate_issuer(soft = true)
|
473
|
+
return true if settings.idp_entity_id.nil?
|
474
|
+
|
475
|
+
begin
|
476
|
+
obtained_issuers = issuers
|
477
|
+
rescue ValidationError => e
|
478
|
+
return soft ? false : validation_error("Error while extracting issuers")
|
479
|
+
end
|
480
|
+
|
481
|
+
obtained_issuers.each do |issuer|
|
482
|
+
unless OneLogin::RubySaml::Utils.uri_match?(issuer, settings.idp_entity_id)
|
483
|
+
error_msg = "Doesn't match the issuer, expected: <#{settings.idp_entity_id}>, but was: <#{issuer}>"
|
484
|
+
return soft ? false : validation_error(error_msg)
|
485
|
+
end
|
486
|
+
end
|
487
|
+
|
488
|
+
true
|
489
|
+
end
|
490
|
+
|
402
491
|
def validate_signature(soft = true)
|
403
492
|
error_msg = "Invalid Signature on SAML Response"
|
404
493
|
|
@@ -430,8 +519,8 @@ module OneLogin
|
|
430
519
|
|
431
520
|
opts = {}
|
432
521
|
opts[:fingerprint_alg] = OpenSSL::Digest::SHA1.new
|
433
|
-
opts[:cert] = settings.
|
434
|
-
fingerprint = get_fingerprint
|
522
|
+
opts[:cert] = settings.get_idp_cert
|
523
|
+
fingerprint = settings.get_fingerprint
|
435
524
|
|
436
525
|
unless fingerprint
|
437
526
|
return soft ? false : validation_error("No fingerprint or certificate on settings")
|
@@ -27,16 +27,17 @@ module OneLogin
|
|
27
27
|
attr_accessor :idp_cert_fingerprint
|
28
28
|
attr_accessor :idp_cert
|
29
29
|
attr_accessor :idp_slo_target_url
|
30
|
+
attr_accessor :idp_entity_id
|
30
31
|
#sp data
|
31
32
|
attr_accessor :sp_entity_id
|
32
33
|
attr_accessor :assertion_consumer_service_url
|
33
34
|
attr_accessor :authn_context
|
34
35
|
attr_accessor :sp_name_qualifier
|
36
|
+
attr_accessor :idp_name_qualifier
|
35
37
|
attr_accessor :name_identifier_format
|
36
38
|
attr_accessor :name_identifier_value
|
37
39
|
attr_accessor :name_identifier_value_requested
|
38
40
|
attr_accessor :sessionindex
|
39
|
-
attr_accessor :assertion_consumer_logout_service_url
|
40
41
|
attr_accessor :compress_request
|
41
42
|
attr_accessor :compress_response
|
42
43
|
attr_accessor :double_quote_xml_attribute_values
|
@@ -117,21 +118,38 @@ module OneLogin
|
|
117
118
|
@single_logout_service_binding = url
|
118
119
|
end
|
119
120
|
|
120
|
-
#
|
121
|
+
# Calculates the fingerprint of the IdP x509 certificate.
|
122
|
+
# @return [String] The fingerprint
|
121
123
|
#
|
122
|
-
def
|
123
|
-
|
124
|
+
def get_fingerprint
|
125
|
+
idp_cert_fingerprint || begin
|
126
|
+
idp_cert = get_idp_cert
|
127
|
+
if idp_cert
|
128
|
+
Digest::SHA1.hexdigest(idp_cert.to_der).upcase.scan(/../).join(":")
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
124
132
|
|
125
|
-
|
126
|
-
|
133
|
+
# @return [OpenSSL::X509::Certificate|nil] Build the IdP certificate from the settings (previously format it)
|
134
|
+
#
|
135
|
+
def get_idp_cert
|
136
|
+
return nil if idp_cert.nil?
|
137
|
+
|
138
|
+
if idp_cert.respond_to?(:to_pem)
|
139
|
+
idp_cert
|
140
|
+
else
|
141
|
+
return nil if idp_cert.empty?
|
142
|
+
formatted_cert = OneLogin::RubySaml::Utils.format_cert(idp_cert)
|
143
|
+
OpenSSL::X509::Certificate.new(formatted_cert)
|
144
|
+
end
|
127
145
|
end
|
128
146
|
|
129
|
-
# @return [OpenSSL::X509::Certificate|nil] Build the
|
147
|
+
# @return [OpenSSL::X509::Certificate|nil] Build the SP certificate from the settings (previously format it)
|
130
148
|
#
|
131
|
-
def
|
132
|
-
return nil if
|
149
|
+
def get_sp_cert
|
150
|
+
return nil if certificate.nil? || certificate.empty?
|
133
151
|
|
134
|
-
formatted_cert = OneLogin::RubySaml::Utils.format_cert(
|
152
|
+
formatted_cert = OneLogin::RubySaml::Utils.format_cert(certificate)
|
135
153
|
OpenSSL::X509::Certificate.new(formatted_cert)
|
136
154
|
end
|
137
155
|
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require "xml_security"
|
2
|
+
require "time"
|
3
|
+
|
4
|
+
# Only supports SAML 2.0
|
5
|
+
# SAML2 Logout Request (SLO IdP initiated, Parser)
|
6
|
+
module OneLogin
|
7
|
+
module RubySaml
|
8
|
+
class SloLogoutrequest
|
9
|
+
|
10
|
+
ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion"
|
11
|
+
PROTOCOL = "urn:oasis:names:tc:SAML:2.0:protocol"
|
12
|
+
|
13
|
+
# OneLogin::RubySaml::Settings Toolkit settings
|
14
|
+
attr_accessor :settings
|
15
|
+
|
16
|
+
attr_reader :document
|
17
|
+
attr_reader :request
|
18
|
+
attr_reader :options
|
19
|
+
|
20
|
+
def initialize(request, settings = nil, options = {})
|
21
|
+
raise ArgumentError.new("Request cannot be nil") if request.nil?
|
22
|
+
self.settings = settings
|
23
|
+
|
24
|
+
@options = options
|
25
|
+
@request = OneLogin::RubySaml::Utils.decode_raw_saml(request)
|
26
|
+
@document = XMLSecurity::SignedDocument.new(@request)
|
27
|
+
end
|
28
|
+
|
29
|
+
def request_id
|
30
|
+
@request_id ||= begin
|
31
|
+
node = REXML::XPath.first(
|
32
|
+
document,
|
33
|
+
"/p:LogoutRequest",
|
34
|
+
{ "p" => PROTOCOL }
|
35
|
+
)
|
36
|
+
node.nil? ? nil : node.attributes['ID']
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def validate!
|
41
|
+
validate(false)
|
42
|
+
end
|
43
|
+
|
44
|
+
def validate(soft = true)
|
45
|
+
return false unless validate_structure(soft)
|
46
|
+
|
47
|
+
valid_issuer?(soft)
|
48
|
+
end
|
49
|
+
|
50
|
+
def name_id
|
51
|
+
@name_id ||= begin
|
52
|
+
node = REXML::XPath.first(document, "/p:LogoutRequest/a:NameID", { "p" => PROTOCOL, "a" => ASSERTION })
|
53
|
+
Utils.element_text(node)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
alias_method :nameid, :name_id
|
58
|
+
|
59
|
+
def name_id_format
|
60
|
+
@name_id_node ||= REXML::XPath.first(document, "/p:LogoutRequest/a:NameID", { "p" => PROTOCOL, "a" => ASSERTION })
|
61
|
+
@name_id_format ||=
|
62
|
+
if @name_id_node && @name_id_node.attribute("Format")
|
63
|
+
@name_id_node.attribute("Format").value
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
alias_method :nameid_format, :name_id_format
|
68
|
+
|
69
|
+
def id
|
70
|
+
@id ||= begin
|
71
|
+
node = REXML::XPath.first(document, "/p:LogoutRequest", { "p" => PROTOCOL } )
|
72
|
+
node.nil? ? nil : node.attributes['ID']
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def issuer
|
77
|
+
@issuer ||= begin
|
78
|
+
node = REXML::XPath.first(document, "/p:LogoutRequest/a:Issuer", { "p" => PROTOCOL, "a" => ASSERTION })
|
79
|
+
Utils.element_text(node)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
def validate_structure(soft = true)
|
86
|
+
Dir.chdir(File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'schemas'))) do
|
87
|
+
@schema = Nokogiri::XML::Schema(IO.read('saml20protocol_schema.xsd'))
|
88
|
+
@xml = Nokogiri::XML(self.document.to_s)
|
89
|
+
end
|
90
|
+
if soft
|
91
|
+
@schema.validate(@xml).map{ return false }
|
92
|
+
else
|
93
|
+
@schema.validate(@xml).map{ |error| validation_error("#{error.message}\n\n#{@xml.to_s}") }
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def valid_issuer?(soft = true)
|
98
|
+
return true if settings.nil? || settings.idp_entity_id.nil? || issuer.nil?
|
99
|
+
|
100
|
+
unless OneLogin::RubySaml::Utils.uri_match?(issuer, settings.idp_entity_id)
|
101
|
+
return soft ? false : validation_error("Doesn't match the issuer, expected: <#{self.settings.idp_entity_id}>, but was: <#{issuer}>")
|
102
|
+
end
|
103
|
+
true
|
104
|
+
end
|
105
|
+
|
106
|
+
def validation_error(message)
|
107
|
+
raise ValidationError.new(message)
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -12,7 +12,7 @@ module OneLogin
|
|
12
12
|
class SloLogoutresponse
|
13
13
|
|
14
14
|
# Logout Response ID
|
15
|
-
|
15
|
+
attr_accessor :uuid
|
16
16
|
|
17
17
|
# Initializes the Logout Response. A SloLogoutresponse Object.
|
18
18
|
# Asigns an ID, a random uuid.
|
@@ -21,15 +21,20 @@ module OneLogin
|
|
21
21
|
@uuid = OneLogin::RubySaml::Utils.uuid
|
22
22
|
end
|
23
23
|
|
24
|
+
def response_id
|
25
|
+
@uuid
|
26
|
+
end
|
27
|
+
|
24
28
|
# Creates the Logout Response string.
|
25
29
|
# @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
|
26
30
|
# @param request_id [String] The ID of the LogoutRequest sent by this SP to the IdP. That ID will be placed as the InResponseTo in the logout response
|
27
31
|
# @param logout_message [String] The Message to be placed as StatusMessage in the logout response
|
28
32
|
# @param params [Hash] Some extra parameters to be added in the GET for example the RelayState
|
33
|
+
# @param logout_status_code [String] The StatusCode to be placed as StatusMessage in the logout response
|
29
34
|
# @return [String] Logout Request string that includes the SAMLRequest
|
30
35
|
#
|
31
|
-
def create(settings, request_id = nil, logout_message = nil, params = {})
|
32
|
-
params = create_params(settings, request_id, logout_message, params)
|
36
|
+
def create(settings, request_id = nil, logout_message = nil, params = {}, logout_status_code = nil)
|
37
|
+
params = create_params(settings, request_id, logout_message, params, logout_status_code)
|
33
38
|
params_prefix = (settings.idp_slo_target_url =~ /\?/) ? '&' : '?'
|
34
39
|
saml_response = CGI.escape(params.delete("SAMLResponse"))
|
35
40
|
response_params = "#{params_prefix}SAMLResponse=#{saml_response}"
|
@@ -45,9 +50,10 @@ module OneLogin
|
|
45
50
|
# @param request_id [String] The ID of the LogoutRequest sent by this SP to the IdP. That ID will be placed as the InResponseTo in the logout response
|
46
51
|
# @param logout_message [String] The Message to be placed as StatusMessage in the logout response
|
47
52
|
# @param params [Hash] Some extra parameters to be added in the GET for example the RelayState
|
53
|
+
# @param logout_status_code [String] The StatusCode to be placed as StatusMessage in the logout response
|
48
54
|
# @return [Hash] Parameters
|
49
55
|
#
|
50
|
-
def create_params(settings, request_id = nil, logout_message = nil, params = {})
|
56
|
+
def create_params(settings, request_id = nil, logout_message = nil, params = {}, logout_status_code = nil)
|
51
57
|
# The method expects :RelayState but sometimes we get 'RelayState' instead.
|
52
58
|
# Based on the HashWithIndifferentAccess value in Rails we could experience
|
53
59
|
# conflicts so this line will solve them.
|
@@ -58,7 +64,7 @@ module OneLogin
|
|
58
64
|
params.delete('RelayState')
|
59
65
|
end
|
60
66
|
|
61
|
-
response_doc = create_logout_response_xml_doc(settings, request_id, logout_message)
|
67
|
+
response_doc = create_logout_response_xml_doc(settings, request_id, logout_message, logout_status_code)
|
62
68
|
response_doc.context[:attribute_quote] = :quote if settings.double_quote_xml_attribute_values
|
63
69
|
|
64
70
|
response = ""
|
@@ -102,14 +108,15 @@ module OneLogin
|
|
102
108
|
# @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
|
103
109
|
# @param request_id [String] The ID of the LogoutRequest sent by this SP to the IdP. That ID will be placed as the InResponseTo in the logout response
|
104
110
|
# @param logout_message [String] The Message to be placed as StatusMessage in the logout response
|
111
|
+
# @param logout_status_code [String] The StatusCode to be placed as StatusMessage in the logout response
|
105
112
|
# @return [String] The SAMLResponse String.
|
106
113
|
#
|
107
|
-
def create_logout_response_xml_doc(settings, request_id = nil, logout_message = nil)
|
108
|
-
document = create_xml_document(settings, request_id, logout_message)
|
114
|
+
def create_logout_response_xml_doc(settings, request_id = nil, logout_message = nil, logout_status_code = nil)
|
115
|
+
document = create_xml_document(settings, request_id, logout_message, logout_status_code)
|
109
116
|
sign_document(document, settings)
|
110
117
|
end
|
111
118
|
|
112
|
-
def create_xml_document(settings, request_id = nil, logout_message = nil)
|
119
|
+
def create_xml_document(settings, request_id = nil, logout_message = nil, status_code = nil)
|
113
120
|
time = Time.now.utc.strftime('%Y-%m-%dT%H:%M:%SZ')
|
114
121
|
|
115
122
|
response_doc = XMLSecurity::Document.new
|
@@ -127,14 +134,15 @@ module OneLogin
|
|
127
134
|
issuer.text = settings.sp_entity_id
|
128
135
|
end
|
129
136
|
|
130
|
-
# add
|
137
|
+
# add status
|
131
138
|
status = root.add_element 'samlp:Status'
|
132
139
|
|
133
|
-
#
|
134
|
-
status_code
|
135
|
-
|
140
|
+
# status code
|
141
|
+
status_code ||= 'urn:oasis:names:tc:SAML:2.0:status:Success'
|
142
|
+
status_code_elem = status.add_element 'samlp:StatusCode'
|
143
|
+
status_code_elem.attributes['Value'] = status_code
|
136
144
|
|
137
|
-
#
|
145
|
+
# status message
|
138
146
|
logout_message ||= 'Successfully Signed Out'
|
139
147
|
status_message = status.add_element 'samlp:StatusMessage'
|
140
148
|
status_message.text = logout_message
|
@@ -179,6 +179,33 @@ module OneLogin
|
|
179
179
|
Zlib::Deflate.deflate(inflated, 9)[2..-5]
|
180
180
|
end
|
181
181
|
|
182
|
+
# Given two strings, attempt to match them as URIs using Rails' parse method. If they can be parsed,
|
183
|
+
# then the fully-qualified domain name and the host should performa a case-insensitive match, per the
|
184
|
+
# RFC for URIs. If Rails can not parse the string in to URL pieces, return a boolean match of the
|
185
|
+
# two strings. This maintains the previous functionality.
|
186
|
+
# @return [Boolean]
|
187
|
+
def self.uri_match?(destination_url, settings_url)
|
188
|
+
dest_uri = URI.parse(destination_url)
|
189
|
+
acs_uri = URI.parse(settings_url)
|
190
|
+
|
191
|
+
if dest_uri.scheme.nil? || acs_uri.scheme.nil? || dest_uri.host.nil? || acs_uri.host.nil?
|
192
|
+
raise URI::InvalidURIError
|
193
|
+
else
|
194
|
+
dest_uri.scheme.downcase == acs_uri.scheme.downcase &&
|
195
|
+
dest_uri.host.downcase == acs_uri.host.downcase &&
|
196
|
+
dest_uri.path == acs_uri.path &&
|
197
|
+
dest_uri.query == acs_uri.query
|
198
|
+
end
|
199
|
+
rescue URI::InvalidURIError
|
200
|
+
original_uri_match?(destination_url, settings_url)
|
201
|
+
end
|
202
|
+
|
203
|
+
# If Rails' URI.parse can't match to valid URL, default back to the original matching service.
|
204
|
+
# @return [Boolean]
|
205
|
+
def self.original_uri_match?(destination_url, settings_url)
|
206
|
+
destination_url == settings_url
|
207
|
+
end
|
208
|
+
|
182
209
|
end
|
183
210
|
end
|
184
211
|
end
|
data/lib/ruby-saml.rb
CHANGED
@@ -2,6 +2,7 @@ require 'onelogin/ruby-saml/logging'
|
|
2
2
|
require 'onelogin/ruby-saml/authrequest'
|
3
3
|
require 'onelogin/ruby-saml/logoutrequest'
|
4
4
|
require 'onelogin/ruby-saml/logoutresponse'
|
5
|
+
require 'onelogin/ruby-saml/slo_logoutrequest'
|
5
6
|
require 'onelogin/ruby-saml/slo_logoutresponse'
|
6
7
|
require 'onelogin/ruby-saml/response'
|
7
8
|
require 'onelogin/ruby-saml/settings'
|
data/lib/xml_security.rb
CHANGED
@@ -222,7 +222,11 @@ module XMLSecurity
|
|
222
222
|
end
|
223
223
|
else
|
224
224
|
if options[:cert]
|
225
|
-
|
225
|
+
cert = options[:cert]
|
226
|
+
if cert.is_a? String
|
227
|
+
cert = OpenSSL::X509::Certificate.new(cert)
|
228
|
+
end
|
229
|
+
base64_cert = Base64.encode64(cert.to_pem)
|
226
230
|
else
|
227
231
|
return soft ? false : (raise OneLogin::RubySaml::ValidationError.new("Certificate element missing in response (ds:X509Certificate) and not cert provided at settings"))
|
228
232
|
end
|
Binary file
|
@@ -0,0 +1,14 @@
|
|
1
|
+
-----BEGIN CERTIFICATE-----
|
2
|
+
MIICPDCCAaWgAwIBAgIIEiC/9HMAWWAwDQYJKoZIhvcNAQEFBQAwTzELMAkGA1UE
|
3
|
+
BhMCVVMxDDAKBgNVBAoTA2libTEMMAoGA1UECxMDc3NvMSQwIgYDVQQDExtjMjVh
|
4
|
+
MDI3Ny50b3JvbnRvLmNhLmlibS5jb20wHhcNMTEwNTI0MTYzNTQ4WhcNMjEwNTIx
|
5
|
+
wsQMPBj4WQTNzTYMCQYDVQQGEwJVUzEMMAoGA1UEChMDaWJtMQwwCgYDVQQLEwNz
|
6
|
+
c28xJDAiBgNVBAMTG2MyNWEwMjc3LnRvcm9udG8uY2EuaWJtLmNvbTCBnzANBgkq
|
7
|
+
hkiG9w0BAQEFAAOBjQAwgYkCgYEAgzfYQZuf5FVdJTcrsIQZ+YHTPjOsw2MGo0jC
|
8
|
+
mdGMcp4brWeFgk1OVaOmytPx6P76wHWR436AleX3crHBPd8gPxuZdnvBQ7PkrKpw
|
9
|
+
Vvaq52juenFrho8JY0TeVgVkY5jAh45YzytjP2y2k/cGQurI/56NT0PpQJ0S1G3N
|
10
|
+
4eTg718CAwEAAaMhMB8wHQYDVR0OBBYEFCYVLJqcJ7WgdzGIsuJ/TzDGDqinMA0G
|
11
|
+
CSqGSIb3DQEBBQUAA4GBAB80bIePf+qWDvWe+9bEEnbFTw7pCknLexxZ0AMqrsmZ
|
12
|
+
+4jmI+evP1JZYCjfIg9X+MBH01hfp5dFcetz3o6w6SkV+BxLYLgfcy5KUcYsIM/1
|
13
|
+
2Zkedj87bS1glzOy5B89pKD2DMbu6828Abzgc+4lyQ2ASifsqM4cZdVayzo8n+dQ
|
14
|
+
-----END CERTIFICATE-----
|