ruby-saml 0.8.9 → 0.8.10
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 +5 -5
- data/Gemfile +9 -1
- data/lib/onelogin/ruby-saml/authrequest.rb +82 -17
- data/lib/onelogin/ruby-saml/logoutrequest.rb +90 -18
- data/lib/onelogin/ruby-saml/settings.rb +73 -12
- data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +157 -0
- data/lib/onelogin/ruby-saml/utils.rb +79 -0
- data/lib/onelogin/ruby-saml/version.rb +1 -1
- data/lib/ruby-saml.rb +2 -1
- data/lib/xml_security.rb +151 -28
- data/test/certificates/ruby-saml.crt +14 -0
- data/test/certificates/ruby-saml.key +15 -0
- data/test/logoutrequest_test.rb +176 -41
- data/test/logoutresponse_test.rb +2 -1
- data/test/request_test.rb +100 -37
- data/test/response_test.rb +1 -1
- data/test/slo_logoutresponse_test.rb +226 -0
- data/test/test_helper.rb +37 -1
- metadata +10 -4
@@ -1,15 +1,94 @@
|
|
1
|
+
if RUBY_VERSION < '1.9'
|
2
|
+
require 'uuid'
|
3
|
+
else
|
4
|
+
require 'securerandom'
|
5
|
+
end
|
6
|
+
|
1
7
|
module OneLogin
|
2
8
|
module RubySaml
|
3
9
|
|
4
10
|
# SAML2 Auxiliary class
|
5
11
|
#
|
6
12
|
class Utils
|
13
|
+
@@uuid_generator = UUID.new if RUBY_VERSION < '1.9'
|
14
|
+
|
7
15
|
# Given a REXML::Element instance, return the concatenation of all child text nodes. Assumes
|
8
16
|
# that there all children other than text nodes can be ignored (e.g. comments). If nil is
|
9
17
|
# passed, nil will be returned.
|
10
18
|
def self.element_text(element)
|
11
19
|
element.texts.map(&:value).join if element
|
12
20
|
end
|
21
|
+
|
22
|
+
# Return a properly formatted x509 certificate
|
23
|
+
#
|
24
|
+
# @param cert [String] The original certificate
|
25
|
+
# @return [String] The formatted certificate
|
26
|
+
#
|
27
|
+
def self.format_cert(cert)
|
28
|
+
# don't try to format an encoded certificate or if is empty or nil
|
29
|
+
if cert.respond_to?(:ascii_only?)
|
30
|
+
return cert if cert.nil? || cert.empty? || !cert.ascii_only?
|
31
|
+
else
|
32
|
+
return cert if cert.nil? || cert.empty? || cert.match(/\x0d/)
|
33
|
+
end
|
34
|
+
|
35
|
+
if cert.scan(/BEGIN CERTIFICATE/).length > 1
|
36
|
+
formatted_cert = []
|
37
|
+
cert.scan(/-{5}BEGIN CERTIFICATE-{5}[\n\r]?.*?-{5}END CERTIFICATE-{5}[\n\r]?/m) {|c|
|
38
|
+
formatted_cert << format_cert(c)
|
39
|
+
}
|
40
|
+
formatted_cert.join("\n")
|
41
|
+
else
|
42
|
+
cert = cert.gsub(/\-{5}\s?(BEGIN|END) CERTIFICATE\s?\-{5}/, "")
|
43
|
+
cert = cert.gsub(/\r/, "")
|
44
|
+
cert = cert.gsub(/\n/, "")
|
45
|
+
cert = cert.gsub(/\s/, "")
|
46
|
+
cert = cert.scan(/.{1,64}/)
|
47
|
+
cert = cert.join("\n")
|
48
|
+
"-----BEGIN CERTIFICATE-----\n#{cert}\n-----END CERTIFICATE-----"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Return a properly formatted private key
|
53
|
+
#
|
54
|
+
# @param key [String] The original private key
|
55
|
+
# @return [String] The formatted private key
|
56
|
+
#
|
57
|
+
def self.format_private_key(key)
|
58
|
+
# don't try to format an encoded private key or if is empty
|
59
|
+
return key if key.nil? || key.empty? || key.match(/\x0d/)
|
60
|
+
|
61
|
+
# is this an rsa key?
|
62
|
+
rsa_key = key.match("RSA PRIVATE KEY")
|
63
|
+
key = key.gsub(/\-{5}\s?(BEGIN|END)( RSA)? PRIVATE KEY\s?\-{5}/, "")
|
64
|
+
key = key.gsub(/\n/, "")
|
65
|
+
key = key.gsub(/\r/, "")
|
66
|
+
key = key.gsub(/\s/, "")
|
67
|
+
key = key.scan(/.{1,64}/)
|
68
|
+
key = key.join("\n")
|
69
|
+
key_label = rsa_key ? "RSA PRIVATE KEY" : "PRIVATE KEY"
|
70
|
+
"-----BEGIN #{key_label}-----\n#{key}\n-----END #{key_label}-----"
|
71
|
+
end
|
72
|
+
|
73
|
+
# Build the Query String signature that will be used in the HTTP-Redirect binding
|
74
|
+
# to generate the Signature
|
75
|
+
# @param params [Hash] Parameters to build the Query String
|
76
|
+
# @option params [String] :type 'SAMLRequest' or 'SAMLResponse'
|
77
|
+
# @option params [String] :data Base64 encoded SAMLRequest or SAMLResponse
|
78
|
+
# @option params [String] :relay_state The RelayState parameter
|
79
|
+
# @option params [String] :sig_alg The SigAlg parameter
|
80
|
+
# @return [String] The Query String
|
81
|
+
#
|
82
|
+
def self.build_query(params)
|
83
|
+
type, data, relay_state, sig_alg = [:type, :data, :relay_state, :sig_alg].map { |k| params[k]}
|
84
|
+
url_string = "#{type}=#{CGI.escape(data)}"
|
85
|
+
url_string << "&RelayState=#{CGI.escape(relay_state)}" if relay_state
|
86
|
+
url_string << "&SigAlg=#{CGI.escape(sig_alg)}"
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.uuid
|
90
|
+
RUBY_VERSION < '1.9' ? "_#{@@uuid_generator.generate}" : "_#{SecureRandom.uuid}"
|
91
|
+
end
|
13
92
|
end
|
14
93
|
end
|
15
94
|
end
|
data/lib/ruby-saml.rb
CHANGED
@@ -2,10 +2,11 @@ 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_logoutresponse'
|
5
6
|
require 'onelogin/ruby-saml/response'
|
6
7
|
require 'onelogin/ruby-saml/settings'
|
7
8
|
require 'onelogin/ruby-saml/utils'
|
8
9
|
require 'onelogin/ruby-saml/validation_error'
|
9
10
|
require 'onelogin/ruby-saml/metadata'
|
10
11
|
require 'onelogin/ruby-saml/version'
|
11
|
-
require 'onelogin/ruby-saml/attributes'
|
12
|
+
require 'onelogin/ruby-saml/attributes'
|
data/lib/xml_security.rb
CHANGED
@@ -34,17 +34,153 @@ require "onelogin/ruby-saml/utils"
|
|
34
34
|
|
35
35
|
module XMLSecurity
|
36
36
|
|
37
|
-
class
|
38
|
-
|
39
|
-
|
37
|
+
class BaseDocument < REXML::Document
|
38
|
+
REXML::Document::entity_expansion_limit = 0
|
39
|
+
|
40
|
+
C14N = "http://www.w3.org/2001/10/xml-exc-c14n#"
|
41
|
+
DSIG = "http://www.w3.org/2000/09/xmldsig#"
|
42
|
+
NOKOGIRI_OPTIONS = Nokogiri::XML::ParseOptions::STRICT |
|
43
|
+
Nokogiri::XML::ParseOptions::NONET
|
44
|
+
|
45
|
+
def canon_algorithm(element)
|
46
|
+
algorithm = element
|
47
|
+
if algorithm.is_a?(REXML::Element)
|
48
|
+
algorithm = element.attribute('Algorithm').value
|
49
|
+
end
|
50
|
+
|
51
|
+
case algorithm
|
52
|
+
when "http://www.w3.org/TR/2001/REC-xml-c14n-20010315",
|
53
|
+
"http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"
|
54
|
+
Nokogiri::XML::XML_C14N_1_0
|
55
|
+
when "http://www.w3.org/2006/12/xml-c14n11",
|
56
|
+
"http://www.w3.org/2006/12/xml-c14n11#WithComments"
|
57
|
+
Nokogiri::XML::XML_C14N_1_1
|
58
|
+
else
|
59
|
+
Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def algorithm(element)
|
64
|
+
algorithm = element
|
65
|
+
if algorithm.is_a?(REXML::Element)
|
66
|
+
algorithm = element.attribute("Algorithm").value
|
67
|
+
end
|
68
|
+
|
69
|
+
algorithm = algorithm && algorithm =~ /(rsa-)?sha(.*?)$/i && $2.to_i
|
70
|
+
|
71
|
+
case algorithm
|
72
|
+
when 256 then OpenSSL::Digest::SHA256
|
73
|
+
when 384 then OpenSSL::Digest::SHA384
|
74
|
+
when 512 then OpenSSL::Digest::SHA512
|
75
|
+
else
|
76
|
+
OpenSSL::Digest::SHA1
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
class Document < BaseDocument
|
82
|
+
RSA_SHA1 = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"
|
83
|
+
RSA_SHA256 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
|
84
|
+
RSA_SHA384 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384"
|
85
|
+
RSA_SHA512 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512"
|
86
|
+
SHA1 = "http://www.w3.org/2000/09/xmldsig#sha1"
|
87
|
+
SHA256 = 'http://www.w3.org/2001/04/xmlenc#sha256'
|
88
|
+
SHA384 = "http://www.w3.org/2001/04/xmldsig-more#sha384"
|
89
|
+
SHA512 = 'http://www.w3.org/2001/04/xmlenc#sha512'
|
90
|
+
ENVELOPED_SIG = "http://www.w3.org/2000/09/xmldsig#enveloped-signature"
|
91
|
+
INC_PREFIX_LIST = "#default samlp saml ds xs xsi md"
|
92
|
+
|
93
|
+
attr_writer :uuid
|
94
|
+
|
95
|
+
def uuid
|
96
|
+
@uuid ||= begin
|
97
|
+
document.root.nil? ? nil : document.root.attributes['ID']
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def sign_document(private_key, certificate, signature_method = RSA_SHA1, digest_method = SHA1)
|
102
|
+
noko = Nokogiri::XML(self.to_s) do |config|
|
103
|
+
config.options = NOKOGIRI_OPTIONS
|
104
|
+
end
|
105
|
+
|
106
|
+
signature_element = REXML::Element.new("ds:Signature").add_namespace('ds', DSIG)
|
107
|
+
signed_info_element = signature_element.add_element("ds:SignedInfo")
|
108
|
+
signed_info_element.add_element("ds:CanonicalizationMethod", {"Algorithm" => C14N})
|
109
|
+
signed_info_element.add_element("ds:SignatureMethod", {"Algorithm"=>signature_method})
|
110
|
+
|
111
|
+
# Add Reference
|
112
|
+
reference_element = signed_info_element.add_element("ds:Reference", {"URI" => "##{uuid}"})
|
113
|
+
|
114
|
+
# Add Transforms
|
115
|
+
transforms_element = reference_element.add_element("ds:Transforms")
|
116
|
+
transforms_element.add_element("ds:Transform", {"Algorithm" => ENVELOPED_SIG})
|
117
|
+
c14element = transforms_element.add_element("ds:Transform", {"Algorithm" => C14N})
|
118
|
+
c14element.add_element("ec:InclusiveNamespaces", {"xmlns:ec" => C14N, "PrefixList" => INC_PREFIX_LIST})
|
119
|
+
|
120
|
+
digest_method_element = reference_element.add_element("ds:DigestMethod", {"Algorithm" => digest_method})
|
121
|
+
inclusive_namespaces = INC_PREFIX_LIST.split(" ")
|
122
|
+
canon_doc = noko.canonicalize(canon_algorithm(C14N), inclusive_namespaces)
|
123
|
+
reference_element.add_element("ds:DigestValue").text = compute_digest(canon_doc, algorithm(digest_method_element))
|
124
|
+
|
125
|
+
# add SignatureValue
|
126
|
+
noko_sig_element = Nokogiri::XML(signature_element.to_s) do |config|
|
127
|
+
config.options = NOKOGIRI_OPTIONS
|
128
|
+
end
|
129
|
+
|
130
|
+
noko_signed_info_element = noko_sig_element.at_xpath('//ds:Signature/ds:SignedInfo', 'ds' => DSIG)
|
131
|
+
canon_string = noko_signed_info_element.canonicalize(canon_algorithm(C14N))
|
132
|
+
|
133
|
+
signature = compute_signature(private_key, algorithm(signature_method).new, canon_string)
|
134
|
+
signature_element.add_element("ds:SignatureValue").text = signature
|
135
|
+
|
136
|
+
# add KeyInfo
|
137
|
+
key_info_element = signature_element.add_element("ds:KeyInfo")
|
138
|
+
x509_element = key_info_element.add_element("ds:X509Data")
|
139
|
+
x509_cert_element = x509_element.add_element("ds:X509Certificate")
|
140
|
+
if certificate.is_a?(String)
|
141
|
+
certificate = OpenSSL::X509::Certificate.new(certificate)
|
142
|
+
end
|
143
|
+
x509_cert_element.text = Base64.encode64(certificate.to_der).gsub(/\n/, "")
|
144
|
+
|
145
|
+
# add the signature
|
146
|
+
issuer_element = self.elements["//saml:Issuer"]
|
147
|
+
if issuer_element
|
148
|
+
self.root.insert_after issuer_element, signature_element
|
149
|
+
else
|
150
|
+
if sp_sso_descriptor = self.elements["/md:EntityDescriptor"]
|
151
|
+
self.root.insert_before sp_sso_descriptor, signature_element
|
152
|
+
else
|
153
|
+
self.root.add_element(signature_element)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
protected
|
159
|
+
|
160
|
+
def compute_signature(private_key, signature_algorithm, document)
|
161
|
+
Base64.encode64(private_key.sign(signature_algorithm, document)).gsub(/\n/, "")
|
162
|
+
end
|
163
|
+
|
164
|
+
def compute_digest(document, digest_algorithm)
|
165
|
+
digest = digest_algorithm.digest(document)
|
166
|
+
Base64.encode64(digest).strip!
|
167
|
+
end
|
168
|
+
|
169
|
+
end
|
170
|
+
|
171
|
+
class SignedDocument < BaseDocument
|
40
172
|
|
41
|
-
|
173
|
+
attr_writer :signed_element_id
|
42
174
|
|
43
175
|
def initialize(response)
|
44
176
|
super(response)
|
45
177
|
extract_signed_element_id
|
46
178
|
end
|
47
179
|
|
180
|
+
def signed_element_id
|
181
|
+
@signed_element_id ||= extract_signed_element_id
|
182
|
+
end
|
183
|
+
|
48
184
|
def validate_document(idp_cert_fingerprint, soft = true)
|
49
185
|
# get cert from response
|
50
186
|
cert_element = REXML::XPath.first(self, "//ds:X509Certificate", { "ds"=>DSIG })
|
@@ -69,7 +205,9 @@ module XMLSecurity
|
|
69
205
|
# check for inclusive namespaces
|
70
206
|
inclusive_namespaces = extract_inclusive_namespaces
|
71
207
|
|
72
|
-
document = Nokogiri
|
208
|
+
document = Nokogiri::XML(self.to_s) do |config|
|
209
|
+
config.options = NOKOGIRI_OPTIONS
|
210
|
+
end
|
73
211
|
|
74
212
|
# create a working copy so we don't modify the original
|
75
213
|
@working_copy ||= REXML::Document.new(self.to_s).root
|
@@ -80,7 +218,6 @@ module XMLSecurity
|
|
80
218
|
element.remove
|
81
219
|
end
|
82
220
|
|
83
|
-
|
84
221
|
# verify signature
|
85
222
|
signed_info_element = REXML::XPath.first(@sig_element, "//ds:SignedInfo", {"ds"=>DSIG})
|
86
223
|
noko_sig_element = document.at_xpath('//ds:Signature', 'ds' => DSIG)
|
@@ -131,30 +268,16 @@ module XMLSecurity
|
|
131
268
|
end
|
132
269
|
|
133
270
|
def extract_signed_element_id
|
134
|
-
reference_element
|
135
|
-
|
136
|
-
|
271
|
+
reference_element = REXML::XPath.first(
|
272
|
+
self,
|
273
|
+
"//ds:Signature/ds:SignedInfo/ds:Reference",
|
274
|
+
{"ds"=>DSIG}
|
275
|
+
)
|
137
276
|
|
138
|
-
|
139
|
-
algorithm = element.attribute('Algorithm').value if element
|
140
|
-
case algorithm
|
141
|
-
when "http://www.w3.org/2001/10/xml-exc-c14n#" then Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0
|
142
|
-
when "http://www.w3.org/TR/2001/REC-xml-c14n-20010315" then Nokogiri::XML::XML_C14N_1_0
|
143
|
-
when "http://www.w3.org/2006/12/xml-c14n11" then Nokogiri::XML::XML_C14N_1_1
|
144
|
-
else Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0
|
145
|
-
end
|
146
|
-
end
|
277
|
+
return nil if reference_element.nil?
|
147
278
|
|
148
|
-
|
149
|
-
|
150
|
-
algorithm = algorithm && algorithm =~ /sha(.*?)$/i && $1.to_i
|
151
|
-
case algorithm
|
152
|
-
when 256 then OpenSSL::Digest::SHA256
|
153
|
-
when 384 then OpenSSL::Digest::SHA384
|
154
|
-
when 512 then OpenSSL::Digest::SHA512
|
155
|
-
else
|
156
|
-
OpenSSL::Digest::SHA1
|
157
|
-
end
|
279
|
+
sei = reference_element.attribute("URI").value[1..-1]
|
280
|
+
sei.nil? ? reference_element.parent.parent.parent.attribute("ID").value : sei
|
158
281
|
end
|
159
282
|
|
160
283
|
def extract_inclusive_namespaces
|
@@ -0,0 +1,14 @@
|
|
1
|
+
-----BEGIN CERTIFICATE-----
|
2
|
+
MIICGzCCAYQCCQCNNcQXom32VDANBgkqhkiG9w0BAQUFADBSMQswCQYDVQQGEwJV
|
3
|
+
UzELMAkGA1UECBMCSU4xFTATBgNVBAcTDEluZGlhbmFwb2xpczERMA8GA1UEChMI
|
4
|
+
T25lTG9naW4xDDAKBgNVBAsTA0VuZzAeFw0xNDA0MjMxODQxMDFaFw0xNTA0MjMx
|
5
|
+
ODQxMDFaMFIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJJTjEVMBMGA1UEBxMMSW5k
|
6
|
+
aWFuYXBvbGlzMREwDwYDVQQKEwhPbmVMb2dpbjEMMAoGA1UECxMDRW5nMIGfMA0G
|
7
|
+
CSqGSIb3DQEBAQUAA4GNADCBiQKBgQDo6m+QZvYQ/xL0ElLgupK1QDcYL4f5Pckw
|
8
|
+
sNgS9pUvV7fzTqCHk8ThLxTk42MQ2McJsOeUJVP728KhymjFCqxgP4VuwRk9rpAl
|
9
|
+
0+mhy6MPdyjyA6G14jrDWS65ysLchK4t/vwpEDz0SQlEoG1kMzllSm7zZS3XregA
|
10
|
+
7DjNaUYQqwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBALM2vGCiQ/vm+a6v40+VX2zd
|
11
|
+
qHA2Q/1vF1ibQzJ54MJCOVWvs+vQXfZFhdm0OPM2IrDU7oqvKPqP6xOAeJK6H0yP
|
12
|
+
7M4YL3fatSvIYmmfyXC9kt3Svz/NyrHzPhUnJ0ye/sUSXxnzQxwcm/9PwAqrQaA3
|
13
|
+
QpQkH57ybF/OoryPe+2h
|
14
|
+
-----END CERTIFICATE-----
|
@@ -0,0 +1,15 @@
|
|
1
|
+
-----BEGIN RSA PRIVATE KEY-----
|
2
|
+
MIICXAIBAAKBgQDo6m+QZvYQ/xL0ElLgupK1QDcYL4f5PckwsNgS9pUvV7fzTqCH
|
3
|
+
k8ThLxTk42MQ2McJsOeUJVP728KhymjFCqxgP4VuwRk9rpAl0+mhy6MPdyjyA6G1
|
4
|
+
4jrDWS65ysLchK4t/vwpEDz0SQlEoG1kMzllSm7zZS3XregA7DjNaUYQqwIDAQAB
|
5
|
+
AoGBALGR6bRBit+yV5TUU3MZSrf8WQSLWDLgs/33FQSAEYSib4+DJke2lKbI6jkG
|
6
|
+
UoSJgFUXFbaQLtMY2+3VDsMKPBdAge9gIdvbkC4yoKjLGm/FBDOxxZcfLpR+9OPq
|
7
|
+
U3qM9D0CNuliBWI7Je+p/zs09HIYucpDXy9E18KA1KNF6rfhAkEA9KoNam6wAKnm
|
8
|
+
vMzz31ws3RuIOUeo2rx6aaVY95+P9tTxd6U+pNkwxy1aCGP+InVSwlYNA1aQ4Axi
|
9
|
+
/GdMIWMkxwJBAPO1CP7cQNZQmu7yusY+GUObDII5YK9WLaY4RAicn5378crPBFxv
|
10
|
+
Ukqf9G6FHo7u88iTCIp+vwa3Hn9Tumg3iP0CQQDgUXWBasCVqzCxU5wY4tMDWjXY
|
11
|
+
hpoLCpmVeRML3dDJt004rFm2HKe7Rhpw7PTZNQZOxUSjFeA4e0LaNf838UWLAkB8
|
12
|
+
QfbHM3ffjhOg96PhhjINdVWoZCb230LBOHj/xxPfUmFTHcBEfQIBSJMxcrBFAnLL
|
13
|
+
9qPpMXymqOFk3ETz9DTlAj8E0qGbp78aVbTOtuwEwNJII+RPw+Zkc+lKR+yaWkAz
|
14
|
+
fIXw527NPHH3+rnBG72wyZr9ud4LAum9jh+5No1LQpk=
|
15
|
+
-----END RSA PRIVATE KEY-----
|
data/test/logoutrequest_test.rb
CHANGED
@@ -1,14 +1,17 @@
|
|
1
1
|
require File.expand_path(File.join(File.dirname(__FILE__), "test_helper"))
|
2
|
+
require 'uuid'
|
2
3
|
|
3
|
-
class
|
4
|
+
class LogoutRequestTest < Minitest::Test
|
4
5
|
|
5
|
-
|
6
|
-
settings
|
6
|
+
describe "Logoutrequest" do
|
7
|
+
let(:settings) { OneLogin::RubySaml::Settings.new }
|
7
8
|
|
8
|
-
|
9
|
+
before do
|
9
10
|
settings.idp_slo_target_url = "http://unauth.com/logout"
|
10
11
|
settings.name_identifier_value = "f00f00"
|
12
|
+
end
|
11
13
|
|
14
|
+
it "create the deflated SAMLRequest URL parameter" do
|
12
15
|
unauth_url = OneLogin::RubySaml::Logoutrequest.new.create(settings)
|
13
16
|
assert unauth_url =~ /^http:\/\/unauth\.com\/logout\?SAMLRequest=/
|
14
17
|
|
@@ -17,8 +20,7 @@ class RequestTest < Test::Unit::TestCase
|
|
17
20
|
assert_match /^<samlp:LogoutRequest/, inflated
|
18
21
|
end
|
19
22
|
|
20
|
-
|
21
|
-
|
23
|
+
it "support additional params" do
|
22
24
|
unauth_url = OneLogin::RubySaml::Logoutrequest.new.create(settings, { :hello => nil })
|
23
25
|
assert unauth_url =~ /&hello=$/
|
24
26
|
|
@@ -26,8 +28,7 @@ class RequestTest < Test::Unit::TestCase
|
|
26
28
|
assert unauth_url =~ /&foo=bar$/
|
27
29
|
end
|
28
30
|
|
29
|
-
|
30
|
-
settings.idp_slo_target_url = "http://example.com"
|
31
|
+
it "set sessionindex" do
|
31
32
|
sessionidx = UUID.new.generate
|
32
33
|
settings.sessionindex = sessionidx
|
33
34
|
|
@@ -38,9 +39,7 @@ class RequestTest < Test::Unit::TestCase
|
|
38
39
|
assert_match %r(#{sessionidx}</samlp:SessionIndex>), inflated
|
39
40
|
end
|
40
41
|
|
41
|
-
|
42
|
-
settings = OneLogin::RubySaml::Settings.new
|
43
|
-
settings.idp_slo_target_url = "http://example.com"
|
42
|
+
it "set name_identifier_value" do
|
44
43
|
settings.name_identifier_format = "transient"
|
45
44
|
name_identifier_value = "abc123"
|
46
45
|
settings.name_identifier_value = name_identifier_value
|
@@ -52,41 +51,25 @@ class RequestTest < Test::Unit::TestCase
|
|
52
51
|
assert_match %r(#{name_identifier_value}</saml:NameID>), inflated
|
53
52
|
end
|
54
53
|
|
55
|
-
|
56
|
-
|
57
|
-
settings.idp_slo_target_url = "http://example.com"
|
58
|
-
settings.name_identifier_format = nil
|
59
|
-
|
60
|
-
assert_raises(OneLogin::RubySaml::ValidationError) { OneLogin::RubySaml::Logoutrequest.new.create(settings) }
|
61
|
-
end
|
62
|
-
|
63
|
-
context "when the target url doesn't contain a query string" do
|
64
|
-
should "create the SAMLRequest parameter correctly" do
|
65
|
-
settings = OneLogin::RubySaml::Settings.new
|
54
|
+
describe "when the target url doesn't contain a query string" do
|
55
|
+
it "create the SAMLRequest parameter correctly" do
|
66
56
|
settings.idp_slo_target_url = "http://example.com"
|
67
|
-
settings.name_identifier_value = "f00f00"
|
68
|
-
|
69
57
|
unauth_url = OneLogin::RubySaml::Logoutrequest.new.create(settings)
|
70
58
|
assert unauth_url =~ /^http:\/\/example.com\?SAMLRequest/
|
71
59
|
end
|
72
60
|
end
|
73
61
|
|
74
|
-
|
75
|
-
|
76
|
-
settings = OneLogin::RubySaml::Settings.new
|
62
|
+
describe "when the target url contains a query string" do
|
63
|
+
it "create the SAMLRequest parameter correctly" do
|
77
64
|
settings.idp_slo_target_url = "http://example.com?field=value"
|
78
|
-
settings.name_identifier_value = "f00f00"
|
79
|
-
|
80
65
|
unauth_url = OneLogin::RubySaml::Logoutrequest.new.create(settings)
|
81
66
|
assert unauth_url =~ /^http:\/\/example.com\?field=value&SAMLRequest/
|
82
67
|
end
|
83
68
|
end
|
84
69
|
|
85
|
-
|
86
|
-
|
87
|
-
settings = OneLogin::RubySaml::Settings.new
|
70
|
+
describe "consumation of logout may need to track the transaction" do
|
71
|
+
it "have access to the request uuid" do
|
88
72
|
settings.idp_slo_target_url = "http://example.com?field=value"
|
89
|
-
settings.name_identifier_value = "f00f00"
|
90
73
|
|
91
74
|
unauth_req = OneLogin::RubySaml::Logoutrequest.new
|
92
75
|
unauth_url = unauth_req.create(settings)
|
@@ -97,15 +80,167 @@ class RequestTest < Test::Unit::TestCase
|
|
97
80
|
end
|
98
81
|
end
|
99
82
|
|
100
|
-
|
101
|
-
|
102
|
-
|
83
|
+
describe "when the settings indicate to sign (embedded) logout request" do
|
84
|
+
|
85
|
+
before do
|
86
|
+
# sign the logout request
|
87
|
+
settings.security[:logout_requests_signed] = true
|
88
|
+
settings.security[:embed_sign] = true
|
89
|
+
settings.certificate = ruby_saml_cert_text
|
90
|
+
settings.private_key = ruby_saml_key_text
|
91
|
+
end
|
92
|
+
|
93
|
+
it "doesn't sign through create_xml_document" do
|
94
|
+
unauth_req = OneLogin::RubySaml::Logoutrequest.new
|
95
|
+
inflated = unauth_req.create_xml_document(settings).to_s
|
96
|
+
|
97
|
+
refute_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], inflated
|
98
|
+
refute_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1'/>], inflated
|
99
|
+
refute_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2000/09/xmldsig#sha1'/>], inflated
|
100
|
+
end
|
101
|
+
|
102
|
+
it "sign unsigned request" do
|
103
|
+
unauth_req = OneLogin::RubySaml::Logoutrequest.new
|
104
|
+
unauth_req_doc = unauth_req.create_xml_document(settings)
|
105
|
+
inflated = unauth_req_doc.to_s
|
106
|
+
|
107
|
+
refute_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], inflated
|
108
|
+
refute_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1'/>], inflated
|
109
|
+
refute_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2000/09/xmldsig#sha1'/>], inflated
|
110
|
+
|
111
|
+
inflated = unauth_req.sign_document(unauth_req_doc, settings).to_s
|
112
|
+
|
113
|
+
assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], inflated
|
114
|
+
assert_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1'/>], inflated
|
115
|
+
assert_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2000/09/xmldsig#sha1'/>], inflated
|
116
|
+
end
|
117
|
+
|
118
|
+
it "signs through create_logout_request_xml_doc" do
|
119
|
+
unauth_req = OneLogin::RubySaml::Logoutrequest.new
|
120
|
+
inflated = unauth_req.create_logout_request_xml_doc(settings).to_s
|
121
|
+
|
122
|
+
assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], inflated
|
123
|
+
assert_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1'/>], inflated
|
124
|
+
assert_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2000/09/xmldsig#sha1'/>], inflated
|
125
|
+
end
|
126
|
+
|
127
|
+
it "created a signed logout request" do
|
128
|
+
settings.compress_request = true
|
129
|
+
|
130
|
+
unauth_req = OneLogin::RubySaml::Logoutrequest.new
|
131
|
+
unauth_url = unauth_req.create(settings)
|
132
|
+
|
133
|
+
inflated = decode_saml_request_payload(unauth_url)
|
134
|
+
assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], inflated
|
135
|
+
assert_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1'/>], inflated
|
136
|
+
assert_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2000/09/xmldsig#sha1'/>], inflated
|
137
|
+
end
|
138
|
+
|
139
|
+
it "create a signed logout request with 256 digest and signature method" do
|
140
|
+
settings.compress_request = false
|
141
|
+
settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA256
|
142
|
+
settings.security[:digest_method] = XMLSecurity::Document::SHA256
|
143
|
+
|
144
|
+
params = OneLogin::RubySaml::Logoutrequest.new.create_params(settings)
|
145
|
+
request_xml = Base64.decode64(params["SAMLRequest"])
|
146
|
+
|
147
|
+
assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], request_xml
|
148
|
+
assert_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'/>], request_xml
|
149
|
+
assert_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2001/04/xmlenc#sha256'/>], request_xml
|
150
|
+
end
|
151
|
+
|
152
|
+
it "create a signed logout request with 512 digest and signature method RSA_SHA384" do
|
153
|
+
settings.compress_request = false
|
154
|
+
settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA384
|
155
|
+
settings.security[:digest_method] = XMLSecurity::Document::SHA512
|
156
|
+
|
157
|
+
params = OneLogin::RubySaml::Logoutrequest.new.create_params(settings)
|
158
|
+
request_xml = Base64.decode64(params["SAMLRequest"])
|
159
|
+
|
160
|
+
assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], request_xml
|
161
|
+
assert_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2001/04/xmldsig-more#rsa-sha384'/>], request_xml
|
162
|
+
assert_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2001/04/xmlenc#sha512'/>], request_xml
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
describe "#create_params when the settings indicate to sign the logout request" do
|
167
|
+
|
168
|
+
let(:cert) { OpenSSL::X509::Certificate.new(ruby_saml_cert_text) }
|
169
|
+
|
170
|
+
before do
|
171
|
+
# sign the logout request
|
172
|
+
settings.security[:logout_requests_signed] = true
|
173
|
+
settings.security[:embed_sign] = false
|
174
|
+
settings.certificate = ruby_saml_cert_text
|
175
|
+
settings.private_key = ruby_saml_key_text
|
176
|
+
end
|
177
|
+
|
178
|
+
it "create a signature parameter with RSA_SHA1 / SHA1 and validate it" do
|
179
|
+
settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1
|
180
|
+
|
181
|
+
params = OneLogin::RubySaml::Logoutrequest.new.create_params(settings, :RelayState => 'http://example.com')
|
182
|
+
assert params['SAMLRequest']
|
183
|
+
assert params[:RelayState]
|
184
|
+
assert params['Signature']
|
185
|
+
assert_equal params['SigAlg'], XMLSecurity::Document::RSA_SHA1
|
186
|
+
|
187
|
+
query_string = "SAMLRequest=#{CGI.escape(params['SAMLRequest'])}"
|
188
|
+
query_string << "&RelayState=#{CGI.escape(params[:RelayState])}"
|
189
|
+
query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}"
|
190
|
+
|
191
|
+
signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg'])
|
192
|
+
assert_equal signature_algorithm, OpenSSL::Digest::SHA1
|
193
|
+
assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string)
|
194
|
+
end
|
195
|
+
|
196
|
+
it "create a signature parameter with RSA_SHA256 / SHA256 and validate it" do
|
197
|
+
settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA256
|
198
|
+
|
199
|
+
params = OneLogin::RubySaml::Logoutrequest.new.create_params(settings, :RelayState => 'http://example.com')
|
200
|
+
assert params['Signature']
|
201
|
+
assert_equal params['SigAlg'], XMLSecurity::Document::RSA_SHA256
|
202
|
+
|
203
|
+
query_string = "SAMLRequest=#{CGI.escape(params['SAMLRequest'])}"
|
204
|
+
query_string << "&RelayState=#{CGI.escape(params[:RelayState])}"
|
205
|
+
query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}"
|
206
|
+
|
207
|
+
signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg'])
|
208
|
+
assert_equal signature_algorithm, OpenSSL::Digest::SHA256
|
209
|
+
assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string)
|
210
|
+
end
|
211
|
+
|
212
|
+
it "create a signature parameter with RSA_SHA384 / SHA384 and validate it" do
|
213
|
+
settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA384
|
214
|
+
|
215
|
+
params = OneLogin::RubySaml::Logoutrequest.new.create_params(settings, :RelayState => 'http://example.com')
|
216
|
+
assert params['Signature']
|
217
|
+
assert_equal params['SigAlg'], XMLSecurity::Document::RSA_SHA384
|
218
|
+
|
219
|
+
query_string = "SAMLRequest=#{CGI.escape(params['SAMLRequest'])}"
|
220
|
+
query_string << "&RelayState=#{CGI.escape(params[:RelayState])}"
|
221
|
+
query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}"
|
222
|
+
|
223
|
+
signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg'])
|
224
|
+
assert_equal signature_algorithm, OpenSSL::Digest::SHA384
|
225
|
+
assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string)
|
226
|
+
end
|
227
|
+
|
228
|
+
it "create a signature parameter with RSA_SHA512 / SHA512 and validate it" do
|
229
|
+
settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA512
|
230
|
+
|
231
|
+
params = OneLogin::RubySaml::Logoutrequest.new.create_params(settings, :RelayState => 'http://example.com')
|
232
|
+
assert params['Signature']
|
233
|
+
assert_equal params['SigAlg'], XMLSecurity::Document::RSA_SHA512
|
234
|
+
|
235
|
+
query_string = "SAMLRequest=#{CGI.escape(params['SAMLRequest'])}"
|
236
|
+
query_string << "&RelayState=#{CGI.escape(params[:RelayState])}"
|
237
|
+
query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}"
|
238
|
+
|
239
|
+
signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg'])
|
240
|
+
assert_equal signature_algorithm, OpenSSL::Digest::SHA512
|
241
|
+
assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string)
|
242
|
+
end
|
103
243
|
|
104
|
-
zstream = Zlib::Inflate.new(-Zlib::MAX_WBITS)
|
105
|
-
inflated = zstream.inflate(decoded)
|
106
|
-
zstream.finish
|
107
|
-
zstream.close
|
108
|
-
inflated
|
109
244
|
end
|
110
245
|
|
111
246
|
end
|