Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 39 additions & 33 deletions lib/auth0/mixins/httpproxy.rb
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
require "addressable/uri"
require "retryable"
require_relative "../exception.rb"
# frozen_string_literal: true

require 'addressable/uri'
require 'retryable'
require_relative '../exception'

module Auth0
module Mixins
# here's the proxy for Rest calls based on rest-client, we're building all request on that gem
# for now, if you want to feel free to use your own http client
module HTTPProxy
attr_accessor :headers, :base_uri, :timeout, :retry_count

DEFAULT_RETRIES = 3
MAX_ALLOWED_RETRIES = 10
MAX_REQUEST_RETRY_JITTER = 250
Expand All @@ -16,19 +19,19 @@ module HTTPProxy
BASE_DELAY = 100

# proxying requests from instance methods to HTTP class methods
%i(get post post_file post_form put patch delete delete_with_body).each do |method|
%i[get post post_file post_form put patch delete delete_with_body].each do |method|
define_method(method) do |uri, body = {}, extra_headers = {}|
body = body.delete_if { |_, v| v.nil? }
token = get_token()
body = body.dup.delete_if { |_, v| v.nil? } if body.is_a?(Hash)
token = get_token
authorization_header(token) unless token.nil?
request_with_retry(method, uri, body, extra_headers)
end
end

def retry_options
sleep_timer = lambda do |attempt|
wait = BASE_DELAY * (2**attempt-1) # Exponential delay with each subsequent request attempt.
wait += rand(wait+1..wait+MAX_REQUEST_RETRY_JITTER) # Add jitter to the delay window.
wait = BASE_DELAY * (2**attempt - 1) # Exponential delay with each subsequent request attempt.
wait += rand(wait + 1..wait + MAX_REQUEST_RETRY_JITTER) # Add jitter to the delay window.
wait = [MAX_REQUEST_RETRY_DELAY, wait].min # Cap delay at MAX_REQUEST_RETRY_DELAY.
wait = [MIN_REQUEST_RETRY_DELAY, wait].max # Ensure delay is no less than MIN_REQUEST_RETRY_DELAY.
wait / 1000.to_f.round(2) # convert ms to seconds
Expand All @@ -55,6 +58,7 @@ def url(path)

def add_headers(h = {})
raise ArgumentError, 'Headers must be an object which responds to #to_hash' unless h.respond_to?(:to_hash)

@headers ||= {}
@headers.merge!(h.to_hash)
end
Expand All @@ -72,36 +76,38 @@ def request_with_retry(method, uri, body = {}, extra_headers = {})
end

def request(method, uri, body = {}, extra_headers = {})
result = if method == :get
@headers ||= {}
get_headers = @headers.merge({params: body}).merge(extra_headers)
call(:get, encode_uri(uri), timeout, get_headers)
elsif method == :delete
@headers ||= {}
delete_headers = @headers.merge({ params: body })
call(:delete, encode_uri(uri), timeout, delete_headers)
elsif method == :delete_with_body
call(:delete, encode_uri(uri), timeout, headers, body.to_json)
elsif method == :post_file
body.merge!(multipart: true)
# Ignore the default Content-Type headers and let the HTTP client define them
post_file_headers = headers.except('Content-Type') if headers != nil
# Actual call with the altered headers
call(:post, encode_uri(uri), timeout, post_file_headers, body)
elsif method == :post_form
form_post_headers = headers.except('Content-Type') if headers != nil
call(:post, encode_uri(uri), timeout, form_post_headers, body.compact)
else
call(method, encode_uri(uri), timeout, headers, body.to_json)
end
result = case method
when :get
@headers ||= {}
get_headers = @headers.merge({ params: body }).merge(extra_headers)
call(:get, encode_uri(uri), timeout, get_headers)
when :delete
@headers ||= {}
delete_headers = @headers.merge({ params: body })
call(:delete, encode_uri(uri), timeout, delete_headers)
when :delete_with_body
call(:delete, encode_uri(uri), timeout, headers, body.to_json)
when :post_file
body.merge!(multipart: true)
# Ignore the default Content-Type headers and let the HTTP client define them
post_file_headers = headers.except('Content-Type') unless headers.nil?
# Actual call with the altered headers
call(:post, encode_uri(uri), timeout, post_file_headers, body)
when :post_form
form_post_headers = headers.except('Content-Type') unless headers.nil?
call(:post, encode_uri(uri), timeout, form_post_headers, body.compact)
else
call(method, encode_uri(uri), timeout, headers, body.to_json)
end

case result.code
when 200...226 then safe_parse_json(result.body)
when 400 then raise Auth0::BadRequest.new(result.body, code: result.code, headers: result.headers)
when 401 then raise Auth0::Unauthorized.new(result.body, code: result.code, headers: result.headers)
when 403 then raise Auth0::AccessDenied.new(result.body, code: result.code, headers: result.headers)
when 404 then raise Auth0::NotFound.new(result.body, code: result.code, headers: result.headers)
when 429 then raise Auth0::RateLimitEncountered.new(result.body, code: result.code, headers: result.headers)
when 429 then raise Auth0::RateLimitEncountered.new(result.body, code: result.code,
headers: result.headers)
when 500 then raise Auth0::ServerError.new(result.body, code: result.code, headers: result.headers)
else raise Auth0::Unsupported.new(result.body, code: result.code, headers: result.headers)
end
Expand All @@ -118,9 +124,9 @@ def call(method, url, timeout, headers, body = nil)
rescue RestClient::Exception => e
case e
when RestClient::RequestTimeout
raise Auth0::RequestTimeout.new(e.message)
raise Auth0::RequestTimeout, e.message
else
return e.response
e.response
end
end
end
Expand Down
13 changes: 13 additions & 0 deletions spec/lib/auth0/mixins/httpproxy_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,19 @@ def expected_payload(method, overrides = {})
expect { @instance.send(http_method, '/test') }.not_to raise_error
end

it "should handle array parameters for #{http_method} method" do
array_data = ['param1', 'param2']
if http_method == :post_form
expected_params = expected_payload(http_method, { payload: array_data })
else
expected_params = expected_payload(http_method, { payload: array_data.to_json })
end

expect(RestClient::Request).to receive(:execute).with(expected_params)
.and_return(StubResponse.new({}, true, 200))
expect { @instance.send(http_method, '/test', array_data) }.not_to raise_error
end

it 'should not raise exception if data returned not in json format (should be fixed in v2)' do
allow(RestClient::Request).to receive(:execute).with(expected_payload(http_method))
.and_return(StubResponse.new('Some random text here', true, 200))
Expand Down
Loading