Skip to content

Commit 048a544

Browse files
committed
Fix vulnerability CVE-2017-11428. Process text of nodes properly, ignoring comments
1 parent 414d144 commit 048a544

File tree

8 files changed

+46
-41
lines changed

8 files changed

+46
-41
lines changed

lib/onelogin/ruby-saml/idp_metadata_parser.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ def idp_name_id_format
192192
"md:IDPSSODescriptor/md:NameIDFormat",
193193
namespace
194194
)
195-
node.text if node
195+
Utils.element_text(node)
196196
end
197197

198198
# @param binding_priority [Array]
@@ -281,14 +281,14 @@ def certificates
281281
unless signing_nodes.empty?
282282
certs['signing'] = []
283283
signing_nodes.each do |cert_node|
284-
certs['signing'] << cert_node.text
284+
certs['signing'] << Utils.element_text(cert_node)
285285
end
286286
end
287287

288288
unless encryption_nodes.empty?
289289
certs['encryption'] = []
290290
encryption_nodes.each do |cert_node|
291-
certs['encryption'] << cert_node.text
291+
certs['encryption'] << Utils.element_text(cert_node)
292292
end
293293
end
294294
end

lib/onelogin/ruby-saml/logoutresponse.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ def issuer
8080
"/p:LogoutResponse/a:Issuer",
8181
{ "p" => PROTOCOL, "a" => ASSERTION }
8282
)
83-
node.nil? ? nil : node.text
83+
Utils.element_text(node)
8484
end
8585
end
8686

@@ -100,7 +100,7 @@ def status_message
100100
"/p:LogoutResponse/p:Status/p:StatusMessage",
101101
{ "p" => PROTOCOL, "a" => ASSERTION }
102102
)
103-
node.text if node
103+
Utils.element_text(node)
104104
end
105105
end
106106

lib/onelogin/ruby-saml/response.rb

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,7 @@ def is_valid?(collect_errors = false)
7171
# @return [String] the NameID provided by the SAML response from the IdP.
7272
#
7373
def name_id
74-
@name_id ||=
75-
if name_id_node
76-
name_id_node.text
77-
end
74+
@name_id ||= Utils.element_text(name_id_node)
7875
end
7976

8077
alias_method :nameid, :name_id
@@ -159,14 +156,14 @@ def attributes
159156
if (e.elements.nil? || e.elements.size == 0)
160157
# SAMLCore requires that nil AttributeValues MUST contain xsi:nil XML attribute set to "true" or "1"
161158
# otherwise the value is to be regarded as empty.
162-
["true", "1"].include?(e.attributes['xsi:nil']) ? nil : e.text.to_s
159+
["true", "1"].include?(e.attributes['xsi:nil']) ? nil : Utils.element_text(e)
163160
# explicitly support saml2:NameID with saml2:NameQualifier if supplied in attributes
164161
# this is useful for allowing eduPersonTargetedId to be passed as an opaque identifier to use to
165162
# identify the subject in an SP rather than email or other less opaque attributes
166163
# NameQualifier, if present is prefixed with a "/" to the value
167164
else
168165
REXML::XPath.match(e,'a:NameID', { "a" => ASSERTION }).collect{|n|
169-
(n.attributes['NameQualifier'] ? n.attributes['NameQualifier'] +"/" : '') + n.text.to_s
166+
(n.attributes['NameQualifier'] ? n.attributes['NameQualifier'] +"/" : '') + Utils.element_text(n)
170167
}
171168
end
172169
}
@@ -238,8 +235,7 @@ def status_message
238235
{ "p" => PROTOCOL }
239236
)
240237
if nodes.size == 1
241-
node = nodes[0]
242-
node.text if node
238+
Utils.element_text(nodes.first)
243239
end
244240
end
245241
end
@@ -272,7 +268,6 @@ def not_on_or_after
272268
#
273269
def issuers
274270
@issuers ||= begin
275-
issuers = []
276271
issuer_response_nodes = REXML::XPath.match(
277272
document,
278273
"/p:Response/a:Issuer",
@@ -292,10 +287,7 @@ def issuers
292287
end
293288

294289
nodes = issuer_response_nodes + issuer_assertion_nodes
295-
nodes.each do |node|
296-
issuers << node.text if node.text
297-
end
298-
issuers.uniq
290+
nodes.map { |node| Utils.element_text(node) }.compact.uniq
299291
end
300292
end
301293

@@ -329,14 +321,8 @@ def destination
329321
#
330322
def audiences
331323
@audiences ||= begin
332-
audiences = []
333324
nodes = xpath_from_signed_assertion('/a:Conditions/a:AudienceRestriction/a:Audience')
334-
nodes.each do |node|
335-
if node && node.text
336-
audiences << node.text
337-
end
338-
end
339-
audiences
325+
nodes.map { |node| Utils.element_text(node) }.compact
340326
end
341327
end
342328

lib/onelogin/ruby-saml/slo_logoutrequest.rb

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ def is_valid?(collect_errors = false)
6060
def name_id
6161
@name_id ||= begin
6262
node = REXML::XPath.first(document, "/p:LogoutRequest/a:NameID", { "p" => PROTOCOL, "a" => ASSERTION })
63-
node.nil? ? nil : node.text
63+
Utils.element_text(node)
6464
end
6565
end
6666

@@ -93,7 +93,7 @@ def issuer
9393
"/p:LogoutRequest/a:Issuer",
9494
{ "p" => PROTOCOL, "a" => ASSERTION }
9595
)
96-
node.nil? ? nil : node.text
96+
Utils.element_text(node)
9797
end
9898
end
9999

