Skip to content

Commit e5e8327

Browse files
Replace MAX_BYTE_SIZE constant with setting
The MAX_BYTE_SIZE constant did not allow for customization, which is necessary for cases where legitimate SAML responses are larger than 250,000 bytes. This replaces the constant with a setting, which has a default value of 250,000 bytes, but can be customized like any other setting.
1 parent dfab94b commit e5e8327

11 files changed

Lines changed: 51 additions & 45 deletions

README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -816,6 +816,28 @@ response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], :allowed_cloc
816816
817817
Make sure to keep the value as comfortably small as possible to keep security risks to a minimum.
818818
819+
## Deflation Limit
820+
821+
To protect against decompression bombs (a form of DoS attack), SAML messages are limited to 250,000 bytes by default.
822+
Sometimes legitimate SAML messages will exceed this limit,
823+
for example due to custom claims like including groups a user is a member of.
824+
If you want to customize this limit, you need to provide a different setting.
825+
Example:
826+
827+
```ruby
828+
def consume
829+
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse])
830+
response.settings = saml_settings
831+
...
832+
end
833+
834+
private
835+
836+
def saml_settings
837+
OneLogin::RubySaml::Settings.new(message_max_bytesize: 500000)
838+
end
839+
```
840+
819841
## Attribute Service
820842
821843
To request attributes from the IdP the SP needs to provide an attribute service within it's metadata and reference the index in the assertion.

lib/onelogin/ruby-saml/logoutresponse.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ class Logoutresponse < SamlMessage
3434
def initialize(response, settings = nil, options = {})
3535
@errors = []
3636
raise ArgumentError.new("Logoutresponse cannot be nil") if response.nil?
37-
@settings = settings
37+
@settings = settings || default_settings
3838

3939
if settings.nil? || settings.soft.nil?
4040
@soft = true
@@ -43,7 +43,7 @@ def initialize(response, settings = nil, options = {})
4343
end
4444

4545
@options = options
46-
@response = decode_raw_saml(response)
46+
@response = decode_raw_saml(response, settings)
4747
@document = XMLSecurity::SignedDocument.new(@response)
4848
end
4949

lib/onelogin/ruby-saml/response.rb

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -53,17 +53,11 @@ def initialize(response, options = {})
5353
raise ArgumentError.new("Response cannot be nil") if response.nil?
5454

5555
@errors = []
56-
5756
@options = options
58-
@soft = true
59-
unless options[:settings].nil?
60-
@settings = options[:settings]
61-
unless @settings.soft.nil?
62-
@soft = @settings.soft
63-
end
64-
end
57+
@settings = options[:settings] || default_settings
58+
@soft = @settings.soft || true
6559

66-
@response = decode_raw_saml(response)
60+
@response = decode_raw_saml(response, settings)
6761
@document = XMLSecurity::SignedDocument.new(@response, @errors)
6862

6963
if assertion_encrypted?
@@ -443,8 +437,6 @@ def validate_structure
443437
def validate_response_state
444438
return append_error("Blank response") if response.nil? || response.empty?
445439

446-
return append_error("No settings on response") if settings.nil?
447-
448440
if settings.idp_cert_fingerprint.nil? && settings.idp_cert.nil? && settings.idp_cert_multi.nil?
449441
return append_error("No fingerprint or certificate on settings")
450442
end

