Skip to content

Commit

Permalink
Allow passing in timeout/retry settings to Net::HTTP
Browse files Browse the repository at this point in the history
When fetching remote XML files from arbitrary URLs, you might want to configure different values for timeouts/retries to avoid allowing users to DoS you via intentionally slow endpoints.  The Net::HTTP defaults are 60 seconds plus 1 retry, which could easily deplete resources if intentionally exploited.
  • Loading branch information
tjschuck committed Feb 28, 2024
1 parent a3844d2 commit b7df715
Show file tree
Hide file tree
Showing 2 changed files with 43 additions and 7 deletions.
37 changes: 30 additions & 7 deletions lib/onelogin/ruby-saml/idp_metadata_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ def self.get_idps(metadata_document, only_entity_id=nil)
# @param url [String] Url where the XML of the Identity Provider Metadata is published.
# @param validate_cert [Boolean] If true and the URL is HTTPs, the cert of the domain is checked.
#
# @param request_options [Hash] options used for requesting the remote URL
# @option request_options [Numeric, nil] :open_timeout Number of seconds to wait for the connection to open. See Net::HTTP#open_timeout for more info. Default is the Net::HTTP default.
# @option request_options [Numeric, nil] :read_timeout Number of seconds to wait for one block to be read. See Net::HTTP#read_timeout for more info. Default is the Net::HTTP default.
# @option request_options [Integer, nil] :max_retries Maximum number of times to retry the request on certain errors. See Net::HTTP#max_retries= for more info. Default is the Net::HTTP default.
#
# @param options [Hash] options used for parsing the metadata and the returned Settings instance
# @option options [OneLogin::RubySaml::Settings, Hash] :settings the OneLogin::RubySaml::Settings object which gets the parsed metadata merged into or an hash for Settings overrides.
# @option options [String, nil] :entity_id when this is given, the entity descriptor for this ID is used. When omitted, the first entity descriptor is used.
Expand All @@ -64,8 +69,8 @@ def self.get_idps(metadata_document, only_entity_id=nil)
# @return [OneLogin::RubySaml::Settings]
#
# @raise [HttpError] Failure to fetch remote IdP metadata
def parse_remote(url, validate_cert = true, options = {})
idp_metadata = get_idp_metadata(url, validate_cert)
def parse_remote(url, validate_cert = true, request_options = {}, options = {})
idp_metadata = get_idp_metadata(url, validate_cert, request_options)
parse(idp_metadata, options)
end

Expand All @@ -74,6 +79,11 @@ def parse_remote(url, validate_cert = true, options = {})
# @param url [String] Url where the XML of the Identity Provider Metadata is published.
# @param validate_cert [Boolean] If true and the URL is HTTPs, the cert of the domain is checked.
#
# @param request_options [Hash] options used for requesting the remote URL
# @option request_options [Numeric, nil] :open_timeout Number of seconds to wait for the connection to open. See Net::HTTP#open_timeout for more info. Default is the Net::HTTP default.
# @option request_options [Numeric, nil] :read_timeout Number of seconds to wait for one block to be read. See Net::HTTP#read_timeout for more info. Default is the Net::HTTP default.
# @option request_options [Integer, nil] :max_retries Maximum number of times to retry the request on certain errors. See Net::HTTP#max_retries= for more info. Default is the Net::HTTP default.
#
# @param options [Hash] options used for parsing the metadata
# @option options [String, nil] :entity_id when this is given, the entity descriptor for this ID is used. When omitted, the first entity descriptor is used.
# @option options [String, Array<String>, nil] :sso_binding an ordered list of bindings to detect the single signon URL. The first binding in the list that is included in the metadata will be used.
Expand All @@ -83,15 +93,20 @@ def parse_remote(url, validate_cert = true, options = {})
# @return [Hash]
#
# @raise [HttpError] Failure to fetch remote IdP metadata
def parse_remote_to_hash(url, validate_cert = true, options = {})
parse_remote_to_array(url, validate_cert, options)[0]
def parse_remote_to_hash(url, validate_cert = true, request_options = {}, options = {})
parse_remote_to_array(url, validate_cert, request_options, options)[0]
end

