diff --git a/src/Client.php b/src/Client.php index bd6a9c3..c47d27b 100644 --- a/src/Client.php +++ b/src/Client.php @@ -22,6 +22,7 @@ class Client public $paging; public $options; public $sleep_service; + private $user_agent_extension = ""; public function __construct( $ikey, @@ -89,6 +90,12 @@ public function disableCaPinning() return $this; } + public function appendToUserAgent(string $user_agent_extension) + { + $this->user_agent_extension = trim($user_agent_extension); + return $this; + } + private function signParameters($method, $host, $path, $params, $skey, $ikey, $now, $body, $additional_headers) { assert(is_string($method)); @@ -242,9 +249,13 @@ public function apiCall($method, $path, $params, $additional_headers = []) $headers["Date"] = $now; $ca_pinning_status = (!empty($this->options["disable_ca_pinning"])) ? "disabled" : "enabled"; - $headers["User-Agent"] = "duo_api_php/" . VERSION + $user_agent = "duo_api_php/" . VERSION . " ca_bundle/" . CA_BUNDLE_VERSION . " (ca_pinning=" . $ca_pinning_status . ")"; + if (!empty($this->user_agent_extension)) { + $user_agent .= " " . $this->user_agent_extension; + } + $headers["User-Agent"] = $user_agent; $headers["Authorization"] = self::signParameters( $method, $this->host, diff --git a/tests/Unit/ClientTest.php b/tests/Unit/ClientTest.php index b5adefd..57a33d1 100644 --- a/tests/Unit/ClientTest.php +++ b/tests/Unit/ClientTest.php @@ -533,6 +533,119 @@ public function testUserAgentWithCaPinningDisabled() $response = $client->apiCall("GET", "/foo/bar", []); } + public function testUserAgentWithExtension() + { + $success_resp = [ + "success" => true, + "response" => "not rate limited", + "http_status_code" => 200, + ]; + + $response = [$success_resp]; + + $client = self::getMockedClient("Client", $response, $paged = true); + $client->appendToUserAgent("MyApp/1.0"); + $this->mocked_curl_requester->method('execute')->with( + $this->anything(), + $this->anything(), + $this->callback(function($headers) { + $expected = "duo_api_php/" . \DuoAPI\VERSION + . " ca_bundle/" . \DuoAPI\CA_BUNDLE_VERSION + . " (ca_pinning=enabled) MyApp/1.0"; + $this->assertArrayHasKey("User-Agent", $headers); + $this->assertEquals($expected, $headers["User-Agent"]); + return true; + }), + $this->anything() + ); + $response = $client->apiCall("GET", "/foo/bar", []); + } + + public function testUserAgentWithWhitespaceExtension() + { + $success_resp = [ + "success" => true, + "response" => "not rate limited", + "http_status_code" => 200, + ]; + + $response = [$success_resp]; + + $client = self::getMockedClient("Client", $response, $paged = true); + $client->appendToUserAgent(" "); + $this->mocked_curl_requester->method('execute')->with( + $this->anything(), + $this->anything(), + $this->callback(function($headers) { + $expected = "duo_api_php/" . \DuoAPI\VERSION + . " ca_bundle/" . \DuoAPI\CA_BUNDLE_VERSION + . " (ca_pinning=enabled)"; + $this->assertArrayHasKey("User-Agent", $headers); + $this->assertEquals($expected, $headers["User-Agent"]); + return true; + }), + $this->anything() + ); + $response = $client->apiCall("GET", "/foo/bar", []); + } + + public function testUserAgentExtensionIsTrimmed() + { + $success_resp = [ + "success" => true, + "response" => "not rate limited", + "http_status_code" => 200, + ]; + + $response = [$success_resp]; + + $client = self::getMockedClient("Client", $response, $paged = true); + $client->appendToUserAgent(" MyApp/1.0 "); + $this->mocked_curl_requester->method('execute')->with( + $this->anything(), + $this->anything(), + $this->callback(function($headers) { + $expected = "duo_api_php/" . \DuoAPI\VERSION + . " ca_bundle/" . \DuoAPI\CA_BUNDLE_VERSION + . " (ca_pinning=enabled) MyApp/1.0"; + $this->assertArrayHasKey("User-Agent", $headers); + $this->assertEquals($expected, $headers["User-Agent"]); + return true; + }), + $this->anything() + ); + $response = $client->apiCall("GET", "/foo/bar", []); + } + + public function testAppendToUserAgentOverwritesPrevious() + { + $success_resp = [ + "success" => true, + "response" => "not rate limited", + "http_status_code" => 200, + ]; + + $response = [$success_resp]; + + $client = self::getMockedClient("Client", $response, $paged = true); + $client->appendToUserAgent("First/1.0"); + $client->appendToUserAgent("Second/2.0"); + $this->mocked_curl_requester->method('execute')->with( + $this->anything(), + $this->anything(), + $this->callback(function($headers) { + $expected = "duo_api_php/" . \DuoAPI\VERSION + . " ca_bundle/" . \DuoAPI\CA_BUNDLE_VERSION + . " (ca_pinning=enabled) Second/2.0"; + $this->assertArrayHasKey("User-Agent", $headers); + $this->assertEquals($expected, $headers["User-Agent"]); + return true; + }), + $this->anything() + ); + $response = $client->apiCall("GET", "/foo/bar", []); + } + public function testDisableCaPinning() { $client = new \DuoAPI\Client("TEST", "TEST", "TEST");