Skip to content

Commit 346f269

Browse files
committed
Merge remote-tracking branch 'remotes/johnnyshields/v2.x-double-quote-xml' into v2.x-partial-nokogiri-upgrade
2 parents 05b9f23 + fe7ba4f commit 346f269

File tree

10 files changed

+76
-42
lines changed

10 files changed

+76
-42
lines changed

UPGRADING.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,18 @@ settings.security[:digest_method] = RubySaml::XML::Crypto::SHA1
5959
settings.security[:signature_method] = RubySaml::XML::Crypto::RSA_SHA1
6060
```
6161

62+
### Behavior change of double_quote_xml_attribute_values setting
63+
64+
RubySaml now always uses double quotes for attribute values when generating XML.
65+
The `settings.double_quote_xml_attribute_values` parameter now always behaves as `true`,
66+
and will be removed in RubySaml 2.1.0.
67+
68+
The reasons for this change are:
69+
- RubySaml will use Nokogiri instead of REXML to generate XML. Nokogiri does not support
70+
generating XML with single quotes.
71+
- Double-quoted XML attributes are more commonly used than single quotes. There are no known
72+
SAML clients which cannot support double-quoted XML.
73+
6274
### Removal of embed_sign setting
6375

6476
The deprecated `settings.security[:embed_sign]` parameter has been removed. If you were using it, please instead switch

lib/ruby_saml/authrequest.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ def create_params(settings, params={})
5454
end
5555

5656
request_doc = create_authentication_xml_doc(settings)
57-
request_doc.context[:attribute_quote] = :quote if settings.double_quote_xml_attribute_values
57+
request_doc.context[:attribute_quote] = :quote
5858

5959
request = +""
6060
request_doc.write(request)

lib/ruby_saml/logoutrequest.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ def create_params(settings, params={})
5252
end
5353

5454
request_doc = create_logout_request_xml_doc(settings)
55-
request_doc.context[:attribute_quote] = :quote if settings.double_quote_xml_attribute_values
55+
request_doc.context[:attribute_quote] = :quote
5656

5757
request = +""
5858
request_doc.write(request)

lib/ruby_saml/metadata.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ class Metadata
2222
#
2323
def generate(settings, pretty_print=false, valid_until=nil, cache_duration=nil)
2424
meta_doc = RubySaml::XML::Document.new
25+
meta_doc.context[:attribute_quote] = :quote
2526
add_xml_declaration(meta_doc)
2627
root = add_root_element(meta_doc, settings, valid_until, cache_duration)
2728
sp_sso = add_sp_sso_element(root, settings)

lib/ruby_saml/settings.rb

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ def initialize(overrides = {}, keep_security_attributes = false)
5454
attr_accessor :name_identifier_value
5555
attr_accessor :name_identifier_value_requested
5656
attr_accessor :sessionindex
57-
attr_accessor :double_quote_xml_attribute_values
5857
attr_accessor :message_max_bytesize
5958
attr_accessor :passive
6059
attr_reader :protocol_binding
@@ -230,7 +229,6 @@ def get_binding(value)
230229
idp_cert_fingerprint_algorithm: RubySaml::XML::Crypto::SHA256,
231230
message_max_bytesize: 250_000,
232231
soft: true,
233-
double_quote_xml_attribute_values: false,
234232
security: {
235233
authn_requests_signed: false,
236234
logout_requests_signed: false,
@@ -248,6 +246,22 @@ def get_binding(value)
248246
}.freeze
249247
}.freeze
250248

249+
{
250+
double_quote_xml_attribute_values: true
251+
}.each do |old_param, new_value|
252+
# @deprecated Will be removed in v2.1.0
253+
define_method(old_param) do
254+
removed_deprecation(old_param, new_value)
255+
new_value
256+
end
257+
258+
# @deprecated Will be removed in v2.1.0
259+
define_method(:"#{old_param}=") do |_|
260+
removed_deprecation(old_param, new_value)
261+
new_value
262+
end
263+
end
264+
251265
{
252266
issuer: :sp_entity_id,
253267
idp_sso_target_url: :idp_sso_service_url,
@@ -355,6 +369,12 @@ def compress_response=(value)
355369

356370
private
357371

372+
# @deprecated Will be removed in v2.1.0
373+
def removed_deprecation(old_param, new_value)
374+
Logging.deprecate "`RubySaml::Settings##{old_param}` is deprecated and will be removed in RubySaml 2.1.0. " \
375+
"It no longer has any effect, and will behave as if always set to #{new_value.inspect}."
376+
end
377+
358378
# @deprecated Will be removed in v2.1.0
359379
def replaced_deprecation(old_param, new_param)
360380
Logging.deprecate "`RubySaml::Settings##{old_param}` is deprecated and will be removed in RubySaml 2.1.0. " \