lib/onelogin/ruby-saml/saml_message.rb

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,6 @@ class SamlMessage
2222
BASE64_FORMAT = %r(\A([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?\Z)
2323
@@mutex = Mutex.new
2424

25-
MAX_BYTE_SIZE = 250000
26-
2725
# @return [Nokogiri::XML::Schema] Gets the schema object of the SAML 2.0 Protocol schema
2826
#
2927
def self.schema
@@ -88,11 +86,11 @@ def valid_saml?(document, soft = true)
8886
# @param saml [String] The deflated and encoded SAML Message
8987
# @return [String] The plain SAML Message
9088
#
91-
def decode_raw_saml(saml)
89+
def decode_raw_saml(saml, settings)
9290
return saml unless base64_encoded?(saml)
9391

94-
if saml.bytesize > MAX_BYTE_SIZE
95-
raise ValidationError.new("Encoded SAML Message exceeds " + MAX_BYTE_SIZE.to_s + " bytes, so was rejected")
92+
if saml.bytesize > settings.message_max_bytesize
93+
raise ValidationError.new("Encoded SAML Message exceeds " + settings.message_max_bytesize.to_s + " bytes, so was rejected")
9694
end
9795

9896
decoded = decode(saml)
@@ -157,6 +155,13 @@ def inflate(deflated)
157155
def deflate(inflated)
158156
Zlib::Deflate.deflate(inflated, 9)[2..-5]
159157
end
158+
159+
# Default settings
160+
# @return [OneLogin::RubySaml::Settings] A settings object
161+
#
162+
def default_settings
163+
OneLogin::RubySaml::Settings.new
164+
end
160165
end
161166
end
162167
end

lib/onelogin/ruby-saml/settings.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ def initialize(overrides = {}, keep_security_attributes = false)
5454
attr_accessor :compress_request
5555
attr_accessor :compress_response
5656
attr_accessor :double_quote_xml_attribute_values
57+
attr_accessor :message_max_bytesize
5758
attr_accessor :passive
5859
attr_reader :protocol_binding
5960
attr_accessor :attributes_index
@@ -264,6 +265,7 @@ def get_binding(value)
264265
:idp_cert_fingerprint_algorithm => XMLSecurity::Document::SHA1,
265266
:compress_request => true,
266267
:compress_response => true,
268+
:message_max_bytesize => 250000,
267269
:soft => true,
268270
:double_quote_xml_attribute_values => false,
269271
:security => {

lib/onelogin/ruby-saml/slo_logoutrequest.rb

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,10 @@ def initialize(request, options = {})
3535

3636
@errors = []
3737
@options = options
38-
@soft = true
39-
unless options[:settings].nil?
40-
@settings = options[:settings]
41-
unless @settings.soft.nil?
42-
@soft = @settings.soft
43-
end
44-
end
38+
@settings = options[:settings] || default_settings
39+
@soft = @settings.soft || true
4540

46-
@request = decode_raw_saml(request)
41+
@request = decode_raw_saml(request, settings)
4742
@document = REXML::Document.new(@request)
4843
end
4944

test/logoutresponse_test.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,16 @@ class RubySamlTest < Minitest::Test
77

88
describe "Logoutresponse" do
99

10+
let(:default_settings) { OneLogin::RubySaml::Settings.new }
1011
let(:valid_logout_response_without_settings) { OneLogin::RubySaml::Logoutresponse.new(valid_logout_response_document) }
1112
let(:valid_logout_response) { OneLogin::RubySaml::Logoutresponse.new(valid_logout_response_document, settings) }
1213

1314
describe "#new" do
1415
it "raise an exception when response is initialized with nil" do
1516
assert_raises(ArgumentError) { OneLogin::RubySaml::Logoutresponse.new(nil) }
1617
end
17-
it "default to empty settings" do
18-
assert_nil valid_logout_response_without_settings.settings
18+
it "default to default settings" do
19+
assert valid_logout_response_without_settings.settings.kind_of?(OneLogin::RubySaml::Settings)
1920
end
2021
it "accept constructor-injected settings" do
2122
refute_nil valid_logout_response.settings

test/response_test.rb

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -184,14 +184,6 @@ def generate_audience_error(expected, actual)
184184
assert_includes blank_response.errors, error_msg
185185
end
186186

187-
it "raise when settings have not been set" do
188-
error_msg = "No settings on response"
189-
assert_raises(OneLogin::RubySaml::ValidationError, error_msg) do
190-
response.is_valid?
191-
end
192-
assert_includes response.errors, error_msg
193-
end
194-
195187
it "raise when No fingerprint or certificate on settings" do
196188
settings.idp_cert_fingerprint = nil
197189
settings.idp_cert = nil
@@ -342,11 +334,6 @@ def generate_audience_error(expected, actual)
342334
assert_includes blank_response.errors, "Blank response"
343335
end
344336

345-
it "return false if settings have not been set" do
346-
assert !response.is_valid?
347-
assert_includes response.errors, "No settings on response"
348-
end
349-
350337
it "return false if fingerprint or certificate not been set on settings" do
351338
response.settings = settings
352339
assert !response.is_valid?

test/saml_message_test.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ class RubySamlTest < Minitest::Test
1010
let(:response_document_xml) { read_response("adfs_response_xmlns.xml") }
1111

1212
it "return decoded raw saml" do
13-
decoded_raw = saml_message.send(:decode_raw_saml, logout_request_deflated_base64)
13+
decoded_raw = saml_message.send(:decode_raw_saml, logout_request_deflated_base64, settings)
1414
assert logout_request_document, decoded_raw
1515
end
1616

@@ -64,9 +64,9 @@ class RubySamlTest < Minitest::Test
6464

6565
data = prefix + "A" * (200000 * 1024) + suffix
6666
bomb = Base64.encode64(Zlib::Deflate.deflate(data, 9)[2..-5])
67-
assert_raises(OneLogin::RubySaml::ValidationError, "Encoded SAML Message exceeds " + OneLogin::RubySaml::SamlMessage::MAX_BYTE_SIZE.to_s + " bytes, so was rejected") do
67+
assert_raises(OneLogin::RubySaml::ValidationError, "Encoded SAML Message exceeds " + OneLogin::RubySaml::Settings::DEFAULTS[:message_max_bytesize].to_s + " bytes, so was rejected") do
6868
saml_message = OneLogin::RubySaml::SamlMessage.new
69-
saml_message.send(:decode_raw_saml, bomb)
69+
saml_message.send(:decode_raw_saml, bomb, settings)
7070
end
7171
end
7272
end

test/settings_test.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ class SettingsTest < Minitest::Test
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,
1919
:sessionindex, :attributes_index, :passive, :force_authn,
20-
:compress_request, :double_quote_xml_attribute_values,
20+
:compress_request, :double_quote_xml_attribute_values, :message_max_bytesize,
2121
:security, :certificate, :private_key,
2222
:authn_context, :authn_context_comparison, :authn_context_decl_ref,
2323
:assertion_consumer_logout_service_url
@@ -89,6 +89,7 @@ class SettingsTest < Minitest::Test
8989
:idp_slo_service_url => "http://sso.muda.no/slo",
9090
:idp_slo_service_binding => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
9191
:idp_cert_fingerprint => "00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00",
92+
:message_max_bytesize => 750000,
9293
:valid_until => '2029-04-16T03:35:08.277Z',
9394
:name_identifier_format => "urn:oasis:names:tc:SAML:2.0:nameid-format:transient",
9495
:attributes_index => 30,

0 commit comments

Comments
 (0)