From 9b6ef49a3ea88d2993faa1bac52241e921365dad Mon Sep 17 00:00:00 2001 From: Yenfry Herrera Feliz Date: Wed, 15 May 2024 17:04:42 -0400 Subject: [PATCH] feat: add AppID user agent parameter (#2917) Co-authored-by: Sean O'Brien <60306702+stobrien89@users.noreply.github.com> --- .../nextrelease/fix-user-agent-header.json | 7 ++ src/AwsClient.php | 7 ++ src/ClientResolver.php | 59 +++++++++++-- tests/ClientResolverTest.php | 86 +++++++++++++++++++ 4 files changed, 150 insertions(+), 9 deletions(-) create mode 100644 .changes/nextrelease/fix-user-agent-header.json diff --git a/.changes/nextrelease/fix-user-agent-header.json b/.changes/nextrelease/fix-user-agent-header.json new file mode 100644 index 0000000000..f0c0c4256c --- /dev/null +++ b/.changes/nextrelease/fix-user-agent-header.json @@ -0,0 +1,7 @@ +[ + { + "type": "feature", + "category": "User-Agent", + "description": "Update user agent implementation for supporting AppId and user agent version" + } +] diff --git a/src/AwsClient.php b/src/AwsClient.php index 3f2b03699e..3312b9b8a7 100644 --- a/src/AwsClient.php +++ b/src/AwsClient.php @@ -203,6 +203,13 @@ public static function getArguments() * client-side parameter validation. * - version: (string, required) The version of the webservice to * utilize (e.g., 2006-03-01). + * - ua_append: (string, array) To pass custom user agent parameters. + * - app_id: (string) an optional application specific identifier that can be set. + * When set it will be appended to the User-Agent header of every request + * in the form of App/{AppId}. This variable is sourced from environment + * variable AWS_SDK_UA_APP_ID or the shared config profile attribute sdk_ua_app_id. + * See https://docs.aws.amazon.com/sdkref/latest/guide/settings-reference.html for + * more information on environment variables and shared config settings. * * @param array $args Client configuration arguments. * diff --git a/src/ClientResolver.php b/src/ClientResolver.php index e0b650fb24..18a0eef744 100644 --- a/src/ClientResolver.php +++ b/src/ClientResolver.php @@ -270,6 +270,15 @@ class ClientResolver 'fn' => [__CLASS__, '_apply_handler'], 'default' => [__CLASS__, '_default_handler'] ], + 'app_id' => [ + 'type' => 'value', + 'valid' => ['string'], + 'doc' => 'app_id(AppId) is an optional application specific identifier that can be set. + When set it will be appended to the User-Agent header of every request in the form of App/{AppId}. + This value is also sourced from environment variable AWS_SDK_UA_APP_ID or the shared config profile attribute sdk_ua_app_id.', + 'fn' => [__CLASS__, '_apply_app_id'], + 'default' => [__CLASS__, '_default_app_id'] + ], 'ua_append' => [ 'type' => 'value', 'valid' => ['string', 'array'], @@ -917,17 +926,43 @@ public static function _apply_http_handler($value, array &$args, HandlerList $li ); } + public static function _apply_app_id($value, array &$args) + { + // AppId should not be longer than 50 chars + static $MAX_APP_ID_LENGTH = 50; + if (strlen($value) > $MAX_APP_ID_LENGTH) { + trigger_error("The provided or configured value for `AppId`, " + ."which is an user agent parameter, exceeds the maximum length of " + ."$MAX_APP_ID_LENGTH characters.", E_USER_WARNING); + } + + $args['app_id'] = $value; + } + + public static function _default_app_id(array $args) + { + return ConfigurationResolver::resolve( + 'sdk_ua_app_id', + '', + 'string', + $args + ); + } + public static function _apply_user_agent($inputUserAgent, array &$args, HandlerList $list) { - //Add SDK version + // Add SDK version $userAgent = ['aws-sdk-php/' . Sdk::VERSION]; - //If on HHVM add the HHVM version + // User Agent Metadata + $userAgent[] = 'ua/2.0'; + + // If on HHVM add the HHVM version if (defined('HHVM_VERSION')) { $userAgent []= 'HHVM/' . HHVM_VERSION; } - //Add OS version + // Add OS version $disabledFunctions = explode(',', ini_get('disable_functions')); if (function_exists('php_uname') && !in_array('php_uname', $disabledFunctions, true) @@ -938,15 +973,15 @@ public static function _apply_user_agent($inputUserAgent, array &$args, HandlerL } } - //Add the language version + // Add the language version $userAgent []= 'lang/php#' . phpversion(); - //Add exec environment if present + // Add exec environment if present if ($executionEnvironment = getenv('AWS_EXECUTION_ENV')) { $userAgent []= $executionEnvironment; } - //Add endpoint discovery if set + // Add endpoint discovery if set if (isset($args['endpoint_discovery'])) { if (($args['endpoint_discovery'] instanceof \Aws\EndpointDiscovery\Configuration && $args['endpoint_discovery']->isEnabled()) @@ -960,7 +995,7 @@ public static function _apply_user_agent($inputUserAgent, array &$args, HandlerL } } - //Add retry mode if set + // Add retry mode if set if (isset($args['retries'])) { if ($args['retries'] instanceof \Aws\Retry\Configuration) { $userAgent []= 'cfg/retry-mode#' . $args["retries"]->getMode(); @@ -970,7 +1005,13 @@ public static function _apply_user_agent($inputUserAgent, array &$args, HandlerL $userAgent []= 'cfg/retry-mode#' . $args["retries"]["mode"]; } } - //Add the input to the end + + // AppID Metadata + if (!empty($args['app_id'])) { + $userAgent[] = 'app/' . $args['app_id']; + } + + // Add the input to the end if ($inputUserAgent){ if (!is_array($inputUserAgent)) { $inputUserAgent = [$inputUserAgent]; @@ -1188,7 +1229,7 @@ public static function _default_endpoint(array &$args) return $value; } - + public static function _apply_region($value, array &$args) { if (empty($value)) { diff --git a/tests/ClientResolverTest.php b/tests/ClientResolverTest.php index beef196c60..639a726acd 100644 --- a/tests/ClientResolverTest.php +++ b/tests/ClientResolverTest.php @@ -18,6 +18,7 @@ use Aws\HandlerList; use Aws\Sdk; use Aws\Result; +use GuzzleHttp\Psr7\Response; use Psr\Http\Message\RequestInterface; use Yoast\PHPUnitPolyfills\TestCases\TestCase; @@ -1574,4 +1575,89 @@ public function testIgnoreConfiguredEndpointUrls() putenv('AWS_ENDPOINT_URL' . '='); putenv('AWS_ENDPOINT_URL_S3' . '='); } + + public function testResolvesAppIdFromClientConfig() + { + $appId = 'TestAppId'; + $s3 = new S3Client([ + 'region' => 'us-east-1', + 'app_id' => $appId, + 'http_handler' => function (RequestInterface $request) use ($appId) { + $userAgentValues = explode(' ', $request->getHeader('user-agent')[0]); + $expectedHeader = "app/$appId"; + $idx = array_search($expectedHeader, $userAgentValues); + $this->assertNotFalse($idx); + $this->assertEquals($expectedHeader, $userAgentValues[$idx]); + + return new Response; + } + ]); + $s3->listBuckets(); + } + + public function testResolvesAppIdSourcedFromEnv() + { + $currentAppIdFromEnv = getenv('AWS_SDK_UA_APP_ID'); + $deferTask = function () use ($currentAppIdFromEnv) { + if (!empty($currentAppIdFromEnv)) { + putenv("AWS_SDK_UA_APP_ID=$currentAppIdFromEnv"); + } + }; + + try { + $appId = 'TestAppId'; + putenv("AWS_SDK_UA_APP_ID=$appId"); + $s3 = new S3Client([ + 'region' => 'us-east-1', + 'http_handler' => function (RequestInterface $request) use ($appId) { + $userAgentValues = explode(' ', $request->getHeader('user-agent')[0]); + $expectedHeader = "app/$appId"; + $idx = array_search($expectedHeader, $userAgentValues); + $this->assertNotFalse($idx); + $this->assertEquals($expectedHeader, $userAgentValues[$idx]); + + return new Response; + } + ]); + $s3->listBuckets(); + } finally { + $deferTask(); + } + } + + public function testResolvesAppIdSourcedFromIniFile() + { + $tempIniConfigFile = sys_get_temp_dir() . '/.aws/config'; + $currentAwsConfigFileFromEnv = getenv('AWS_CONFIG_FILE'); + $deferTask = function () use ($tempIniConfigFile, $currentAwsConfigFileFromEnv) { + unlink($tempIniConfigFile); + if (!empty($currentAwsConfigFileFromEnv)) { + putenv("AWS_CONFIG_FILE=$currentAwsConfigFileFromEnv"); + } + }; + $appId = 'TestAppId'; + $iniContent = << 'us-east-1', + 'http_handler' => function (RequestInterface $request) use ($appId) { + $userAgentValues = explode(' ', $request->getHeader('user-agent')[0]); + $expectedHeader = "app/$appId"; + $idx = array_search($expectedHeader, $userAgentValues); + $this->assertNotFalse($idx); + $this->assertEquals($expectedHeader, $userAgentValues[$idx]); + + return new Response; + } + ]); + $s3->listBuckets(); + } finally { + $deferTask(); + } + } }