lib/ruby_saml/slo_logoutresponse.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ def create_params(settings, request_id = nil, logout_message = nil, params = {},
6060
end
6161

6262
response_doc = create_logout_response_xml_doc(settings, request_id, logout_message, logout_status_code)
63-
response_doc.context[:attribute_quote] = :quote if settings.double_quote_xml_attribute_values
63+
response_doc.context[:attribute_quote] = :quote
6464

6565
response = +""
6666
response_doc.write(response)

test/authrequest_test.rb

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ class AuthrequestTest < Minitest::Test
3636
zstream.finish
3737
zstream.close
3838

39-
assert_match(/<samlp:AuthnRequest[^<]* Destination='http:\/\/example.com'/, inflated)
39+
assert_match(/<samlp:AuthnRequest[^<]* Destination="http:\/\/example\.com"/, inflated)
4040
end
4141

4242
it "create the SAMLRequest URL parameter without deflating" do
@@ -61,7 +61,7 @@ class AuthrequestTest < Minitest::Test
6161
zstream.finish
6262
zstream.close
6363

64-
assert_match(/<samlp:AuthnRequest[^<]* IsPassive='true'/, inflated)
64+
assert_match(/<samlp:AuthnRequest[^<]* IsPassive="true"/, inflated)
6565
end
6666

6767
it "create the SAMLRequest URL parameter with ProtocolBinding" do
@@ -76,7 +76,7 @@ class AuthrequestTest < Minitest::Test
7676
zstream.finish
7777
zstream.close
7878

79-
assert_match(/<samlp:AuthnRequest[^<]* ProtocolBinding='urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST'/, inflated)
79+
assert_match(/<samlp:AuthnRequest[^<]* ProtocolBinding="urn:oasis:names:tc:SAML:2\.0:bindings:HTTP-POST"/, inflated)
8080
end
8181

8282
it "create the SAMLRequest URL parameter with AttributeConsumingServiceIndex" do
@@ -90,7 +90,7 @@ class AuthrequestTest < Minitest::Test
9090
inflated = zstream.inflate(decoded)
9191
zstream.finish
9292
zstream.close
93-
assert_match(/<samlp:AuthnRequest[^<]* AttributeConsumingServiceIndex='30'/, inflated)
93+
assert_match(/<samlp:AuthnRequest[^<]* AttributeConsumingServiceIndex="30"/, inflated)
9494
end
9595

9696
it "create the SAMLRequest URL parameter with ForceAuthn" do
@@ -104,7 +104,7 @@ class AuthrequestTest < Minitest::Test
104104
inflated = zstream.inflate(decoded)
105105
zstream.finish
106106
zstream.close
107-
assert_match(/<samlp:AuthnRequest[^<]* ForceAuthn='true'/, inflated)
107+
assert_match(/<samlp:AuthnRequest[^<]* ForceAuthn="true"/, inflated)
108108
end
109109

110110
it "create the SAMLRequest URL parameter with NameID Format" do
@@ -118,8 +118,8 @@ class AuthrequestTest < Minitest::Test
118118
zstream.finish
119119
zstream.close
120120

121-
assert_match(/<samlp:NameIDPolicy[^<]* AllowCreate='true'/, inflated)
122-
assert_match(/<samlp:NameIDPolicy[^<]* Format='urn:oasis:names:tc:SAML:2.0:nameid-format:transient'/, inflated)
121+
assert_match(/<samlp:NameIDPolicy[^<]* AllowCreate="true"/, inflated)
122+
assert_match(/<samlp:NameIDPolicy[^<]* Format="urn:oasis:names:tc:SAML:2\.0:nameid-format:transient"/, inflated)
123123
end
124124

125125
it "create the SAMLRequest URL parameter with Subject" do
@@ -135,8 +135,8 @@ class AuthrequestTest < Minitest::Test
135135
zstream.close
136136

137137
assert inflated.include?('<saml:Subject>')
138-
assert inflated.include?("<saml:NameID Format='urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress'>testuser@example.com</saml:NameID>")
139-
assert inflated.include?("<saml:SubjectConfirmation Method='urn:oasis:names:tc:SAML:2.0:cm:bearer'/>")
138+
assert inflated.include?('<saml:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">testuser@example.com</saml:NameID>')
139+
assert inflated.include?('<saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer"/>')
140140
end
141141

142142
it "accept extra parameters" do
@@ -165,8 +165,8 @@ class AuthrequestTest < Minitest::Test
165165
it "uuid is initialized to nil" do
166166
request = RubySaml::Authrequest.new
167167

168-
assert_nil(request.uuid)
169-
assert_equal request.request_id, request.uuid
168+
assert_nil request.uuid
169+
assert_nil request.request_id
170170
end
171171

172172
it "creates request with ID prefixed with default '_'" do
@@ -199,8 +199,9 @@ class AuthrequestTest < Minitest::Test
199199

200200
it "can mutate the uuid" do
201201
request = RubySaml::Authrequest.new
202-
request_id = request.request_id
203-
assert_equal request_id, request.uuid
202+
assert_nil request.uuid
203+
assert_nil request.request_id
204+
204205
request.uuid = "new_uuid"
205206
assert_equal "new_uuid", request.uuid
206207
assert_equal request.uuid, request.request_id
@@ -224,7 +225,7 @@ class AuthrequestTest < Minitest::Test
224225
it "create the SAMLRequest parameter correctly" do
225226

226227
auth_url = RubySaml::Authrequest.new.create(settings)
227-
assert_match(/^http:\/\/example.com\?SAMLRequest/, auth_url)
228+
assert_match(/^http:\/\/example\.com\?SAMLRequest/, auth_url)
228229
end
229230
end
230231

@@ -233,7 +234,7 @@ class AuthrequestTest < Minitest::Test
233234
settings.idp_sso_service_url = "http://example.com?field=value"
234235

235236
auth_url = RubySaml::Authrequest.new.create(settings)
236-
assert_match(/^http:\/\/example.com\?field=value&SAMLRequest/, auth_url)
237+
assert_match(/^http:\/\/example\.com\?field=value&SAMLRequest/, auth_url)
237238
end
238239
end
239240

@@ -253,22 +254,22 @@ class AuthrequestTest < Minitest::Test
253254
it "create the saml:AuthnContextClassRef with comparison exact" do
254255
settings.authn_context = 'secure/name/password/uri'
255256
auth_doc = RubySaml::Authrequest.new.create_authentication_xml_doc(settings)
256-
assert_match(/<samlp:RequestedAuthnContext[\S ]+Comparison='exact'/, auth_doc.to_s)
257+
assert_match(/<samlp:RequestedAuthnContext[\S ]+Comparison="exact"/, auth_doc.to_s)
257258
assert_match(/<saml:AuthnContextClassRef>secure\/name\/password\/uri<\/saml:AuthnContextClassRef>/, auth_doc.to_s)
258259
end
259260

260261
it "create the saml:AuthnContextClassRef with comparison minimun" do
261262
settings.authn_context = 'secure/name/password/uri'
262263
settings.authn_context_comparison = 'minimun'
263264
auth_doc = RubySaml::Authrequest.new.create_authentication_xml_doc(settings)
264-
assert_match(/<samlp:RequestedAuthnContext[\S ]+Comparison='minimun'/, auth_doc.to_s)
265+
assert_match(/<samlp:RequestedAuthnContext[\S ]+Comparison="minimun"/, auth_doc.to_s)
265266
assert_match(/<saml:AuthnContextClassRef>secure\/name\/password\/uri<\/saml:AuthnContextClassRef>/, auth_doc.to_s)
266267
end
267268

268269
it "create the saml:AuthnContextDeclRef element correctly" do
269270
settings.authn_context_decl_ref = 'urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport'
270271
auth_doc = RubySaml::Authrequest.new.create_authentication_xml_doc(settings)
271-
assert_match(/<saml:AuthnContextDeclRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport<\/saml:AuthnContextDeclRef>/, auth_doc.to_s)
272+
assert_match(/<saml:AuthnContextDeclRef>urn:oasis:names:tc:SAML:2\.0:ac:classes:PasswordProtectedTransport<\/saml:AuthnContextDeclRef>/, auth_doc.to_s)
272273
end
273274

274275
it "create the saml:AuthnContextClassRef element correctly" do
@@ -280,22 +281,22 @@ class AuthrequestTest < Minitest::Test
280281
it "create the saml:AuthnContextClassRef with comparison exact" do
281282
settings.authn_context = 'secure/name/password/uri'
282283
auth_doc = RubySaml::Authrequest.new.create_authentication_xml_doc(settings)
283-
assert auth_doc.to_s =~ /<samlp:RequestedAuthnContext[\S ]+Comparison='exact'/
284+
assert auth_doc.to_s =~ /<samlp:RequestedAuthnContext[\S ]+Comparison="exact"/
284285
assert auth_doc.to_s =~ /<saml:AuthnContextClassRef>secure\/name\/password\/uri<\/saml:AuthnContextClassRef>/
285286
end
286287

287288
it "create the saml:AuthnContextClassRef with comparison minimun" do
288289
settings.authn_context = 'secure/name/password/uri'
289290
settings.authn_context_comparison = 'minimun'
290291
auth_doc = RubySaml::Authrequest.new.create_authentication_xml_doc(settings)
291-
assert auth_doc.to_s =~ /<samlp:RequestedAuthnContext[\S ]+Comparison='minimun'/
292+
assert auth_doc.to_s =~ /<samlp:RequestedAuthnContext[\S ]+Comparison="minimun"/
292293
assert auth_doc.to_s =~ /<saml:AuthnContextClassRef>secure\/name\/password\/uri<\/saml:AuthnContextClassRef>/
293294
end
294295

295296
it "create the saml:AuthnContextDeclRef element correctly" do
296297
settings.authn_context_decl_ref = 'urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport'
297298
auth_doc = RubySaml::Authrequest.new.create_authentication_xml_doc(settings)
298-
assert auth_doc.to_s =~ /<saml:AuthnContextDeclRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport<\/saml:AuthnContextDeclRef>/
299+
assert auth_doc.to_s =~ /<saml:AuthnContextDeclRef>urn:oasis:names:tc:SAML:2\.0:ac:classes:PasswordProtectedTransport<\/saml:AuthnContextDeclRef>/
299300
end
300301

301302
it "create multiple saml:AuthnContextDeclRef elements correctly " do

test/logoutrequest_test.rb

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ class RequestTest < Minitest::Test
7878
settings.idp_slo_service_url = "http://example.com?field=value"
7979

8080
unauth_url = RubySaml::Logoutrequest.new.create(settings)
81-
assert_match(/^http:\/\/example.com\?field=value&SAMLRequest/, unauth_url)
81+
assert_match(/^http:\/\/example\.com\?field=value&SAMLRequest/, unauth_url)
8282
end
8383
end
8484

@@ -90,16 +90,16 @@ class RequestTest < Minitest::Test
9090
unauth_url = unauth_req.create(settings)
9191

9292
inflated = decode_saml_request_payload(unauth_url)
93-
assert_match %r[ID='#{unauth_req.uuid}'], inflated
93+
assert_match %r[ID="#{unauth_req.uuid}"], inflated
9494
end
9595
end
9696

9797
describe "uuid" do
9898
it "uuid is initialized to nil" do
9999
request = RubySaml::Logoutrequest.new
100100

101-
assert_nil(request.uuid)
102-
assert_equal request.request_id, request.uuid
101+
assert_nil request.uuid
102+
assert_nil request.request_id
103103
end
104104

105105
it "creates request with ID prefixed with default '_'" do
@@ -132,8 +132,9 @@ class RequestTest < Minitest::Test
132132

133133
it "can mutate the uuid" do
134134
request = RubySaml::Logoutrequest.new
135-
request_id = request.request_id
136-
assert_equal request_id, request.uuid
135+
assert_nil request.uuid
136+
assert_nil request.request_id
137+
137138
request.uuid = "new_uuid"
138139
assert_equal "new_uuid", request.uuid
139140
assert_equal request.uuid, request.request_id

test/settings_test.rb

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@ class SettingsTest < Minitest::Test
1616
:idp_cert, :idp_cert_fingerprint, :idp_cert_fingerprint_algorithm, :idp_cert_multi,
1717
:idp_attribute_names, :issuer, :assertion_consumer_service_url, :single_logout_service_url,
1818
:sp_name_qualifier, :name_identifier_format, :name_identifier_value, :name_identifier_value_requested,
19-
:sessionindex, :attributes_index, :passive, :force_authn,
20-
:double_quote_xml_attribute_values, :message_max_bytesize,
19+
:sessionindex, :attributes_index, :passive, :force_authn, :message_max_bytesize,
2120
:security, :certificate, :private_key, :certificate_new, :sp_cert_multi,
2221
:authn_context, :authn_context_comparison, :authn_context_decl_ref,
2322
:assertion_consumer_logout_service_url
@@ -556,7 +555,6 @@ class SettingsTest < Minitest::Test
556555
assert_equal expected_encryption, actual[:encryption].map { |ary| ary.map(&:to_pem) }
557556
end
558557

559-
560558
it 'handles OpenSSL::PKey::PKey objects for single case' do
561559
@settings.certificate = cert_text1
562560
@settings.private_key = OpenSSL::PKey::RSA.new(key_text1)

test/slo_logoutresponse_test.rb

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ class SloLogoutresponseTest < Minitest::Test
5454
unauth_url = RubySaml::SloLogoutresponse.new.create(settings, logout_request.id)
5555

5656
inflated = decode_saml_response_payload(unauth_url)
57-
assert_match(/InResponseTo='_c0348950-935b-0131-1060-782bcb56fcaa'/, inflated)
57+
assert_match(/InResponseTo="_c0348950-935b-0131-1060-782bcb56fcaa"/, inflated)
5858
end
5959

6060
it "set a custom successful logout message on the response" do
@@ -69,7 +69,7 @@ class SloLogoutresponseTest < Minitest::Test
6969

7070
inflated = decode_saml_response_payload(unauth_url)
7171
assert_match(/<samlp:StatusMessage>Custom Logout Message<\/samlp:StatusMessage>/, inflated)
72-
assert_match(/<samlp:StatusCode Value='urn:oasis:names:tc:SAML:2.0:status:PartialLogout/, inflated)
72+
assert_match(/<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2\.0:status:PartialLogout/, inflated)
7373
end
7474

7575
it "uses the response location when set" do
@@ -79,15 +79,15 @@ class SloLogoutresponseTest < Minitest::Test
7979
assert_match(/^http:\/\/unauth\.com\/logout\/return\?SAMLResponse=/, unauth_url)
8080

8181
inflated = decode_saml_response_payload(unauth_url)
82-
assert_match(/Destination='http:\/\/unauth.com\/logout\/return'/, inflated)
82+
assert_match(/Destination="http:\/\/unauth\.com\/logout\/return"/, inflated)
8383
end
8484

8585
describe "uuid" do
8686
it "uuid is initialized to nil" do
8787
response = RubySaml::SloLogoutresponse.new
8888

89-
assert_nil(response.uuid)
90-
assert_equal response.response_id, response.uuid
89+
assert_nil response.uuid
90+
assert_nil response.response_id
9191
end
9292

9393
it "creates response with ID prefixed with default '_'" do
@@ -120,8 +120,9 @@ class SloLogoutresponseTest < Minitest::Test
120120

121121
it "can mutate the uuid" do
122122
response = RubySaml::SloLogoutresponse.new
123-
response_id = response.response_id
124-
assert_equal response_id, response.uuid
123+
assert_nil response.uuid
124+
assert_nil response.response_id
125+
125126
response.uuid = "new_uuid"
126127
assert_equal "new_uuid", response.uuid
127128
assert_equal response.uuid, response.response_id

0 commit comments

Comments
 (0)