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
12 changes: 11 additions & 1 deletion lib/duo_api/api_client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ class DuoApi

VERSION = Gem.loaded_specs['duo_api'] ? Gem.loaded_specs['duo_api'].version : '0.0.0'

# Version of the bundled CA certificate collection
CA_BUNDLE_VERSION = '1.0'

# Constants for handling rate limit backoff
MAX_BACKOFF_WAIT_SECS = 32
INITIAL_BACKOFF_WAIT_SECS = 1
Expand Down Expand Up @@ -78,7 +81,7 @@ def request(method, path, params = {}, additional_headers = nil)
request = Net::HTTP.const_get(method.capitalize).new(uri.to_s)
request.basic_auth(@ikey, signed)
request['Date'] = current_date
request['User-Agent'] = "duo_api_ruby/#{VERSION}"
request['User-Agent'] = user_agent

# Set Content-Type and request body for JSON requests
if params_go_in_body
Expand Down Expand Up @@ -106,6 +109,13 @@ def request(method, path, params = {}, additional_headers = nil)

private

# Build the User-Agent string, including the CA bundle version and the
# current CA pinning state.
def user_agent
ca_pinning_state = @ca_pinning_disabled ? 'disable' : 'enable'
"duo_api_ruby/#{VERSION} ca_bundle/#{CA_BUNDLE_VERSION} (ca_pinning=#{ca_pinning_state})"
end

# Encode a key-value pair for a URL
def encode_key_val(key, val)
key = ERB::Util.url_encode(key.to_s)
Expand Down
48 changes: 48 additions & 0 deletions test/test_disable_ca_pinning.rb
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,51 @@ def test_unpinned_client_does_not_pass_ca_file_to_http
@client_unpinned.request('GET', '/foo/bar')
end
end

class TestUserAgent < BaseTestCase
def test_user_agent_includes_ca_bundle_version
assert_include(@client.send(:user_agent), "ca_bundle/#{DuoApi::CA_BUNDLE_VERSION}")
end

def test_user_agent_pinning_enabled_by_default
client = DuoApi.new(IKEY, SKEY, HOST)
assert_equal(
"duo_api_ruby/#{DuoApi::VERSION} ca_bundle/#{DuoApi::CA_BUNDLE_VERSION} (ca_pinning=enable)",
client.send(:user_agent)
)
end

def test_user_agent_pinning_disabled
client = DuoApi.new(IKEY, SKEY, HOST, nil, disable_ca_pinning: true)
assert_equal(
"duo_api_ruby/#{DuoApi::VERSION} ca_bundle/#{DuoApi::CA_BUNDLE_VERSION} (ca_pinning=disable)",
client.send(:user_agent)
)
end
end

class TestUserAgentHeader < BaseTestCase
def setup
@client_pinned = DuoApi.new(IKEY, SKEY, HOST)
@client_unpinned = DuoApi.new(IKEY, SKEY, HOST, nil, disable_ca_pinning: true)
@mock_http = mock
@ok_resp = Net::HTTPSuccess.new('200')
end

def assert_user_agent_header(client, expected_state)
Net::HTTP.expects(:start).yields(@mock_http)
@mock_http.expects(:request).with do |request|
request['User-Agent'] ==
"duo_api_ruby/#{DuoApi::VERSION} ca_bundle/#{DuoApi::CA_BUNDLE_VERSION} (ca_pinning=#{expected_state})"
end.returns(@ok_resp)
client.request('GET', '/foo/bar')
end

def test_pinned_client_sets_user_agent_header
assert_user_agent_header(@client_pinned, 'enable')
end

def test_unpinned_client_sets_user_agent_header
assert_user_agent_header(@client_unpinned, 'disable')
end
end
Loading