@@ -115,18 +115,13 @@ def not_on_or_after
115115
# @return [Array] Gets the SessionIndex if exists (Supported multiple values). Empty Array if none found
116116
#
117117
def session_indexes
118-
s_indexes = []
119118
nodes = REXML::XPath.match(
120119
document,
121120
"/p:LogoutRequest/p:SessionIndex",
122121
{ "p" => PROTOCOL }
123122
)
124123

125-
nodes.each do |node|
126-
s_indexes << node.text
127-
end
128-
129-
s_indexes
124+
nodes.map { |node| Utils.element_text(node) }
130125
end
131126

132127
private

lib/onelogin/ruby-saml/utils.rb

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ def self.decrypt_data(encrypted_node, private_key)
173173
"./xenc:CipherData/xenc:CipherValue",
174174
{ 'xenc' => XENC }
175175
)
176-
node = Base64.decode64(cipher_value.text)
176+
node = Base64.decode64(element_text(cipher_value))
177177
encrypt_method = REXML::XPath.first(
178178
encrypt_data,
179179
"./xenc:EncryptionMethod",
@@ -201,7 +201,7 @@ def self.retrieve_symmetric_key(encrypt_data, private_key)
201201
"xenc" => XENC
202202
)
203203

204-
cipher_text = Base64.decode64(encrypted_symmetric_key_element.text)
204+
cipher_text = Base64.decode64(element_text(encrypted_symmetric_key_element))
205205

206206
encrypt_method = REXML::XPath.first(
207207
encrypted_key,
@@ -281,6 +281,13 @@ def self.uri_match?(destination_url, settings_url)
281281
def self.original_uri_match?(destination_url, settings_url)
282282
destination_url == settings_url
283283
end
284+
285+
# Given a REXML::Element instance, return the concatenation of all child text nodes. Assumes
286+
# that there all children other than text nodes can be ignored (e.g. comments). If nil is
287+
# passed, nil will be returned.
288+
def self.element_text(element)
289+
element.texts.join if element
290+
end
284291
end
285292
end
286293
end

lib/xml_security.rb

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
require 'nokogiri'
3030
require "digest/sha1"
3131
require "digest/sha2"
32+
require "onelogin/ruby-saml/utils"
3233
require "onelogin/ruby-saml/error_handling"
3334

3435
module XMLSecurity
@@ -206,7 +207,7 @@ def validate_document(idp_cert_fingerprint, soft = true, options = {})
206207
)
207208

208209
if cert_element
209-
base64_cert = cert_element.text
210+
base64_cert = OneLogin::RubySaml::Utils.element_text(cert_element)
210211
cert_text = Base64.decode64(base64_cert)
211212
begin
212213
cert = OpenSSL::X509::Certificate.new(cert_text)
@@ -249,7 +250,7 @@ def validate_document_with_cert(idp_cert)
249250
)
250251

251252
if cert_element
252-
base64_cert = cert_element.text
253+
base64_cert = OneLogin::RubySaml::Utils.element_text(cert_element)
253254
cert_text = Base64.decode64(base64_cert)
254255
begin
255256
cert = OpenSSL::X509::Certificate.new(cert_text)
@@ -296,8 +297,8 @@ def validate_signature(base64_cert, soft = true)
296297
sig_element,
297298
"./ds:SignatureValue",
298299
{"ds" => DSIG}
299-
).text
300-
signature = Base64.decode64(base64_signature)
300+
)
301+
signature = Base64.decode64(OneLogin::RubySaml::Utils.element_text(base64_signature))
301302