# Parse all Identity Provider metadata and return the results as Array
#
# @param url [String] Url where the XML of the Identity Provider Metadata is published.
# @param validate_cert [Boolean] If true and the URL is HTTPs, the cert of the domain is checked.
#
# @param request_options [Hash] options used for requesting the remote URL
# @option request_options [Numeric, nil] :open_timeout Number of seconds to wait for the connection to open. See Net::HTTP#open_timeout for more info. Default is the Net::HTTP default.
# @option request_options [Numeric, nil] :read_timeout Number of seconds to wait for one block to be read. See Net::HTTP#read_timeout for more info. Default is the Net::HTTP default.
# @option request_options [Integer, nil] :max_retries Maximum number of times to retry the request on certain errors. See Net::HTTP#max_retries= for more info. Default is the Net::HTTP default.
#
# @param options [Hash] options used for parsing the metadata
# @option options [String, nil] :entity_id when this is given, the entity descriptor for this ID is used. When omitted, all found IdPs are returned.
# @option options [String, Array<String>, nil] :sso_binding an ordered list of bindings to detect the single signon URL. The first binding in the list that is included in the metadata will be used.
Expand All @@ -101,8 +116,8 @@ def parse_remote_to_hash(url, validate_cert = true, options = {})
# @return [Array<Hash>]
#
# @raise [HttpError] Failure to fetch remote IdP metadata
def parse_remote_to_array(url, validate_cert = true, options = {})
idp_metadata = get_idp_metadata(url, validate_cert)
def parse_remote_to_array(url, validate_cert = true, request_options = {}, options = {})
idp_metadata = get_idp_metadata(url, validate_cert, request_options)
parse_to_array(idp_metadata, options)
end

Expand Down Expand Up @@ -191,9 +206,13 @@ def parse_to_idp_metadata_array(idp_metadata, options = {})
# Retrieve the remote IdP metadata from the URL or a cached copy.
# @param url [String] Url where the XML of the Identity Provider Metadata is published.
# @param validate_cert [Boolean] If true and the URL is HTTPs, the cert of the domain is checked.
# @param request_options [Hash] options used for requesting the remote URL
# @option request_options [Numeric, nil] :open_timeout Number of seconds to wait for the connection to open. See Net::HTTP#open_timeout for more info. Default is the Net::HTTP default.
# @option request_options [Numeric, nil] :read_timeout Number of seconds to wait for one block to be read. See Net::HTTP#read_timeout for more info. Default is the Net::HTTP default.
# @option request_options [Integer, nil] :max_retries Maximum number of times to retry the request on certain errors. See Net::HTTP#max_retries= for more info. Default is the Net::HTTP default.
# @return [REXML::document] Parsed XML IdP metadata
# @raise [HttpError] Failure to fetch remote IdP metadata
def get_idp_metadata(url, validate_cert)
def get_idp_metadata(url, validate_cert, request_options)
uri = URI.parse(url)
raise ArgumentError.new("url must begin with http or https") unless /^https?/ =~ uri.scheme
http = Net::HTTP.new(uri.host, uri.port)
Expand All @@ -210,6 +229,10 @@ def get_idp_metadata(url, validate_cert)
end
end

http.open_timeout = request_options[:open_timeout] if request_options[:open_timeout]
http.read_timeout = request_options[:read_timeout] if request_options[:read_timeout]
http.max_retries = request_options[:max_retries] if request_options[:max_retries]

get = Net::HTTP::Get.new(uri.request_uri)
get.basic_auth uri.user, uri.password if uri.user
@response = http.request(get)
Expand Down
13 changes: 13 additions & 0 deletions test/idp_metadata_parser_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,19 @@ def initialize; end

assert_equal OpenSSL::SSL::VERIFY_NONE, @http.verify_mode
end

it "allows setting HTTP options on the request" do
refute_equal 2, @http.open_timeout
refute_equal 3, @http.read_timeout
refute_equal 4, @http.max_retries

idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new
settings = idp_metadata_parser.parse_remote(@url, true, open_timeout: 2, read_timeout: 3, max_retries: 4)

assert_equal 2, @http.open_timeout
assert_equal 3, @http.read_timeout
assert_equal 4, @http.max_retries
end
end

describe "download and parse IdP descriptor file into an Hash" do
Expand Down

0 comments on commit b7df715

Please sign in to comment.