diff --git a/.travis.yml b/.travis.yml index 8c1b058ac..00c6b2a55 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,24 +1,48 @@ language: php -branches: - only: [master] - -sudo: false - -php: - - 5.4 - - 5.5 - - 5.6 - - 7.0 - - 7.1 - - 7.2 - -env: - - - - COMPOSER_ARGS="--prefer-lowest" matrix: include: - - php: "7.2" + - name: "PHP 5.4" + php: "5.4" + - name: "PHP 5.4 (Prefer Lowest Dependency Version)" + php: "5.4" + env: COMPOSER_ARGS="--prefer-lowest" + + - name: "PHP 5.5" + php: "5.5" + - name: "PHP 5.5 (Prefer Lowest Dependency Version)" + php: "5.5" + env: COMPOSER_ARGS="--prefer-lowest" + + - name: "PHP 5.6" + php: "5.6" + - name: "PHP 5.6 (Prefer Lowest Dependency Version)" + php: "5.6" + env: COMPOSER_ARGS="--prefer-lowest" + + - name: "PHP 7.0" + php: "7.0" + - name: "PHP 7.0 (Prefer Lowest Dependency Version)" + php: "7.0" + env: COMPOSER_ARGS="--prefer-lowest" + + - name: "PHP 7.1" + php: "7.1" + - name: "PHP 7.1 (Prefer Lowest Dependency Version)" + php: "7.1" + env: COMPOSER_ARGS="--prefer-lowest" + + - name: "PHP 7.2" + php: "7.2" + - name: "PHP 7.2 (Prefer Lowest Dependency Version)" + php: "7.2" + env: COMPOSER_ARGS="--prefer-lowest" + + - name: "PHP 7.3" + php: "7.3" + + - name: "Check Style" + php: "7.3" env: RUN_CS_FIXER=true before_script: diff --git a/composer.json b/composer.json index 8dfa95a97..3ded33dfa 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,11 @@ "guzzlehttp/promises": "0.1.1|^1.3", "friendsofphp/php-cs-fixer": "^1.11", "phpunit/phpunit": "^4.8.36|^5.7", - "sebastian/comparator": ">=1.2.3" + "sebastian/comparator": ">=1.2.3", + "phpseclib/phpseclib": "^2" + }, + "suggest": { + "phpseclib/phpseclib": "May be used in place of OpenSSL for signing strings. Please require version ^2." }, "autoload": { "psr-4": { diff --git a/src/ApplicationDefaultCredentials.php b/src/ApplicationDefaultCredentials.php index 6465bdcb8..eca3dc92d 100644 --- a/src/ApplicationDefaultCredentials.php +++ b/src/ApplicationDefaultCredentials.php @@ -20,8 +20,11 @@ use DomainException; use Google\Auth\Credentials\AppIdentityCredentials; use Google\Auth\Credentials\GCECredentials; +use Google\Auth\HttpHandler\HttpClientCache; +use Google\Auth\HttpHandler\HttpHandlerFactory; use Google\Auth\Middleware\AuthTokenMiddleware; use Google\Auth\Subscriber\AuthTokenSubscriber; +use GuzzleHttp\Client; use Psr\Cache\CacheItemPoolInterface; /** @@ -144,6 +147,15 @@ public static function getCredentials( $jsonKey = CredentialsLoader::fromEnv() ?: CredentialsLoader::fromWellKnownFile(); + if (!$httpHandler) { + if (!($client = HttpClientCache::getHttpClient())) { + $client = new Client(); + HttpClientCache::setHttpClient($client); + } + + $httpHandler = HttpHandlerFactory::build($client); + } + if (!is_null($jsonKey)) { $creds = CredentialsLoader::makeCredentials($scope, $jsonKey); } elseif (AppIdentityCredentials::onAppEngine() && !GCECredentials::onAppEngineFlexible()) { diff --git a/src/Credentials/AppIdentityCredentials.php b/src/Credentials/AppIdentityCredentials.php index 15c0074b8..31342e6f9 100644 --- a/src/Credentials/AppIdentityCredentials.php +++ b/src/Credentials/AppIdentityCredentials.php @@ -24,6 +24,7 @@ */ use google\appengine\api\app_identity\AppIdentityService; use Google\Auth\CredentialsLoader; +use Google\Auth\SignBlobInterface; /** * AppIdentityCredentials supports authorization on Google App Engine. @@ -49,7 +50,7 @@ * * $res = $client->get('volumes?q=Henry+David+Thoreau&country=US'); */ -class AppIdentityCredentials extends CredentialsLoader +class AppIdentityCredentials extends CredentialsLoader implements SignBlobInterface { /** * Result of fetchAuthToken. @@ -63,6 +64,11 @@ class AppIdentityCredentials extends CredentialsLoader */ private $scope; + /** + * @var string + */ + private $clientName; + public function __construct($scope = array()) { $this->scope = $scope; @@ -100,23 +106,16 @@ public static function onAppEngine() * @param callable $httpHandler callback which delivers psr7 request * * @return array A set of auth related metadata, containing the following - * keys: - * - access_token (string) - * - expiration_time (string) - * - * @throws \Exception + * keys: + * - access_token (string) + * - expiration_time (string) */ public function fetchAuthToken(callable $httpHandler = null) { - if (!self::onAppEngine()) { - return array(); - } - - if (!class_exists('google\appengine\api\app_identity\AppIdentityService')) { - throw new \Exception( - 'This class must be run in App Engine, or you must include the AppIdentityService ' - . 'mock class defined in tests/mocks/AppIdentityService.php' - ); + try { + $this->checkAppEngineContext(); + } catch (\Exception $e) { + return []; } // AppIdentityService expects an array when multiple scopes are supplied @@ -128,6 +127,42 @@ public function fetchAuthToken(callable $httpHandler = null) return $token; } + /** + * Sign a string using AppIdentityService. + * + * @param string $stringToSign The string to sign. + * @param bool $forceOpenSsl [optional] Does not apply to this credentials + * type. + * @return string The signature, base64-encoded. + * @throws \Exception If AppEngine SDK or mock is not available. + */ + public function signBlob($stringToSign, $forceOpenSsl = false) + { + $this->checkAppEngineContext(); + + return base64_encode(AppIdentityService::signForApp($stringToSign)['signature']); + } + + /** + * Get the client name from AppIdentityService. + * + * Subsequent calls to this method will return a cached value. + * + * @param callable $httpHandler Not used in this implementation. + * @return string + * @throws \Exception If AppEngine SDK or mock is not available. + */ + public function getClientName(callable $httpHandler = null) + { + $this->checkAppEngineContext(); + + if (!$this->clientName) { + $this->clientName = AppIdentityService::getServiceAccountName(); + } + + return $this->clientName; + } + /** * @return array|null */ @@ -153,4 +188,14 @@ public function getCacheKey() { return ''; } + + private function checkAppEngineContext() + { + if (!self::onAppEngine() || !class_exists('google\appengine\api\app_identity\AppIdentityService')) { + throw new \Exception( + 'This class must be run in App Engine, or you must include the AppIdentityService ' + . 'mock class defined in tests/mocks/AppIdentityService.php' + ); + } + } } diff --git a/src/Credentials/GCECredentials.php b/src/Credentials/GCECredentials.php index b616bf5e3..a6788fb77 100644 --- a/src/Credentials/GCECredentials.php +++ b/src/Credentials/GCECredentials.php @@ -18,7 +18,10 @@ namespace Google\Auth\Credentials; use Google\Auth\CredentialsLoader; +use Google\Auth\HttpHandler\HttpClientCache; use Google\Auth\HttpHandler\HttpHandlerFactory; +use Google\Auth\Iam; +use Google\Auth\SignBlobInterface; use GuzzleHttp\Exception\ClientException; use GuzzleHttp\Exception\RequestException; use GuzzleHttp\Exception\ServerException; @@ -48,9 +51,10 @@ * * $res = $client->get('myproject/taskqueues/myqueue'); */ -class GCECredentials extends CredentialsLoader +class GCECredentials extends CredentialsLoader implements SignBlobInterface { const cacheKey = 'GOOGLE_AUTH_PHP_GCE'; + /** * The metadata IP address on appengine instances. * @@ -64,6 +68,11 @@ class GCECredentials extends CredentialsLoader */ const TOKEN_URI_PATH = 'v1/instance/service-accounts/default/token'; + /** + * The metadata path of the client ID. + */ + const CLIENT_ID_URI_PATH = 'v1/instance/service-accounts/default/email'; + /** * The header whose presence indicates GCE presence. */ @@ -101,6 +110,24 @@ class GCECredentials extends CredentialsLoader */ protected $lastReceivedToken; + /** + * @var string + */ + private $clientName; + + /** + * @var Iam|null + */ + private $iam; + + /** + * @param Iam $iam [optional] An IAM instance. + */ + public function __construct(Iam $iam = null) + { + $this->iam = $iam; + } + /** * The full uri for accessing the default token. * @@ -113,6 +140,18 @@ public static function getTokenUri() return $base . self::TOKEN_URI_PATH; } + /** + * The full uri for accessing the default service account. + * + * @return string + */ + public static function getClientNameUri() + { + $base = 'http://' . self::METADATA_IP . '/computeMetadata/'; + + return $base . self::CLIENT_ID_URI_PATH; + } + /** * Determines if this an App Engine Flexible instance, by accessing the * GAE_INSTANCE environment variable. @@ -135,9 +174,9 @@ public static function onAppEngineFlexible() */ public static function onGce(callable $httpHandler = null) { - if (is_null($httpHandler)) { - $httpHandler = HttpHandlerFactory::build(); - } + $httpHandler = $httpHandler + ?: HttpHandlerFactory::build(HttpClientCache::getHttpClient()); + $checkUri = 'http://' . self::METADATA_IP; for ($i = 1; $i <= self::MAX_COMPUTE_PING_TRIES; $i++) { try { @@ -181,26 +220,19 @@ public static function onGce(callable $httpHandler = null) */ public function fetchAuthToken(callable $httpHandler = null) { - if (is_null($httpHandler)) { - $httpHandler = HttpHandlerFactory::build(); - } + $httpHandler = $httpHandler + ?: HttpHandlerFactory::build(HttpClientCache::getHttpClient()); + if (!$this->hasCheckedOnGce) { $this->isOnGce = self::onGce($httpHandler); + $this->hasCheckedOnGce = true; } if (!$this->isOnGce) { return array(); // return an empty array with no access token } - $resp = $httpHandler( - new Request( - 'GET', - self::getTokenUri(), - [self::FLAVOR_HEADER => 'Google'] - ) - ); - $body = (string)$resp->getBody(); - // Assume it's JSON; if it's not throw an exception - if (null === $json = json_decode($body, true)) { + $json = $this->getFromMetadata($httpHandler, self::getTokenUri()); + if (null === $json = json_decode($json, true)) { throw new \Exception('Invalid JSON response'); } @@ -233,4 +265,85 @@ public function getLastReceivedToken() return null; } + + /** + * Get the client name from GCE metadata. + * + * Subsequent calls will return a cached value. + * + * @param callable $httpHandler callback which delivers psr7 request + * @return string + */ + public function getClientName(callable $httpHandler = null) + { + if ($this->clientName) { + return $this->clientName; + } + + $httpHandler = $httpHandler + ?: HttpHandlerFactory::build(HttpClientCache::getHttpClient()); + + if (!$this->hasCheckedOnGce) { + $this->isOnGce = self::onGce($httpHandler); + $this->hasCheckedOnGce = true; + } + + if (!$this->isOnGce) { + return ''; + } + + $this->clientName = $this->getFromMetadata($httpHandler, self::getClientNameUri()); + + return $this->clientName; + } + + /** + * Sign a string using the default service account private key. + * + * This implementation uses IAM's signBlob API. + * + * @see https://cloud.google.com/iam/credentials/reference/rest/v1/projects.serviceAccounts/signBlob SignBlob + * + * @param string $stringToSign The string to sign. + * @param bool $forceOpenSsl [optional] Does not apply to this credentials + * type. + * @return string + */ + public function signBlob($stringToSign, $forceOpenSsl = false) + { + $httpHandler = HttpHandlerFactory::build(HttpClientCache::getHttpClient()); + + // Providing a signer is useful for testing, but it's undocumented + // because it's not something a user would generally need to do. + $signer = $this->iam ?: new Iam($httpHandler); + + $email = $this->getClientName($httpHandler); + + $previousToken = $this->getLastReceivedToken(); + $accessToken = $previousToken + ? $previousToken['access_token'] + : $this->fetchAuthToken($httpHandler)['access_token']; + + return $signer->signBlob($email, $accessToken, $stringToSign); + } + + /** + * Fetch the value of a GCE metadata server URI. + * + * @param callable $httpHandler An HTTP Handler to deliver PSR7 requests. + * @param string $uri The metadata URI. + * @return string + */ + private function getFromMetadata(callable $httpHandler, $uri) + { + $resp = $httpHandler( + new Request( + 'GET', + $uri, + [GCECredentials::FLAVOR_HEADER => 'Google'] + ) + ); + + return (string) $resp->getBody(); + } } diff --git a/src/Credentials/InsecureCredentials.php b/src/Credentials/InsecureCredentials.php index dae894fab..6461a9a7b 100644 --- a/src/Credentials/InsecureCredentials.php +++ b/src/Credentials/InsecureCredentials.php @@ -67,4 +67,15 @@ public function getLastReceivedToken() { return $this->token; } + + /** + * Get the client name. In this case, it returns an empty string. + * + * @param callable $httpHandler Not used in this implementation. + * @return string + */ + public function getClientName(callable $httpHandler = null) + { + return ''; + } } diff --git a/src/Credentials/ServiceAccountCredentials.php b/src/Credentials/ServiceAccountCredentials.php index 585255b99..7e801b759 100644 --- a/src/Credentials/ServiceAccountCredentials.php +++ b/src/Credentials/ServiceAccountCredentials.php @@ -19,6 +19,8 @@ use Google\Auth\CredentialsLoader; use Google\Auth\OAuth2; +use Google\Auth\ServiceAccountSignerTrait; +use Google\Auth\SignBlobInterface; /** * ServiceAccountCredentials supports authorization using a Google service @@ -53,8 +55,10 @@ * * $res = $client->get('myproject/taskqueues/myqueue'); */ -class ServiceAccountCredentials extends CredentialsLoader +class ServiceAccountCredentials extends CredentialsLoader implements SignBlobInterface { + use ServiceAccountSignerTrait; + /** * The OAuth2 instance used to conduct authorization. * @@ -178,4 +182,17 @@ public function setSub($sub) { $this->auth->setSub($sub); } + + /** + * Get the client name from the keyfile. + * + * In this case, it returns the keyfile's client_email key. + * + * @param callable $httpHandler Not used by this credentials type. + * @return string + */ + public function getClientName(callable $httpHandler = null) + { + return $this->auth->getIssuer(); + } } diff --git a/src/Credentials/ServiceAccountJwtAccessCredentials.php b/src/Credentials/ServiceAccountJwtAccessCredentials.php index b1ecf4fb4..cf9e06aaa 100644 --- a/src/Credentials/ServiceAccountJwtAccessCredentials.php +++ b/src/Credentials/ServiceAccountJwtAccessCredentials.php @@ -19,6 +19,8 @@ use Google\Auth\CredentialsLoader; use Google\Auth\OAuth2; +use Google\Auth\ServiceAccountSignerTrait; +use Google\Auth\SignBlobInterface; /** * Authenticates requests using Google's Service Account credentials via @@ -29,8 +31,10 @@ * console (via 'Generate new Json Key'). It is not part of any OAuth2 * flow, rather it creates a JWT and sends that as a credential. */ -class ServiceAccountJwtAccessCredentials extends CredentialsLoader +class ServiceAccountJwtAccessCredentials extends CredentialsLoader implements SignBlobInterface { + use ServiceAccountSignerTrait; + /** * The OAuth2 instance used to conduct authorization. * @@ -130,4 +134,17 @@ public function getLastReceivedToken() { return $this->auth->getLastReceivedToken(); } + + /** + * Get the client name from the keyfile. + * + * In this case, it returns the keyfile's client_email key. + * + * @param callable $httpHandler Not used by this credentials type. + * @return string + */ + public function getClientName(callable $httpHandler = null) + { + return $this->auth->getIssuer(); + } } diff --git a/src/Credentials/UserRefreshCredentials.php b/src/Credentials/UserRefreshCredentials.php index 74dcad8f8..b5854abc8 100644 --- a/src/Credentials/UserRefreshCredentials.php +++ b/src/Credentials/UserRefreshCredentials.php @@ -134,4 +134,15 @@ public function getLastReceivedToken() { return $this->auth->getLastReceivedToken(); } + + /** + * Get the client name. + * + * @param callable $httpHandler Not used by this credentials type. + * @return string + */ + public function getClientName(callable $httpHandler = null) + { + return $this->auth->getClientId(); + } } diff --git a/src/FetchAuthTokenCache.php b/src/FetchAuthTokenCache.php index 5b8e01b08..7824d1548 100644 --- a/src/FetchAuthTokenCache.php +++ b/src/FetchAuthTokenCache.php @@ -23,7 +23,7 @@ * A class to implement caching for any object implementing * FetchAuthTokenInterface */ -class FetchAuthTokenCache implements FetchAuthTokenInterface +class FetchAuthTokenCache implements FetchAuthTokenInterface, SignBlobInterface { use CacheTrait; @@ -105,4 +105,38 @@ public function getLastReceivedToken() { return $this->fetcher->getLastReceivedToken(); } + + /** + * Get the client name from the fetcher. + * + * @param callable $httpHandler An HTTP handler to deliver PSR7 requests. + * @return string + */ + public function getClientName(callable $httpHandler = null) + { + return $this->fetcher->getClientName($httpHandler); + } + + /** + * Sign a blob using the fetcher. + * + * @param string $stringToSign The string to sign. + * @param bool $forceOpenssl Require use of OpenSSL for local signing. Does + * not apply to signing done using external services. **Defaults to** + * `false`. + * @return string The resulting signature. + * @throws \RuntimeException If the fetcher does not implement + * `Google\Auth\SignBlobInterface`. + */ + public function signBlob($stringToSign, $forceOpenSsl = false) + { + if (!$this->fetcher instanceof SignBlobInterface) { + throw new \RuntimeException( + 'Credentials fetcher does not implement ' . + 'Google\Auth\SignBlobInterface' + ); + } + + return $this->fetcher->signBlob($stringToSign, $forceOpenSsl); + } } diff --git a/src/FetchAuthTokenInterface.php b/src/FetchAuthTokenInterface.php index e3d8d28b6..77ffea332 100644 --- a/src/FetchAuthTokenInterface.php +++ b/src/FetchAuthTokenInterface.php @@ -52,4 +52,13 @@ public function getCacheKey(); * } */ public function getLastReceivedToken(); + + /** + * Returns the current Client Name. + * + * @param callable $httpHandler callback which delivers psr7 request, if + * one is required to obtain a client name. + * @return string + */ + public function getClientName(callable $httpHandler = null); } diff --git a/src/HttpHandler/HttpClientCache.php b/src/HttpHandler/HttpClientCache.php new file mode 100644 index 000000000..f4a62b967 --- /dev/null +++ b/src/HttpHandler/HttpClientCache.php @@ -0,0 +1,54 @@ +httpHandler = $httpHandler + ?: HttpHandlerFactory::build(HttpClientCache::getHttpClient()); + } + + /** + * Sign a string using the IAM signBlob API. + * + * Note that signing using IAM requires your service account to have the + * `iam.serviceAccounts.signBlob` permission, part of the "Service Account + * Token Creator" IAM role. + * + * @param string $email The service account email. + * @param string $accessToken An access token from the service account. + * @param string $stringToSign The string to be signed. + * @param array $delegates [optional] A list of service account emails to + * add to the delegate chain. If omitted, the value of `$email` will + * be used. + * @return string The signed string, base64-encoded. + */ + public function signBlob($email, $accessToken, $stringToSign, array $delegates = []) + { + $httpHandler = $this->httpHandler; + $name = sprintf(self::SERVICE_ACCOUNT_NAME, $email); + $uri = self::IAM_API_ROOT . '/' . sprintf(self::SIGN_BLOB_PATH, $name); + + if ($delegates) { + foreach ($delegates as &$delegate) { + $delegate = sprintf(self::SERVICE_ACCOUNT_NAME, $delegate); + } + } else { + $delegates = [$name]; + } + + $body = [ + 'delegates' => $delegates, + 'payload' => base64_encode($stringToSign), + ]; + + $headers = [ + 'Authorization' => 'Bearer ' . $accessToken + ]; + + $request = new Psr7\Request( + 'POST', + $uri, + $headers, + Psr7\stream_for(json_encode($body)) + ); + + $res = $httpHandler($request); + $body = json_decode((string) $res->getBody(), true); + + return $body['signedBlob']; + } +} diff --git a/src/OAuth2.php b/src/OAuth2.php index be5622127..fa3f5939d 100644 --- a/src/OAuth2.php +++ b/src/OAuth2.php @@ -17,6 +17,7 @@ namespace Google\Auth; +use Google\Auth\HttpHandler\HttpClientCache; use Google\Auth\HttpHandler\HttpHandlerFactory; use GuzzleHttp\Psr7; use GuzzleHttp\Psr7\Request; @@ -495,7 +496,7 @@ public function generateCredentialsRequest() public function fetchAuthToken(callable $httpHandler = null) { if (is_null($httpHandler)) { - $httpHandler = HttpHandlerFactory::build(); + $httpHandler = HttpHandlerFactory::build(HttpClientCache::getHttpClient()); } $response = $httpHandler($this->generateCredentialsRequest()); @@ -1268,6 +1269,20 @@ public function getLastReceivedToken() return null; } + /** + * Get the client ID. + * + * Alias of {@see Google\Auth\OAuth2::getClientId()}. + * + * @param callable $httpHandler + * @return string + * @access private + */ + public function getClientName(callable $httpHandler = null) + { + return $this->getClientId(); + } + /** * @todo handle uri as array * diff --git a/src/ServiceAccountSignerTrait.php b/src/ServiceAccountSignerTrait.php new file mode 100644 index 000000000..37148deda --- /dev/null +++ b/src/ServiceAccountSignerTrait.php @@ -0,0 +1,57 @@ +auth->getSigningKey(); + + $signedString = ''; + if (class_exists('RSA') && !$forceOpenssl) { + $rsa = new RSA; + $rsa->loadKey($privateKey); + $rsa->setSignatureMode(RSA::SIGNATURE_PKCS1); + $rsa->setHash('sha256'); + + $signedString = $rsa->sign($stringToSign); + } elseif (extension_loaded('openssl')) { + openssl_sign($stringToSign, $signedString, $privateKey, 'sha256WithRSAEncryption'); + } else { + // @codeCoverageIgnoreStart + throw new \RuntimeException('OpenSSL is not installed.'); + } + // @codeCoverageIgnoreEnd + + return base64_encode($signedString); + } +} diff --git a/src/SignBlobInterface.php b/src/SignBlobInterface.php new file mode 100644 index 000000000..5f26442a6 --- /dev/null +++ b/src/SignBlobInterface.php @@ -0,0 +1,35 @@ +getMock('Psr\Cache\CacheItemPoolInterface'); + $cachePool = $this->prophesize('Psr\Cache\CacheItemPoolInterface'); $middleware = ApplicationDefaultCredentials::getMiddleware( 'a scope', $httpHandler, $cacheOptions, - $cachePool + $cachePool->reveal() ); } diff --git a/tests/Credentials/AppIdentityCredentialsTest.php b/tests/Credentials/AppIdentityCredentialsTest.php new file mode 100644 index 000000000..9c106212e --- /dev/null +++ b/tests/Credentials/AppIdentityCredentialsTest.php @@ -0,0 +1,229 @@ +assertFalse(AppIdentityCredentials::onAppEngine()); + } + + /** + * @runInSeparateProcess + */ + public function testOnAppEngineIsTrueWhenServerSoftwareIsGoogleAppEngine() + { + $this->imitateInAppEngine(); + $this->assertTrue(AppIdentityCredentials::onAppEngine()); + } + + /** + * @runInSeparateProcess + */ + public function testOnAppEngineIsTrueWhenAppEngineRuntimeIsPhp() + { + $this->imitateInAppEngine(); + $this->assertTrue(AppIdentityCredentials::onAppEngine()); + } + + /** + * @runInSeparateProcess + */ + public function testOnAppEngineIsTrueInDevelopmentServer() + { + $_SERVER['APPENGINE_RUNTIME'] = 'php'; + $this->assertTrue(AppIdentityCredentials::onAppEngine()); + } + + public function testGetCacheKeyShouldBeEmpty() + { + $g = new AppIdentityCredentials(); + $this->assertEmpty($g->getCacheKey()); + } + + public function testFetchAuthTokenShouldBeEmptyIfNotOnAppEngine() + { + $g = new AppIdentityCredentials(); + $this->assertEquals(array(), $g->fetchAuthToken()); + } + + /* @expectedException */ + public function testThrowsExceptionIfClassDoesntExist() + { + $_SERVER['SERVER_SOFTWARE'] = 'Google App Engine'; + $g = new AppIdentityCredentials(); + } + + /** + * @runInSeparateProcess + */ + public function testFetchAuthTokenReturnsExpectedToken() + { + $this->imitateInAppEngine(); + + $wantedToken = [ + 'access_token' => '1/abdef1234567890', + 'expires_in' => '57', + 'token_type' => 'Bearer', + ]; + + AppIdentityService::$accessToken = $wantedToken; + + $g = new AppIdentityCredentials(); + $this->assertEquals($wantedToken, $g->fetchAuthToken()); + } + + /** + * @runInSeparateProcess + */ + public function testScopeIsAlwaysArray() + { + $this->imitateInAppEngine(); + + $scope1 = ['scopeA', 'scopeB']; + $scope2 = 'scopeA scopeB'; + $scope3 = 'scopeA'; + + $g = new AppIdentityCredentials($scope1); + $g->fetchAuthToken(); + $this->assertEquals($scope1, AppIdentityService::$scope); + + $g = new AppIdentityCredentials($scope2); + $g->fetchAuthToken(); + $this->assertEquals(explode(' ', $scope2), AppIdentityService::$scope); + + $g = new AppIdentityCredentials($scope3); + $g->fetchAuthToken(); + $this->assertEquals([$scope3], AppIdentityService::$scope); + } + + /** + * @dataProvider appEngineRequired + */ + public function testMethodsFailWhenNotInAppEngine($method, $args = [], $expected = null) + { + if ($expected === null) { + if (method_exists($this, 'expectException')) { + $this->expectException('\Exception'); + } else { + $this->setExpectedException('\Exception'); + } + } + + $creds = new AppIdentityCredentials; + $res = call_user_func_array([$creds, $method], $args); + + if ($expected) { + $this->assertEquals($expected, $res); + } + } + + public function appEngineRequired() + { + return [ + ['fetchAuthToken', [], []], + ['signBlob', ['foo']], + ['getClientName'] + ]; + } + + /** + * @runInSeparateProcess + */ + public function testSignBlob() + { + $this->imitateInAppEngine(); + + $creds = new AppIdentityCredentials; + $string = 'test'; + $res = $creds->signBlob($string); + + $this->assertEquals(base64_encode('Signed: ' . $string), $res); + } + + /** + * @runInSeparateProcess + */ + public function testGetClientName() + { + $this->imitateInAppEngine(); + + $creds = new AppIdentityCredentials; + + $expected = 'foobar'; + AppIdentityService::$serviceAccountName = $expected; + + $this->assertEquals($expected, $creds->getClientName()); + + AppIdentityService::$serviceAccountName = 'notreturned'; + $this->assertEquals($expected, $creds->getClientName()); + } + + public function testGetLastReceivedTokenNullByDefault() + { + $creds = new AppIdentityCredentials; + $this->assertNull($creds->getLastReceivedToken()); + } + + /** + * @runInSeparateProcess + */ + public function testGetLastReceviedTokenCaches() + { + $this->imitateInAppEngine(); + + $creds = new AppIdentityCredentials; + + $wantedToken = [ + 'access_token' => '1/abdef1234567890', + 'expires_in' => '57', + 'expiration_time' => time() + 57, + 'token_type' => 'Bearer', + ]; + + AppIdentityService::$accessToken = $wantedToken; + + $creds->fetchAuthToken(); + + $this->assertEquals([ + 'access_token' => $wantedToken['access_token'], + 'expires_at' => $wantedToken['expiration_time'] + ], $creds->getLastReceivedToken()); + } + + private function imitateInAppEngine() + { + // include the mock AppIdentityService class + require_once __DIR__ . '/../mocks/AppIdentityService.php'; + $_SERVER['SERVER_SOFTWARE'] = 'Google App Engine'; + // $_SERVER['APPENGINE_RUNTIME'] = 'php'; + } +} diff --git a/tests/Credentials/AppIndentityCredentialsTest.php b/tests/Credentials/AppIndentityCredentialsTest.php deleted file mode 100644 index d43714aaa..000000000 --- a/tests/Credentials/AppIndentityCredentialsTest.php +++ /dev/null @@ -1,111 +0,0 @@ -assertFalse(AppIdentityCredentials::onAppEngine()); - } - - public function testIsTrueWhenServerSoftwareIsGoogleAppEngine() - { - $_SERVER['SERVER_SOFTWARE'] = 'Google App Engine'; - $this->assertTrue(AppIdentityCredentials::onAppEngine()); - } - - public function testIsTrueWhenAppEngineRuntimeIsPhp() - { - $_SERVER['APPENGINE_RUNTIME'] = 'php'; - $this->assertTrue(AppIdentityCredentials::onAppEngine()); - } -} - -class AppIdentityCredentialsGetCacheKeyTest extends TestCase -{ - public function testShouldBeEmpty() - { - $g = new AppIdentityCredentials(); - $this->assertEmpty($g->getCacheKey()); - } -} - -class AppIdentityCredentialsFetchAuthTokenTest extends TestCase -{ - public function testShouldBeEmptyIfNotOnAppEngine() - { - $g = new AppIdentityCredentials(); - $this->assertEquals(array(), $g->fetchAuthToken()); - } - - /* @expectedException */ - public function testThrowsExceptionIfClassDoesntExist() - { - $_SERVER['SERVER_SOFTWARE'] = 'Google App Engine'; - $g = new AppIdentityCredentials(); - } - - public function testReturnsExpectedToken() - { - // include the mock AppIdentityService class - require_once __DIR__ . '/../mocks/AppIdentityService.php'; - - $wantedToken = [ - 'access_token' => '1/abdef1234567890', - 'expires_in' => '57', - 'token_type' => 'Bearer', - ]; - - AppIdentityService::$accessToken = $wantedToken; - - $_SERVER['SERVER_SOFTWARE'] = 'Google App Engine'; - - $g = new AppIdentityCredentials(); - $this->assertEquals($wantedToken, $g->fetchAuthToken()); - } - - public function testScopeIsAlwaysArray() - { - // include the mock AppIdentityService class - require_once __DIR__ . '/../mocks/AppIdentityService.php'; - - $scope1 = ['scopeA', 'scopeB']; - $scope2 = 'scopeA scopeB'; - $scope3 = 'scopeA'; - - $_SERVER['SERVER_SOFTWARE'] = 'Google App Engine'; - - $g = new AppIdentityCredentials($scope1); - $g->fetchAuthToken(); - $this->assertEquals($scope1, AppIdentityService::$scope); - - $g = new AppIdentityCredentials($scope2); - $g->fetchAuthToken(); - $this->assertEquals(explode(' ', $scope2), AppIdentityService::$scope); - - $g = new AppIdentityCredentials($scope3); - $g->fetchAuthToken(); - $this->assertEquals([$scope3], AppIdentityService::$scope); - } -} diff --git a/tests/Credentials/GCECredentialsTest.php b/tests/Credentials/GCECredentialsTest.php index d9a8500b5..5530a8bcf 100644 --- a/tests/Credentials/GCECredentialsTest.php +++ b/tests/Credentials/GCECredentialsTest.php @@ -18,13 +18,19 @@ namespace Google\Auth\Tests; use Google\Auth\Credentials\GCECredentials; +use Google\Auth\HttpHandler\HttpClientCache; +use GuzzleHttp\ClientInterface; use GuzzleHttp\Psr7; -use GuzzleHttp\Psr7\Response; use PHPUnit\Framework\TestCase; +use Prophecy\Argument; -class GCECredentialsOnGCETest extends TestCase +/** + * @group credentials + * @group credentials-gce + */ +class GCECredentialsTest extends TestCase { - public function testIsFalseOnClientErrorStatus() + public function testOnGCEIsFalseOnClientErrorStatus() { // simulate retry attempts by returning multiple 400s $httpHandler = getHandler([ @@ -35,7 +41,7 @@ public function testIsFalseOnClientErrorStatus() $this->assertFalse(GCECredentials::onGCE($httpHandler)); } - public function testIsFalseOnServerErrorStatus() + public function testOnGCEIsFalseOnServerErrorStatus() { // simulate retry attempts by returning multiple 500s $httpHandler = getHandler([ @@ -46,7 +52,7 @@ public function testIsFalseOnServerErrorStatus() $this->assertFalse(GCECredentials::onGCE($httpHandler)); } - public function testIsFalseOnOkStatusWithoutExpectedHeader() + public function testOnGCEIsFalseOnOkStatusWithoutExpectedHeader() { $httpHandler = getHandler([ buildResponse(200), @@ -54,47 +60,33 @@ public function testIsFalseOnOkStatusWithoutExpectedHeader() $this->assertFalse(GCECredentials::onGCE($httpHandler)); } - public function testIsOkIfGoogleIsTheFlavor() + public function testOnGCEIsOkIfGoogleIsTheFlavor() { $httpHandler = getHandler([ buildResponse(200, [GCECredentials::FLAVOR_HEADER => 'Google']), ]); $this->assertTrue(GCECredentials::onGCE($httpHandler)); } -} -class GCECredentialsOnAppEngineFlexibleTest extends TestCase -{ - public function testIsFalseByDefault() + public function testOnAppEngineFlexIsFalseByDefault() { $this->assertFalse(GCECredentials::onAppEngineFlexible()); } - public function testIsTrueWhenGaeInstanceHasAefPrefix() + public function testOnAppEngineFlexIsTrueWhenGaeInstanceHasAefPrefix() { putenv('GAE_INSTANCE=aef-default-20180313t154438'); $this->assertTrue(GCECredentials::onAppEngineFlexible()); - } - - protected function tearDown() - { - // removes it if assigned putenv('GAE_INSTANCE'); } -} -class GCECredentialsGetCacheKeyTest extends TestCase -{ - public function testShouldNotBeEmpty() + public function testGetCacheKeyShouldNotBeEmpty() { $g = new GCECredentials(); $this->assertNotEmpty($g->getCacheKey()); } -} -class GCECredentialsFetchAuthTokenTest extends TestCase -{ - public function testShouldBeEmptyIfNotOnGCE() + public function testFetchAuthTokenShouldBeEmptyIfNotOnGCE() { // simulate retry attempts by returning multiple 500s $httpHandler = getHandler([ @@ -110,7 +102,7 @@ public function testShouldBeEmptyIfNotOnGCE() * @expectedException Exception * @expectedExceptionMessage Invalid JSON response */ - public function testShouldFailIfResponseIsNotJson() + public function testFetchAuthTokenShouldFailIfResponseIsNotJson() { $notJson = '{"foo": , this is cannot be passed as json" "bar"}'; $httpHandler = getHandler([ @@ -121,7 +113,7 @@ public function testShouldFailIfResponseIsNotJson() $g->fetchAuthToken($httpHandler); } - public function testShouldReturnTokenInfo() + public function testFetchAuthTokenShouldReturnTokenInfo() { $wantedTokens = [ 'access_token' => '1/abdef1234567890', @@ -137,4 +129,122 @@ public function testShouldReturnTokenInfo() $this->assertEquals($wantedTokens, $g->fetchAuthToken($httpHandler)); $this->assertEquals(time() + 57, $g->getLastReceivedToken()['expires_at']); } + + public function testGetLastReceivedTokenIsNullByDefault() + { + $creds = new GCECredentials; + $this->assertNull($creds->getLastReceivedToken()); + } + + public function testGetClientName() + { + $expected = 'foobar'; + + $httpHandler = getHandler([ + buildResponse(200, [GCECredentials::FLAVOR_HEADER => 'Google']), + buildResponse(200, [], Psr7\stream_for($expected)), + buildResponse(200, [], Psr7\stream_for('notexpected')) + ]); + + $creds = new GCECredentials; + $this->assertEquals($expected, $creds->getClientName($httpHandler)); + + // call again to test cached value + $this->assertEquals($expected, $creds->getClientName($httpHandler)); + } + + public function testGetClientNameShouldBeEmptyIfNotOnGCE() + { + // simulate retry attempts by returning multiple 500s + $httpHandler = getHandler([ + buildResponse(500), + buildResponse(500), + buildResponse(500) + ]); + + $creds = new GCECredentials; + $this->assertEquals('', $creds->getClientName($httpHandler)); + } + + public function testSignBlob() + { + $guzzleVersion = ClientInterface::VERSION; + if ($guzzleVersion[0] === '5') { + $this->markTestSkipped('Only compatible with guzzle 6+'); + } + + $expectedEmail = 'test@test.com'; + $expectedAccessToken = 'token'; + $stringToSign = 'inputString'; + $resultString = 'foobar'; + $token = [ + 'access_token' => $expectedAccessToken, + 'expires_in' => '57', + 'token_type' => 'Bearer', + ]; + + $iam = $this->prophesize('Google\Auth\Iam'); + $iam->signBlob($expectedEmail, $expectedAccessToken, $stringToSign) + ->shouldBeCalled() + ->willReturn($resultString); + + $client = $this->prophesize('GuzzleHttp\ClientInterface'); + $client->send(Argument::any(), Argument::any()) + ->willReturn( + buildResponse(200, [GCECredentials::FLAVOR_HEADER => 'Google']), + buildResponse(200, [], Psr7\stream_for($expectedEmail)), + buildResponse(200, [], Psr7\stream_for(json_encode($token))) + ); + + HttpClientCache::setHttpClient($client->reveal()); + + $creds = new GCECredentials($iam->reveal()); + $signature = $creds->signBlob($stringToSign); + } + + public function testSignBlobWithLastReceivedAccessToken() + { + $guzzleVersion = ClientInterface::VERSION; + if ($guzzleVersion[0] === '5') { + $this->markTestSkipped('Only compatible with guzzle 6+'); + } + + $expectedEmail = 'test@test.com'; + $expectedAccessToken = 'token'; + $notExpectedAccessToken = 'othertoken'; + $stringToSign = 'inputString'; + $resultString = 'foobar'; + $token1 = [ + 'access_token' => $expectedAccessToken, + 'expires_in' => '57', + 'token_type' => 'Bearer', + ]; + $token2 = [ + 'access_token' => $notExpectedAccessToken, + 'expires_in' => '57', + 'token_type' => 'Bearer', + ]; + + $iam = $this->prophesize('Google\Auth\Iam'); + $iam->signBlob($expectedEmail, $expectedAccessToken, $stringToSign) + ->shouldBeCalled() + ->willReturn($resultString); + + $client = $this->prophesize('GuzzleHttp\ClientInterface'); + $client->send(Argument::any(), Argument::any()) + ->willReturn( + buildResponse(200, [GCECredentials::FLAVOR_HEADER => 'Google']), + buildResponse(200, [], Psr7\stream_for(json_encode($token1))), + buildResponse(200, [], Psr7\stream_for($expectedEmail)), + buildResponse(200, [], Psr7\stream_for(json_encode($token2))) + ); + + HttpClientCache::setHttpClient($client->reveal()); + + $creds = new GCECredentials($iam->reveal()); + // cache a token + $creds->fetchAuthToken(); + + $signature = $creds->signBlob($stringToSign); + } } diff --git a/tests/Credentials/IAMCredentialsTest.php b/tests/Credentials/IAMCredentialsTest.php index 86cd57417..dfb5a654f 100644 --- a/tests/Credentials/IAMCredentialsTest.php +++ b/tests/Credentials/IAMCredentialsTest.php @@ -20,6 +20,10 @@ use Google\Auth\Credentials\IAMCredentials; use PHPUnit\Framework\TestCase; +/** + * @group credentials + * @group credentials-iam + */ class IAMConstructorTest extends TestCase { /** diff --git a/tests/Credentials/InsecureCredentialsTest.php b/tests/Credentials/InsecureCredentialsTest.php index ee0d6d113..8b886d492 100644 --- a/tests/Credentials/InsecureCredentialsTest.php +++ b/tests/Credentials/InsecureCredentialsTest.php @@ -20,6 +20,10 @@ use Google\Auth\Credentials\InsecureCredentials; use PHPUnit\Framework\TestCase; +/** + * @group credentials + * @group credentials-insecure + */ class InsecureCredentialsTest extends TestCase { public function testFetchAuthToken() @@ -39,4 +43,10 @@ public function testGetLastReceivedToken() $insecure = new InsecureCredentials(); $this->assertEquals(['access_token' => ''], $insecure->getLastReceivedToken()); } + + public function testGetClientName() + { + $creds = new InsecureCredentials; + $this->assertEquals('', $creds->getClientName()); + } } diff --git a/tests/Credentials/ServiceAccountCredentialsTest.php b/tests/Credentials/ServiceAccountCredentialsTest.php index 3a0beb47b..a3de6e78f 100644 --- a/tests/Credentials/ServiceAccountCredentialsTest.php +++ b/tests/Credentials/ServiceAccountCredentialsTest.php @@ -147,6 +147,24 @@ public function testInitalizeFromAFile() new ServiceAccountCredentials('scope/1', $keyFile) ); } + + /** + * @expectedException LogicException + */ + public function testFailsToInitializeFromInvalidJsonData() + { + $tmp = tmpfile(); + fwrite($tmp, '{'); + + $path = stream_get_meta_data($tmp)['uri']; + + try { + new ServiceAccountCredentials('scope/1', $path); + } catch (\Exception $e) { + fclose($tmp); + throw $e; + } + } } class SACFromEnvTest extends TestCase @@ -310,6 +328,16 @@ public function testUpdateMetadataFunc() } } +class SACGetClientNameTest extends TestCase +{ + public function testReturnsClientEmail() + { + $testJson = createTestJson(); + $sa = new ServiceAccountCredentials('scope/1', $testJson); + $this->assertEquals($testJson['client_email'], $sa->getClientName()); + } +} + class SACJwtAccessTest extends TestCase { private $privateKey; @@ -328,6 +356,41 @@ private function createTestJson() return $testJson; } + /** + * @expectedException InvalidArgumentException + */ + public function testFailsToInitalizeFromANonExistentFile() + { + $keyFile = __DIR__ . '/../fixtures' . '/does-not-exist-private.json'; + new ServiceAccountJwtAccessCredentials($keyFile); + } + + public function testInitalizeFromAFile() + { + $keyFile = __DIR__ . '/../fixtures' . '/private.json'; + $this->assertNotNull( + new ServiceAccountJwtAccessCredentials($keyFile) + ); + } + + /** + * @expectedException LogicException + */ + public function testFailsToInitializeFromInvalidJsonData() + { + $tmp = tmpfile(); + fwrite($tmp, '{'); + + $path = stream_get_meta_data($tmp)['uri']; + + try { + new ServiceAccountJwtAccessCredentials($path); + } catch (\Exception $e) { + fclose($tmp); + throw $e; + } + } + /** * @expectedException InvalidArgumentException */ @@ -519,3 +582,24 @@ public function testNoScopeAndNoAuthUri() ); } } + +class SACJWTGetCacheKeyTest extends TestCase +{ + public function testShouldBeTheSameAsOAuth2WithTheSameScope() + { + $testJson = createTestJson(); + $scope = ['scope/1', 'scope/2']; + $sa = new ServiceAccountJwtAccessCredentials($testJson); + $this->assertNull($sa->getCacheKey()); + } +} + +class SACJWTGetClientNameTest extends TestCase +{ + public function testReturnsClientEmail() + { + $testJson = createTestJson(); + $sa = new ServiceAccountJwtAccessCredentials($testJson); + $this->assertEquals($testJson['client_email'], $sa->getClientName()); + } +} diff --git a/tests/Credentials/UserRefreshCredentialsTest.php b/tests/Credentials/UserRefreshCredentialsTest.php index 44c427958..989b2c427 100644 --- a/tests/Credentials/UserRefreshCredentialsTest.php +++ b/tests/Credentials/UserRefreshCredentialsTest.php @@ -94,6 +94,20 @@ public function testShouldFailIfJsonDoesNotHaveRefreshToken() ); } + /** + * @expectedException InvalidArgumentException + */ + public function testShouldFailIfJsonDoesNotHaveClientId() + { + $testJson = createURCTestJson(); + unset($testJson['client_id']); + $scope = ['scope/1', 'scope/2']; + $sa = new UserRefreshCredentials( + $scope, + $testJson + ); + } + /** * @expectedException InvalidArgumentException */ @@ -111,6 +125,24 @@ public function testInitalizeFromAFile() ); } + /** + * @expectedException LogicException + */ + public function testFailsToInitializeFromInvalidJsonData() + { + $tmp = tmpfile(); + fwrite($tmp, '{'); + + $path = stream_get_meta_data($tmp)['uri']; + + try { + new UserRefreshCredentials('scope/1', $path); + } catch (\Exception $e) { + fclose($tmp); + throw $e; + } + } + /** * @expectedException PHPUnit_Framework_Error_Warning */ @@ -248,3 +280,13 @@ public function testCanFetchCredsOK() $this->assertEquals($testJson, $tokens); } } + +class URCGetClientNameTest extends TestCase +{ + public function testReturnsClientId() + { + $testJson = createURCTestJson(); + $sa = new UserRefreshCredentials('scope/1', $testJson); + $this->assertEquals($testJson['client_id'], $sa->getClientName()); + } +} diff --git a/tests/FetchAuthTokenCacheTest.php b/tests/FetchAuthTokenCacheTest.php index a027fa7bf..c4c4be772 100644 --- a/tests/FetchAuthTokenCacheTest.php +++ b/tests/FetchAuthTokenCacheTest.php @@ -151,4 +151,80 @@ public function testShouldSaveValueInCacheWithCacheOptions() $accessToken = $cachedFetcher->fetchAuthToken(); $this->assertEquals($accessToken, ['access_token' => $token]); } + + public function testGetLastReceivedToken() + { + $token = 'foo'; + + $mockFetcher = $this->prophesize('Google\Auth\FetchAuthTokenInterface'); + $mockFetcher->getLastReceivedToken() + ->shouldBeCalled() + ->willReturn([ + 'access_token' => $token + ]); + + $fetcher = new FetchAuthTokenCache( + $mockFetcher->reveal(), + [], + $this->mockCache + ); + + $this->assertEquals($token, $fetcher->getLastReceivedToken()['access_token']); + } + + public function testGetClientName() + { + $name = 'test@example.com'; + + $mockFetcher = $this->prophesize('Google\Auth\FetchAuthTokenInterface'); + $mockFetcher->getClientName(null) + ->shouldBeCalled() + ->willReturn($name); + + $fetcher = new FetchAuthTokenCache( + $mockFetcher->reveal(), + [], + $this->mockCache + ); + + $this->assertEquals($name, $fetcher->getClientName()); + } + + public function testSignBlob() + { + $stringToSign = 'foobar'; + $signature = 'helloworld'; + + $mockFetcher = $this->prophesize('Google\Auth\SignBlobInterface'); + $mockFetcher->willImplement('Google\Auth\FetchAuthTokenInterface'); + $mockFetcher->signBlob($stringToSign, true) + ->shouldBeCalled() + ->willReturn($signature); + + $fetcher = new FetchAuthTokenCache( + $mockFetcher->reveal(), + [], + $this->mockCache + ); + + $this->assertEquals($signature, $fetcher->signBlob($stringToSign, true)); + } + + /** + * @expectedException RuntimeException + */ + public function testSignBlobInvalidFetcher() + { + $mockFetcher = $this->prophesize('Google\Auth\FetchAuthTokenInterface'); + $mockFetcher->signBlob('test') + ->shouldNotbeCalled(); + + $fetcher = new FetchAuthTokenCache( + $mockFetcher->reveal(), + [], + $this->mockCache + ); + + $this->assertEquals($signature, $fetcher->signBlob('test')); + } } diff --git a/tests/HttpHandler/Guzzle5HttpHandlerTest.php b/tests/HttpHandler/Guzzle5HttpHandlerTest.php index 0f503ff72..04dfdf711 100644 --- a/tests/HttpHandler/Guzzle5HttpHandlerTest.php +++ b/tests/HttpHandler/Guzzle5HttpHandlerTest.php @@ -53,7 +53,7 @@ public function setUp() public function testSuccessfullySendsRealRequest() { - $request = new \GuzzleHttp\Psr7\Request('get', 'http://httpbin.org/get'); + $request = new \GuzzleHttp\Psr7\Request('get', 'https://httpbin.org/get'); $client = new \GuzzleHttp\Client(); $handler = new Guzzle5HttpHandler($client); $response = $handler($request); @@ -61,7 +61,7 @@ public function testSuccessfullySendsRealRequest() $this->assertEquals(200, $response->getStatusCode()); $json = json_decode((string) $response->getBody(), true); $this->assertArrayHasKey('url', $json); - $this->assertEquals($request->getUri(), $json['url']); + $this->assertEquals((string) $request->getUri(), $json['url']); } public function testSuccessfullySendsMockRequest() diff --git a/tests/HttpHandler/HttpHandlerFactoryTest.php b/tests/HttpHandler/HttpHandlerFactoryTest.php index 73126e604..2e5f2efb6 100644 --- a/tests/HttpHandler/HttpHandlerFactoryTest.php +++ b/tests/HttpHandler/HttpHandlerFactoryTest.php @@ -17,6 +17,7 @@ namespace Google\Auth\Tests; +use Google\Auth\HttpHandler\HttpClientCache; use Google\Auth\HttpHandler\HttpHandlerFactory; class HttpHandlerFactoryTest extends BaseTest @@ -25,6 +26,7 @@ public function testBuildsGuzzle5Handler() { $this->onlyGuzzle5(); + HttpClientCache::setHttpClient(null); $handler = HttpHandlerFactory::build(); $this->assertInstanceOf('Google\Auth\HttpHandler\Guzzle5HttpHandler', $handler); } @@ -33,6 +35,7 @@ public function testBuildsGuzzle6Handler() { $this->onlyGuzzle6(); + HttpClientCache::setHttpClient(null); $handler = HttpHandlerFactory::build(); $this->assertInstanceOf('Google\Auth\HttpHandler\Guzzle6HttpHandler', $handler); } diff --git a/tests/IamTest.php b/tests/IamTest.php new file mode 100644 index 000000000..286379cf6 --- /dev/null +++ b/tests/IamTest.php @@ -0,0 +1,100 @@ +assertEquals($expectedUri, (string) $request->getUri()); + $this->assertEquals('Bearer ' . $expectedAccessToken, $request->getHeaderLine('Authorization')); + $this->assertEquals([ + 'delegates' => $expectedDelegates, + 'payload' => base64_encode($expectedString) + ], json_decode((string) $request->getBody(), true)); + + return new Psr7\Response(200, [], Psr7\stream_for(json_encode([ + 'signedBlob' => $expectedResponse + ]))); + }; + + $iam = new Iam($httpHandler); + $res = $iam->signBlob( + $expectedEmail, + $expectedAccessToken, + $expectedString, + $delegates + ); + + $this->assertEquals($expectedResponse, $res); + } + + public function delegates() + { + return [ + [], + [['foo@bar.com']], + [ + [ + 'foo@bar.com', + 'bar@bar.com' + ] + ], + ]; + } +} diff --git a/tests/ServiceAccountSignerTraitTest.php b/tests/ServiceAccountSignerTraitTest.php new file mode 100644 index 000000000..2e14a7191 --- /dev/null +++ b/tests/ServiceAccountSignerTraitTest.php @@ -0,0 +1,74 @@ +signBlob(self::STRING_TO_SIGN, $useOpenSsl); + + $this->assertEquals(implode('', $this->signedString), $res); + } + + public function useOpenSsl() + { + return [[true], [false]]; + } +} + +class ServiceAccountSignerTraitImpl +{ + use ServiceAccountSignerTrait; + + private $auth; + + public function __construct($signingKey) + { + $this->auth = new AuthStub; + $this->auth->signingKey = $signingKey; + } +} + +class AuthStub +{ + public $signingKey; + + public function getSigningKey() + { + return $this->signingKey; + } +} diff --git a/tests/mocks/AppIdentityService.php b/tests/mocks/AppIdentityService.php index 324292a99..de1232b70 100644 --- a/tests/mocks/AppIdentityService.php +++ b/tests/mocks/AppIdentityService.php @@ -5,10 +5,11 @@ class AppIdentityService { public static $scope; - public static $accessToken = array( + public static $accessToken = [ 'access_token' => 'xyz', 'expiration_time' => '2147483646', - ); + ]; + public static $serviceAccountName; public static function getAccessToken($scope) { @@ -16,4 +17,16 @@ public static function getAccessToken($scope) return self::$accessToken; } + + public static function signForApp($stringToSign) + { + return [ + 'signature' => 'Signed: ' . $stringToSign + ]; + } + + public static function getServiceAccountName() + { + return self::$serviceAccountName; + } }