302303
# canonicalization method
303304
canon_algorithm = canon_algorithm REXML::XPath.first(
@@ -338,8 +339,8 @@ def validate_signature(base64_cert, soft = true)
338339
ref,
339340
"//ds:DigestValue",
340341
{ "ds" => DSIG }
341-
).text
342-
digest_value = Base64.decode64(encoded_digest_value)
342+
)
343+
digest_value = Base64.decode64(OneLogin::RubySaml::Utils.element_text(encoded_digest_value))
343344

344345
unless digests_match?(hash, digest_value)
345346
@errors << "Digest mismatch"

test/response_test.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,21 @@ class RubySamlTest < Minitest::Test
6969
assert_includes ampersands_response.errors, "SAML Response must contain 1 assertion"
7070
end
7171

72+
describe "Prevent node text with comment attack (VU#475445)" do
73+
before do
74+
@response = OneLogin::RubySaml::Response.new(read_response('response_node_text_attack.xml.base64'))
75+
end
76+
77+
it "receives the full NameID when there is an injected comment" do
78+
assert_equal "[email protected]", @response.name_id
79+
end
80+
81+
it "receives the full AttributeValue when there is an injected comment" do
82+
assert_equal "smith", @response.attributes["surname"]
83+
end
84+
85+
end
86+
7287
describe "Prevent XEE attack" do
7388
before do
7489
@response = OneLogin::RubySaml::Response.new(fixture(:attackxee))
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIElEPSJHT1NBTUxSMTI5MDExNzQ1NzE3OTQiIFZlcnNpb249IjIuMCIgSXNzdWVJbnN0YW50PSIyMDEwLTExLTE4VDIxOjU3OjM3WiIgRGVzdGluYXRpb249IntyZWNpcGllbnR9Ij4NCiAgPHNhbWxwOlN0YXR1cz4NCiAgICA8c2FtbHA6U3RhdHVzQ29kZSBWYWx1ZT0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnN0YXR1czpTdWNjZXNzIi8+PC9zYW1scDpTdGF0dXM+DQogIDxzYW1sOkFzc2VydGlvbiB4bWxuczp4cz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEiIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIFZlcnNpb249IjIuMCIgSUQ9InBmeGE0NjU3NGRmLWIzYjAtYTA2YS0yM2M4LTYzNjQxMzE5ODc3MiIgSXNzdWVJbnN0YW50PSIyMDEwLTExLTE4VDIxOjU3OjM3WiI+DQogICAgPHNhbWw6SXNzdWVyPmh0dHBzOi8vYXBwLm9uZWxvZ2luLmNvbS9zYW1sL21ldGFkYXRhLzEzNTkwPC9zYW1sOklzc3Vlcj4NCiAgICA8ZHM6U2lnbmF0dXJlIHhtbG5zOmRzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIj4NCiAgICAgIDxkczpTaWduZWRJbmZvPg0KICAgICAgICA8ZHM6Q2Fub25pY2FsaXphdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPg0KICAgICAgICA8ZHM6U2lnbmF0dXJlTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI3JzYS1zaGExIi8+DQogICAgICAgIDxkczpSZWZlcmVuY2UgVVJJPSIjcGZ4YTQ2NTc0ZGYtYjNiMC1hMDZhLTIzYzgtNjM2NDEzMTk4NzcyIj4NCiAgICAgICAgICA8ZHM6VHJhbnNmb3Jtcz4NCiAgICAgICAgICAgIDxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjZW52ZWxvcGVkLXNpZ25hdHVyZSIvPg0KICAgICAgICAgICAgPGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPg0KICAgICAgICAgIDwvZHM6VHJhbnNmb3Jtcz4NCiAgICAgICAgICA8ZHM6RGlnZXN0TWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI3NoYTEiLz4NCiAgICAgICAgICA8ZHM6RGlnZXN0VmFsdWU+cEpRN01TL2VrNEtSUldHbXYvSDQzUmVIWU1zPTwvZHM6RGlnZXN0VmFsdWU+DQogICAgICAgIDwvZHM6UmVmZXJlbmNlPg0KICAgICAgPC9kczpTaWduZWRJbmZvPg0KICAgICAgPGRzOlNpZ25hdHVyZVZhbHVlPnlpdmVLY1BkRHB1RE5qNnNoclEzQUJ3ci9jQTNDcnlEMnBoRy94TFpzektXeFU1L21sYUt0OGV3YlpPZEtLdnRPczJwSEJ5NUR1YTNrOTRBRnp4R3llbDVnT293bW95WEpyQU9ya1BPMHZsaTFWOG8zaFBQVVp3UmdTWDZROXBTMUNxUWdoS2lFYXNSeXlscXFKVWFQWXptT3pPRTgvWGxNa3dpV21PMD08L2RzOlNpZ25hdHVyZVZhbHVlPg0KICAgICAgPGRzOktleUluZm8+DQogICAgICAgIDxkczpYNTA5RGF0YT4NCiAgICAgICAgICA8ZHM6WDUwOUNlcnRpZmljYXRlPk1JSUJyVENDQWFHZ0F3SUJBZ0lCQVRBREJnRUFNR2N4Q3pBSkJnTlZCQVlUQWxWVE1STXdFUVlEVlFRSURBcERZV3hwWm05eWJtbGhNUlV3RXdZRFZRUUhEQXhUWVc1MFlTQk5iMjVwWTJFeEVUQVBCZ05WQkFvTUNFOXVaVXh2WjJsdU1Sa3dGd1lEVlFRRERCQmhjSEF1YjI1bGJHOW5hVzR1WTI5dE1CNFhEVEV3TURNd09UQTVOVGcwTlZvWERURTFNRE13T1RBNU5UZzBOVm93WnpFTE1Ba0dBMVVFQmhNQ1ZWTXhFekFSQmdOVkJBZ01Da05oYkdsbWIzSnVhV0V4RlRBVEJnTlZCQWNNREZOaGJuUmhJRTF2Ym1sallURVJNQThHQTFVRUNnd0lUMjVsVEc5bmFXNHhHVEFYQmdOVkJBTU1FR0Z3Y0M1dmJtVnNiMmRwYmk1amIyMHdnWjh3RFFZSktvWklodmNOQVFFQkJRQURnWTBBTUlHSkFvR0JBT2pTdTFmalB5OGQ1dzRReUwxemQ0aEl3MU1ra2ZmNFdZL1RMRzhPWmtVNVlUU1dtbUhQRDVrdllINXVvWFMvNnFRODFxWHBSMndWOENUb3daSlVMZzA5ZGRSZFJuOFFzcWoxRnlPQzVzbEUzeTJiWjJvRnVhNzJvZi80OWZwdWpuRlQ2S25RNjFDQk1xbERvVFFxT1Q2MnZHSjhuUDZNWld2QTZzeHF1ZDVBZ01CQUFFd0F3WUJBQU1CQUE9PTwvZHM6WDUwOUNlcnRpZmljYXRlPg0KICAgICAgICA8L2RzOlg1MDlEYXRhPg0KICAgICAgPC9kczpLZXlJbmZvPg0KICAgIDwvZHM6U2lnbmF0dXJlPg0KICAgIDxzYW1sOlN1YmplY3Q+DQogICAgICA8c2FtbDpOYW1lSUQgRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoxLjE6bmFtZWlkLWZvcm1hdDplbWFpbEFkZHJlc3MiPnN1cHBvcnQ8IS0tIGF0dGFjayEgLS0+QG9uZWxvZ2luLmNvbTwvc2FtbDpOYW1lSUQ+DQogICAgICA8c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uIE1ldGhvZD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmNtOmJlYXJlciI+DQogICAgICAgIDxzYW1sOlN1YmplY3RDb25maXJtYXRpb25EYXRhIE5vdE9uT3JBZnRlcj0iMjAxMC0xMS0xOFQyMjowMjozN1oiIFJlY2lwaWVudD0ie3JlY2lwaWVudH0iLz48L3NhbWw6U3ViamVjdENvbmZpcm1hdGlvbj4NCiAgICA8L3NhbWw6U3ViamVjdD4NCiAgICA8c2FtbDpDb25kaXRpb25zIE5vdEJlZm9yZT0iMjAxMC0xMS0xOFQyMTo1MjozN1oiIE5vdE9uT3JBZnRlcj0iMjAxMC0xMS0xOFQyMjowMjozN1oiPg0KICAgICAgPHNhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj4NCiAgICAgICAgPHNhbWw6QXVkaWVuY2U+e2F1ZGllbmNlfTwvc2FtbDpBdWRpZW5jZT4NCiAgICAgIDwvc2FtbDpBdWRpZW5jZVJlc3RyaWN0aW9uPg0KICAgIDwvc2FtbDpDb25kaXRpb25zPg0KICAgIDxzYW1sOkF1dGhuU3RhdGVtZW50IEF1dGhuSW5zdGFudD0iMjAxMC0xMS0xOFQyMTo1NzozN1oiIFNlc3Npb25Ob3RPbk9yQWZ0ZXI9IjIwMTAtMTEtMTlUMjE6NTc6MzdaIiBTZXNzaW9uSW5kZXg9Il81MzFjMzJkMjgzYmRmZjdlMDRlNDg3YmNkYmM0ZGQ4ZCI+DQogICAgICA8c2FtbDpBdXRobkNvbnRleHQ+DQogICAgICAgIDxzYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPnVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphYzpjbGFzc2VzOlBhc3N3b3JkPC9zYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPg0KICAgICAgPC9zYW1sOkF1dGhuQ29udGV4dD4NCiAgICA8L3NhbWw6QXV0aG5TdGF0ZW1lbnQ+DQogICAgPHNhbWw6QXR0cmlidXRlU3RhdGVtZW50Pg0KICAgICAgPHNhbWw6QXR0cmlidXRlIE5hbWU9InN1cm5hbWUiPg0KICAgICAgICA8c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4bWxuczp4cz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEiIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhzaTp0eXBlPSJ4czpzdHJpbmciPnM8IS0tIGF0dGFjayEgLS0+bWl0aDwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT4NCiAgICAgIDwvc2FtbDpBdHRyaWJ1dGU+DQogICAgICA8c2FtbDpBdHRyaWJ1dGUgTmFtZT0iYW5vdGhlcl92YWx1ZSI+DQogICAgICAgIDxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeHNpOnR5cGU9InhzOnN0cmluZyI+dmFsdWUxPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPg0KICAgICAgICA8c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4bWxuczp4cz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEiIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhzaTp0eXBlPSJ4czpzdHJpbmciPnZhbHVlMjwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT4NCiAgICAgIDwvc2FtbDpBdHRyaWJ1dGU+DQogICAgICA8c2FtbDpBdHRyaWJ1dGUgTmFtZT0icm9sZSI+DQogICAgICAgIDxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeHNpOnR5cGU9InhzOnN0cmluZyI+cm9sZTE8L3NhbWw6QXR0cmlidXRlVmFsdWU+DQogICAgICA8L3NhbWw6QXR0cmlidXRlPg0KICAgIDwvc2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+DQogICAgPHNhbWw6QXR0cmlidXRlU3RhdGVtZW50Pg0KICAgICAgPHNhbWw6QXR0cmlidXRlIE5hbWU9ImZpcnN0bmFtZSI+DQogICAgICAgIDxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeHNpOnR5cGU9InhzOnN0cmluZyI+Ym9iPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPg0KICAgICAgPC9zYW1sOkF0dHJpYnV0ZT4gIA0KICAgICAgPHNhbWw6QXR0cmlidXRlIE5hbWU9ImF0dHJpYnV0ZV93aXRoX25pbF92YWx1ZSI+DQogICAgICAgIDxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeHNpOm5pbD0idHJ1ZSIvPg0KICAgICAgPC9zYW1sOkF0dHJpYnV0ZT4NCiAgICAgIDxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJhdHRyaWJ1dGVfd2l0aF9uaWxzX2FuZF9lbXB0eV9zdHJpbmdzIj4NCiAgICAgICAgPHNhbWw6QXR0cmlidXRlVmFsdWUvPg0KICAgICAgICA8c2FtbDpBdHRyaWJ1dGVWYWx1ZT52YWx1ZVByZXNlbnQ8L3NhbWw6QXR0cmlidXRlVmFsdWU+DQogICAgICAgIDxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeHNpOm5pbD0idHJ1ZSIvPg0KICAgICAgICA8c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4bWxuczp4cz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEiIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhzaTpuaWw9IjEiLz4NCiAgICAgIDwvc2FtbDpBdHRyaWJ1dGU+DQogICAgPC9zYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD4NCiAgPC9zYW1sOkFzc2VydGlvbj4NCjwvc2FtbHA6UmVzcG9uc2U+

0 commit comments

Comments
 (0)