From 42191271667cdb49472784170e7ced6875fd85bc Mon Sep 17 00:00:00 2001 From: Brent Shaffer Date: Mon, 28 Dec 2015 10:00:05 -0800 Subject: [PATCH] add support for guzzle 6 --- .travis.yml | 26 +- composer.json | 6 +- src/Google/AccessToken/Revoke.php | 30 +- src/Google/AccessToken/Verify.php | 4 +- src/Google/AuthHandler/AuthHandlerFactory.php | 42 +++ src/Google/AuthHandler/Guzzle5AuthHandler.php | 86 ++++++ src/Google/AuthHandler/Guzzle6AuthHandler.php | 90 ++++++ src/Google/Client.php | 242 +++++++-------- src/Google/Http/Batch.php | 42 +-- src/Google/Http/MediaFileUpload.php | 72 ++--- src/Google/Http/Pool.php | 96 ------ src/Google/Http/REST.php | 57 ++-- src/Google/Service/Resource.php | 42 +-- tests/BaseTest.php | 50 +++- tests/Google/AccessToken/RevokeTest.php | 57 ++-- tests/Google/AccessToken/VerifyTest.php | 23 +- tests/Google/ClientTest.php | 283 ++++++++++++------ tests/Google/Http/MediaFileUploadTest.php | 200 +++++++++++++ tests/Google/Http/MediaFuleUploadTest.php | 86 ------ tests/Google/Http/PoolTest.php | 63 ---- tests/Google/Http/RESTTest.php | 41 +-- tests/Google/Service/ResourceTest.php | 74 ++--- tests/Google/Task/RunnerTest.php | 31 +- 23 files changed, 1037 insertions(+), 706 deletions(-) create mode 100644 src/Google/AuthHandler/AuthHandlerFactory.php create mode 100644 src/Google/AuthHandler/Guzzle5AuthHandler.php create mode 100644 src/Google/AuthHandler/Guzzle6AuthHandler.php delete mode 100644 src/Google/Http/Pool.php create mode 100644 tests/Google/Http/MediaFileUploadTest.php delete mode 100644 tests/Google/Http/MediaFuleUploadTest.php delete mode 100644 tests/Google/Http/PoolTest.php diff --git a/.travis.yml b/.travis.yml index ca334e740..bcd0f794d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,34 +7,42 @@ env: global: - MEMCACHE_HOST=127.0.0.1 - MEMCACHE_PORT=11211 + matrix: + - GUZZLE_VERSION=~5.2 + - GUZZLE_VERSION=~6.0 sudo: false cache: directories: - $HOME/.composer/cache + - vendor +php: + - 5.4 + - 5.5 + - 5.6 + - 7.0 + - hhvm + +# Guzzle 6.0 is not compatible with PHP 5.4 matrix: - fast_finish: true - include: + exclude: - php: 5.4 - - php: 5.5 - - php: 5.6 - env: PHPCS=true - - php: hhvm + env: GUZZLE_VERSION=~6.0 before_install: - composer self-update install: - composer install + - composer require guzzlehttp/guzzle:$GUZZLE_VERSION before_script: - - sh -c 'if [ "$TRAVIS_PHP_VERSION" != "hhvm" ]; then echo "extension=memcache.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi;' - - sh -c 'if [ "$TRAVIS_PHP_VERSION" != "hhvm" ]; then echo "extension=memcached.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi;' - phpenv version-name | grep ^5.[34] && echo "extension=apc.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini ; true - phpenv version-name | grep ^5.[34] && echo "apc.enable_cli=1" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini ; true script: - vendor/bin/phpunit - - if [[ "$PHPCS" == "true" ]]; then vendor/bin/phpcs src --standard=style/ruleset.xml -np; fi + - if [[ "$TRAVIS_PHP_VERSION" == "5.4" ]]; then vendor/bin/phpcs src --standard=style/ruleset.xml -np; fi + diff --git a/composer.json b/composer.json index b3a4721a8..304b21c26 100644 --- a/composer.json +++ b/composer.json @@ -7,11 +7,13 @@ "license": "Apache-2.0", "require": { "php": ">=5.4", - "google/auth": "0.4", + "google/auth": "0.5", "firebase/php-jwt": "~2.0|~3.0", "monolog/monolog": "^1.17", "phpseclib/phpseclib": "~2.0", - "guzzlehttp/guzzle": "5.2.*" + "guzzlehttp/guzzle": "~5.2|~6.0", + "guzzlehttp/psr7": "1.2.*", + "psr/http-message": "1.0.*" }, "require-dev": { "phpunit/phpunit": "~4", diff --git a/src/Google/AccessToken/Revoke.php b/src/Google/AccessToken/Revoke.php index 9ae7e553d..807a6d90f 100644 --- a/src/Google/AccessToken/Revoke.php +++ b/src/Google/AccessToken/Revoke.php @@ -16,8 +16,10 @@ * limitations under the License. */ -use GuzzleHttp\Client; +use Google\Auth\HttpHandler\HttpHandlerFactory; use GuzzleHttp\ClientInterface; +use GuzzleHttp\Psr7; +use GuzzleHttp\Psr7\Request; /** * Wrapper around Google Access Tokens which provides convenience functions @@ -36,10 +38,6 @@ class Google_AccessToken_Revoke */ public function __construct(ClientInterface $http = null) { - if (is_null($http)) { - $http = new Client(); - } - $this->http = $http; } @@ -53,17 +51,25 @@ public function __construct(ClientInterface $http = null) public function revokeToken(array $token) { if (isset($token['refresh_token'])) { - $tokenString = $token['refresh_token']; + $tokenString = $token['refresh_token']; } else { - $tokenString = $token['access_token']; + $tokenString = $token['access_token']; } - $request = $this->http->createRequest('POST', Google_Client::OAUTH2_REVOKE_URI); - $request->addHeader('Cache-Control', 'no-store'); - $request->addHeader('Content-Type', 'application/x-www-form-urlencoded'); - $request->getBody()->replaceFields(array('token' => $tokenString)); + $body = Psr7\stream_for(http_build_query(array('token' => $tokenString))); + $request = new Request( + 'POST', + Google_Client::OAUTH2_REVOKE_URI, + [ + 'Cache-Control' => 'no-store', + 'Content-Type' => 'application/x-www-form-urlencoded', + ], + $body + ); + + $httpHandler = HttpHandlerFactory::build($this->http); - $response = $this->http->send($request); + $response = $httpHandler($request); if ($response->getStatusCode() == 200) { return true; } diff --git a/src/Google/AccessToken/Verify.php b/src/Google/AccessToken/Verify.php index 3086d770f..49271fd99 100644 --- a/src/Google/AccessToken/Verify.php +++ b/src/Google/AccessToken/Verify.php @@ -73,7 +73,7 @@ public function verifyIdToken($idToken, $audience = null) } // Check signature - $certs = $this->getFederatedSignonCerts(); + $certs = $this->getFederatedSignOnCerts(); foreach ($certs as $cert) { $modulus = new BigInteger($this->jwt->urlsafeB64Decode($cert['n']), 256); $exponent = new BigInteger($this->jwt->urlsafeB64Decode($cert['e']), 256); @@ -152,7 +152,7 @@ private function retrieveCertsFromLocation($url) $response = $this->http->get($url); if ($response->getStatusCode() == 200) { - return $response->json(); + return json_decode((string) $response->getBody(), true); } throw new Google_Exception( sprintf( diff --git a/src/Google/AuthHandler/AuthHandlerFactory.php b/src/Google/AuthHandler/AuthHandlerFactory.php new file mode 100644 index 000000000..4f2155a63 --- /dev/null +++ b/src/Google/AuthHandler/AuthHandlerFactory.php @@ -0,0 +1,42 @@ +cache = $cache; + } + + public function attachCredentials(ClientInterface $http, CredentialsLoader $credentials) + { + // if we end up needing to make an HTTP request to retrieve credentials, we + // can use our existing one, but we need to throw exceptions so the error + // bubbles up. + $authHttp = $this->createAuthHttp($http); + $authHttpHandler = HttpHandlerFactory::build($authHttp); + $subscriber = new AuthTokenSubscriber( + $credentials, + [], + $this->cache, + $authHttpHandler + ); + + $http->setDefaultOption('auth', 'google_auth'); + $http->getEmitter()->attach($subscriber); + + return $http; + } + + public function attachToken(ClientInterface $http, array $token, array $scopes) + { + $tokenFunc = function ($scopes) use ($token) { + return $token['access_token']; + }; + + $subscriber = new ScopedAccessTokenSubscriber( + $tokenFunc, + $scopes, + [], + $this->cache + ); + + $http->setDefaultOption('auth', 'scoped'); + $http->getEmitter()->attach($subscriber); + + return $http; + } + + public function attachKey(ClientInterface $http, $key) + { + $subscriber = new SimpleSubscriber(['key' => $key]); + + $http->setDefaultOption('auth', 'simple'); + $http->getEmitter()->attach($subscriber); + + return $http; + } + + private function createAuthHttp(ClientInterface $http) + { + return new Client( + [ + 'base_url' => $http->getBaseUrl(), + 'defaults' => [ + 'exceptions' => true, + 'verify' => $http->getDefaultOption('verify'), + 'proxy' => $http->getDefaultOption('proxy'), + ] + ] + ); + } +} diff --git a/src/Google/AuthHandler/Guzzle6AuthHandler.php b/src/Google/AuthHandler/Guzzle6AuthHandler.php new file mode 100644 index 000000000..c0a24cd8f --- /dev/null +++ b/src/Google/AuthHandler/Guzzle6AuthHandler.php @@ -0,0 +1,90 @@ +cache = $cache; + } + + public function attachCredentials(ClientInterface $http, CredentialsLoader $credentials) + { + // if we end up needing to make an HTTP request to retrieve credentials, we + // can use our existing one, but we need to throw exceptions so the error + // bubbles up. + $authHttp = $this->createAuthHttp($http); + $authHttpHandler = HttpHandlerFactory::build($authHttp); + $middleware = new AuthTokenMiddleware( + $credentials, + [], + $this->cache, + $authHttpHandler + ); + + $config = $http->getConfig(); + $config['handler']->push($middleware); + $config['auth'] = 'google_auth'; + $http = new Client($config); + + return $http; + } + + public function attachToken(ClientInterface $http, array $token, array $scopes) + { + $tokenFunc = function ($scopes) use ($token) { + return $token['access_token']; + }; + + $middleware = new ScopedAccessTokenMiddleware( + $tokenFunc, + $scopes, + [], + $this->cache + ); + + $config = $http->getConfig(); + $config['handler']->push($middleware); + $config['auth'] = 'scoped'; + $http = new Client($config); + + return $http; + } + + public function attachKey(ClientInterface $http, $key) + { + $middleware = new SimpleMiddleware(['key' => $key]); + + $config = $http->getConfig(); + $config['handler']->push($middleware); + $config['auth'] = 'simple'; + $http = new Client($config); + + return $http; + } + + private function createAuthHttp(ClientInterface $http) + { + return new Client( + [ + 'base_uri' => $http->getConfig('base_uri'), + 'exceptions' => true, + 'verify' => $http->getConfig('verify'), + 'proxy' => $http->getConfig('proxy'), + ] + ); + } +} diff --git a/src/Google/Client.php b/src/Google/Client.php index 0d5a2533f..ba1768d7b 100644 --- a/src/Google/Client.php +++ b/src/Google/Client.php @@ -16,19 +16,17 @@ */ use Google\Auth\ApplicationDefaultCredentials; -use Google\Auth\AuthTokenFetcher; use Google\Auth\CacheInterface; use Google\Auth\CredentialsLoader; +use Google\Auth\HttpHandler\HttpHandlerFactory; use Google\Auth\OAuth2; -use Google\Auth\ScopedAccessToken; -use Google\Auth\ServiceAccountCredentials; -use Google\Auth\Simple; -use Google\Auth\UserRefreshCredentials; +use Google\Auth\Credentials\ServiceAccountCredentials; +use Google\Auth\Credentials\UserRefreshCredentials; use GuzzleHttp\Client; use GuzzleHttp\ClientInterface; -use GuzzleHttp\Collection; use GuzzleHttp\Ring\Client\StreamHandler; -use GuzzleHttp\Stream\Stream; +use GuzzleHttp\Psr7; +use Psr\Http\Message\RequestInterface; use Psr\Log\LoggerInterface; use Monolog\Logger; use Monolog\Handler\StreamHandler as MonologStreamHandler; @@ -92,8 +90,7 @@ class Google_Client */ public function __construct($config = array()) { - $this->config = Collection::fromConfig( - $config, + $this->config = array_merge( [ 'application_name' => '', @@ -115,6 +112,9 @@ public function __construct($config = array()) // fetch the ApplicationDefaultCredentials, if applicable // @see https://developers.google.com/identity/protocols/application-default-credentials 'use_application_default_credentials' => false, + 'signing_key' => null, + 'signing_algorithm' => null, + 'subject' => null, // Other OAuth2 parameters. 'hd' => '', @@ -129,7 +129,8 @@ public function __construct($config = array()) // Task Runner retry configuration // @see Google_Task_Runner 'retry' => array(), - ] + ], + $config ); } @@ -172,7 +173,8 @@ public function fetchAccessTokenWithAuthCode($code) $auth->setCode($code); $auth->setRedirectUri($this->getRedirectUri()); - $creds = $auth->fetchAuthToken($this->getHttpClient()); + $httpHandler = HttpHandlerFactory::build($this->getHttpClient()); + $creds = $auth->fetchAuthToken($httpHandler); if ($creds && isset($creds['access_token'])) { $creds['created'] = time(); $this->setAccessToken($creds); @@ -216,7 +218,8 @@ public function fetchAccessTokenWithAssertion(ClientInterface $authHttp = null) $credentials = $this->createApplicationDefaultCredentials(); - $accessToken = $credentials->fetchAuthToken($authHttp); + $httpHandler = HttpHandlerFactory::build($authHttp); + $accessToken = $credentials->fetchAuthToken($httpHandler); if ($accessToken && isset($accessToken['access_token'])) { $this->setAccessToken($accessToken); } @@ -255,7 +258,8 @@ public function fetchAccessTokenWithRefreshToken($refreshToken = null) $auth = $this->getOAuth2Service(); $auth->setRefreshToken($refreshToken); - $creds = $auth->fetchAuthToken($this->getHttpClient()); + $httpHandler = HttpHandlerFactory::build($this->getHttpClient()); + $creds = $auth->fetchAuthToken($httpHandler); if ($creds && isset($creds['access_token'])) { $creds['created'] = time(); $this->setAccessToken($creds); @@ -281,33 +285,33 @@ public function createAuthUrl($scope = null) } // only accept one of prompt or approval_prompt - $approvalPrompt = $this->config->get('prompt') + $approvalPrompt = $this->config['prompt'] ? null - : $this->config->get('approval_prompt'); + : $this->config['approval_prompt']; // include_granted_scopes should be string "true", string "false", or null - $includeGrantedScopes = $this->config->get('include_granted_scopes') === null + $includeGrantedScopes = $this->config['include_granted_scopes'] === null ? null - : var_export($this->config->get('include_granted_scopes'), true); + : var_export($this->config['include_granted_scopes'], true); $params = array_filter( [ - 'access_type' => $this->config->get('access_type'), + 'access_type' => $this->config['access_type'], 'approval_prompt' => $approvalPrompt, - 'hd' => $this->config->get('hd'), + 'hd' => $this->config['hd'], 'include_granted_scopes' => $includeGrantedScopes, - 'login_hint' => $this->config->get('login_hint'), - 'openid.realm' => $this->config->get('openid.realm'), - 'prompt' => $this->config->get('prompt'), + 'login_hint' => $this->config['login_hint'], + 'openid.realm' => $this->config['openid.realm'], + 'prompt' => $this->config['prompt'], 'response_type' => 'code', 'scope' => $scope, - 'state' => $this->config->get('state'), + 'state' => $this->config['state'], ] ); // If the list of scopes contains plus.login, add request_visible_actions // to auth URL. - $rva = $this->config->get('request_visible_actions'); + $rva = $this->config['request_visible_actions']; if (strlen($rva) > 0 && false !== strpos($scope, 'plus.login')) { $params['request_visible_actions'] = $rva; } @@ -325,15 +329,14 @@ public function createAuthUrl($scope = null) * @param GuzzleHttp\ClientInterface $authHttp an http client for authentication. * @return void */ - public function authorize(ClientInterface $http, ClientInterface $authHttp = null) + public function authorize(ClientInterface $http = null, ClientInterface $authHttp = null) { - $subscriber = null; - $authIdentifier = null; - - // if we end up needing to make an HTTP request to retrieve credentials, we - // can use our existing one, but we need to throw exceptions so the error - // bubbles up. - $authHttp = $authHttp ?: $this->createDefaultAuthHttpClient($http); + $credentials = null; + $token = null; + $scopes = null; + if (is_null($http)) { + $http = $this->getHttpClient(); + } // These conditionals represent the decision tree for authentication // 1. Check for Application Default Credentials @@ -341,18 +344,7 @@ public function authorize(ClientInterface $http, ClientInterface $authHttp = nul // 3a. Check for an Access Token // 3b. If access token exists but is expired, try to refresh it if ($this->isUsingApplicationDefaultCredentials()) { - $credentials = $this->createApplicationDefaultCredentials($authHttp); - $subscriber = new AuthTokenFetcher( - $credentials, - [], - $this->cache, - $authHttp - ); - $authIdentifier = 'google_auth'; - } elseif ($key = $this->config->get('developer_key')) { - // if a developer key is set, authorize using that - $subscriber = new Simple(['key' => $key]); - $authIdentifier = 'simple'; + $credentials = $this->createApplicationDefaultCredentials(); } elseif ($token = $this->getAccessToken()) { $scopes = $this->prepareScopes(); // add refresh subscriber to request a new token @@ -361,32 +353,17 @@ public function authorize(ClientInterface $http, ClientInterface $authHttp = nul $scopes, $token['refresh_token'] ); - $subscriber = new AuthTokenFetcher( - $credentials, - [], - $this->getCache(), - $authHttp - ); - $authIdentifier = 'google_auth'; - } else { - $subscriber = new ScopedAccessToken( - function ($scopes) use ($token) { - return $token['access_token']; - }, - (array) $scopes, - [] - ); - $authIdentifier = 'scoped'; } } - if ($subscriber) { - $http->setDefaultOption('auth', $authIdentifier); - $http->getEmitter()->attach($subscriber); - $this->getLogger()->log( - 'info', - sprintf('Added listener for auth type "%s"', $authIdentifier) - ); + $authHandler = $this->getAuthHandler(); + + if ($credentials) { + $http = $authHandler->attachCredentials($http, $credentials); + } elseif ($token) { + $http = $authHandler->attachToken($http, $token, (array) $scopes); + } elseif ($key = $this->config['developer_key']) { + $http = $authHandler->attachKey($http, $key); } return $http; @@ -401,7 +378,7 @@ function ($scopes) use ($token) { */ public function useApplicationDefaultCredentials($useAppCreds = true) { - $this->config->set('use_application_default_credentials', $useAppCreds); + $this->config['use_application_default_credentials'] = $useAppCreds; } /** @@ -412,7 +389,7 @@ public function useApplicationDefaultCredentials($useAppCreds = true) */ public function isUsingApplicationDefaultCredentials() { - return $this->config->get('use_application_default_credentials'); + return $this->config['use_application_default_credentials']; } /** @@ -507,12 +484,12 @@ public function setAuth($auth) */ public function setClientId($clientId) { - $this->config->set('client_id', $clientId); + $this->config['client_id'] = $clientId; } public function getClientId() { - return $this->config->get('client_id'); + return $this->config['client_id']; } /** @@ -521,12 +498,12 @@ public function getClientId() */ public function setClientSecret($clientSecret) { - $this->config->set('client_secret', $clientSecret); + $this->config['client_secret'] = $clientSecret; } public function getClientSecret() { - return $this->config->get('client_secret'); + return $this->config['client_secret']; } /** @@ -535,12 +512,12 @@ public function getClientSecret() */ public function setRedirectUri($redirectUri) { - $this->config->set('redirect_uri', $redirectUri); + $this->config['redirect_uri'] = $redirectUri; } public function getRedirectUri() { - return $this->config->get('redirect_uri'); + return $this->config['redirect_uri']; } /** @@ -550,7 +527,7 @@ public function getRedirectUri() */ public function setState($state) { - $this->config->set('state', $state); + $this->config['state'] = $state; } /** @@ -560,7 +537,7 @@ public function setState($state) */ public function setAccessType($accessType) { - $this->config->set('access_type', $accessType); + $this->config['access_type'] = $accessType; } /** @@ -570,7 +547,7 @@ public function setAccessType($accessType) */ public function setApprovalPrompt($approvalPrompt) { - $this->config->set('approval_prompt', $approvalPrompt); + $this->config['approval_prompt'] = $approvalPrompt; } /** @@ -579,7 +556,7 @@ public function setApprovalPrompt($approvalPrompt) */ public function setLoginHint($loginHint) { - $this->config->set('login_hint', $loginHint); + $this->config['login_hint'] = $loginHint; } /** @@ -588,7 +565,7 @@ public function setLoginHint($loginHint) */ public function setApplicationName($applicationName) { - $this->config->set('application_name', $applicationName); + $this->config['application_name'] = $applicationName; } /** @@ -604,7 +581,7 @@ public function setRequestVisibleActions($requestVisibleActions) if (is_array($requestVisibleActions)) { $requestVisibleActions = join(" ", $requestVisibleActions); } - $this->config->set('request_visible_actions', $requestVisibleActions); + $this->config['request_visible_actions'] = $requestVisibleActions; } /** @@ -614,7 +591,7 @@ public function setRequestVisibleActions($requestVisibleActions) */ public function setDeveloperKey($developerKey) { - $this->config->set('developer_key', $developerKey); + $this->config['developer_key'] = $developerKey; } /** @@ -625,7 +602,7 @@ public function setDeveloperKey($developerKey) */ public function setHostedDomain($hd) { - $this->config->set('hd', $hd); + $this->config['hd'] = $hd; } /** * Set the prompt hint. Valid values are none, consent and select_account. @@ -635,7 +612,7 @@ public function setHostedDomain($hd) */ public function setPrompt($prompt) { - $this->config->set('prompt', $prompt); + $this->config['prompt'] = $prompt; } /** * openid.realm is a parameter from the OpenID 2.0 protocol, not from OAuth @@ -645,7 +622,7 @@ public function setPrompt($prompt) */ public function setOpenidRealm($realm) { - $this->config->set('openid.realm', $realm); + $this->config['openid.realm'] = $realm; } /** * If this is provided with the value true, and the authorization request is @@ -655,7 +632,7 @@ public function setOpenidRealm($realm) */ public function setIncludeGrantedScopes($include) { - $this->config->set('include_granted_scopes', $include); + $this->config['include_granted_scopes'] = $include; } /** @@ -761,28 +738,24 @@ public function prepareScopes() /** * Helper method to execute deferred HTTP requests. * - * @param $request GuzzleHttp\Message\RequestInterface|Google_Http_Batch + * @param $request Psr\Http\Message\RequestInterface|Google_Http_Batch * @throws Google_Exception - * @return object of the type of the expected class or array. + * @return object of the type of the expected class or Psr\Http\Message\ResponseInterface. */ - public function execute($request, $expectedClass = null) + public function execute(RequestInterface $request, $expectedClass = null) { - $request->setHeader( + $request = $request->withHeader( 'User-Agent', - $this->config->get('application_name') + $this->config['application_name'] . " " . self::USER_AGENT_SUFFIX . $this->getLibraryVersion() ); - $http = $this->getHttpClient(); + // call the authorize method + // this is where most of the grunt work is done + $http = $this->authorize(); - $result = Google_Http_REST::execute($http, $request, $this->config['retry']); - $expectedClass = $expectedClass ?: $request->getHeader('X-Php-Expected-Class'); - if ($expectedClass) { - $result = new $expectedClass($result); - } - - return $result; + return Google_Http_REST::execute($http, $request, $expectedClass, $this->config['retry']); } /** @@ -810,12 +783,12 @@ public function isAppEngine() public function setConfig($name, $value) { - $this->config->set($name, $value); + $this->config[$name] = $value; } public function getConfig($name, $default = null) { - return $this->config->get($name) ?: $default; + return isset($this->config[$name]) ? $this->config[$name] : $default; } /** @@ -859,9 +832,9 @@ public function setAuthConfig($config) // set the information from the config $this->setClientId($config['client_id']); - $this->config->set('client_email', $config['client_email']); - $this->config->set('signing_key', $config['private_key']); - $this->config->set('signing_algorithm', 'HS256'); + $this->config['client_email'] = $config['client_email']; + $this->config['signing_key'] = $config['private_key']; + $this->config['signing_algorithm'] = 'HS256'; } elseif (isset($config[$key])) { // old-style $this->setClientId($config[$key]['client_id']); @@ -886,7 +859,7 @@ public function setAuthConfig($config) */ public function setSubject($subject) { - $this->config->set('subject', $subject); + $this->config['subject'] = $subject; } /** @@ -933,9 +906,9 @@ protected function createOAuth2Service() 'authorizationUri' => self::OAUTH2_AUTH_URL, 'tokenCredentialUri' => self::OAUTH2_TOKEN_URI, 'redirectUri' => $this->getRedirectUri(), - 'issuer' => $this->config->get('client_id'), - 'signingKey' => $this->config->get('signing_key'), - 'signingAlgorithm' => $this->config->get('signing_algorithm'), + 'issuer' => $this->config['client_id'], + 'signingKey' => $this->config['signing_key'], + 'signingAlgorithm' => $this->config['signing_algorithm'], ] ); @@ -1027,48 +1000,42 @@ public function getHttpClient() protected function createDefaultHttpClient() { - $options = [ - 'base_url' => $this->config->get('base_path'), - 'defaults' => ['exceptions' => false], - ]; - - // set StreamHandler on AppEngine by default - if ($this->isAppEngine()) { - $options['handler'] = new StreamHandler(); + $options = ['exceptions' => false]; + + $version = ClientInterface::VERSION; + if ('5' === $version[0]) { + $options = [ + 'base_url' => $this->config['base_path'], + 'defaults' => $options, + ]; + if ($this->isAppEngine()) { + // set StreamHandler on AppEngine by default + $options['handler'] = new StreamHandler(); + $options['defaults']['verify'] = '/etc/ca-certificates.crt'; + } + } else { + // guzzle 6 + $options['base_uri'] = $this->config['base_path']; } return new Client($options); } - protected function createDefaultAuthHttpClient($http = null) - { - $options = [ - 'base_url' => $this->config->get('base_path'), - 'defaults' => [ - 'exceptions' => true, - 'verify' => $http ? $http->getDefaultOption('verify') : true, - 'proxy' => $http ? $http->getDefaultOption('proxy') : null, - ] - ]; - - return new Client($options); - } - private function createApplicationDefaultCredentials() { $scopes = $this->prepareScopes(); - $sub = $this->config->get('subject'); - $signingKey = $this->config->get('signing_key'); + $sub = $this->config['subject']; + $signingKey = $this->config['signing_key']; // create credentials using values supplied in setAuthConfig if ($signingKey) { $serviceAccountCredentials = array( - 'client_id' => $this->config->get('client_id'), - 'client_email' => $this->config->get('client_email'), + 'client_id' => $this->config['client_id'], + 'client_email' => $this->config['client_email'], 'private_key' => $signingKey, 'type' => 'service_account', ); - $keyStream = Stream::factory(json_encode($serviceAccountCredentials)); + $keyStream = Psr7\stream_for(json_encode($serviceAccountCredentials)); $credentials = CredentialsLoader::makeCredentials($scopes, $keyStream); } else { $credentials = ApplicationDefaultCredentials::getCredentials($scopes); @@ -1087,6 +1054,11 @@ private function createApplicationDefaultCredentials() return $credentials; } + protected function getAuthHandler() + { + return Google_AuthHandler_AuthHandlerFactory::build($this->getCache()); + } + private function createUserRefreshCredentials($scope, $refreshToken) { $creds = array_filter( diff --git a/src/Google/Http/Batch.php b/src/Google/Http/Batch.php index e53aedf8c..b08eb6535 100644 --- a/src/Google/Http/Batch.php +++ b/src/Google/Http/Batch.php @@ -15,11 +15,11 @@ * limitations under the License. */ -use GuzzleHttp\Message\Request; -use GuzzleHttp\Message\Response; -use GuzzleHttp\Message\RequestInterface; -use GuzzleHttp\Message\ResponseInterface; -use GuzzleHttp\Stream\Stream; +use GuzzleHttp\Psr7; +use GuzzleHttp\Psr7\Request; +use GuzzleHttp\Psr7\Response; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; /** * Class to handle batched requests to the Google API service. @@ -79,22 +79,27 @@ public function execute() $firstLine = sprintf( '%s %s HTTP/%s', $request->getMethod(), - $request->getResource(), + $request->getRequestTarget(), $request->getProtocolVersion() ); $content = (string) $request->getBody(); + $headers = ''; + foreach ($request->getHeaders() as $name => $values) { + $headers .= sprintf("%s:%s\r\n", $name, implode(', ', $values)); + } + $body .= sprintf( $batchHttpTemplate, $this->boundary, $key, $firstLine, - Request::getHeadersAsString($request), + $headers, $content ? "\n".$content : '' ); - $classes['response-' . $key] = $request->getHeader('X-Php-Expected-Class'); + $classes['response-' . $key] = $request->getHeaderLine('X-Php-Expected-Class'); } $body .= "--{$this->boundary}--"; @@ -104,23 +109,22 @@ public function execute() 'Content-Type' => sprintf('multipart/mixed; boundary=%s', $this->boundary), 'Content-Length' => strlen($body), ); - $request = $this->client->getHttpClient()->createRequest( + + $request = new Request( 'POST', $url, - [ - 'headers' => $headers, - 'body' => Stream::factory($body), - ] + $headers, + $body ); - $response = $this->client->getHttpClient()->send($request); + $response = $this->client->execute($request); return $this->parseResponse($response, $classes); } public function parseResponse(ResponseInterface $response, $classes = array()) { - $contentType = $response->getHeader('content-type'); + $contentType = $response->getHeaderLine('content-type'); $contentType = explode(';', $contentType); $boundary = false; foreach ($contentType as $part) { @@ -135,6 +139,7 @@ public function parseResponse(ResponseInterface $response, $classes = array()) $body = str_replace("--$boundary--", "--$boundary", $body); $parts = explode("--$boundary", $body); $responses = array(); + $requests = array_values($this->requests); foreach ($parts as $i => $part) { $part = trim($part); @@ -150,7 +155,7 @@ public function parseResponse(ResponseInterface $response, $classes = array()) $response = new Response( $status, $partHeaders, - Stream::factory($partBody) + Psr7\stream_for($partBody) ); // Need content id. @@ -161,10 +166,7 @@ public function parseResponse(ResponseInterface $response, $classes = array()) } try { - $response = Google_Http_REST::decodeHttpResponse($response); - if (isset($classes[$key])) { - $response = new $classes[$key]($response); - } + $response = Google_Http_REST::decodeHttpResponse($response, $requests[$i-1]); } catch (Google_Service_Exception $e) { // Store the exception as the response, so successful responses // can be processed. diff --git a/src/Google/Http/MediaFileUpload.php b/src/Google/Http/MediaFileUpload.php index 74b837f89..72a6eeaf8 100644 --- a/src/Google/Http/MediaFileUpload.php +++ b/src/Google/Http/MediaFileUpload.php @@ -15,9 +15,10 @@ * limitations under the License. */ -use GuzzleHttp\Message\Request; -use GuzzleHttp\Stream\Stream; -use GuzzleHttp\Url; +use GuzzleHttp\Psr7; +use GuzzleHttp\Psr7\Request; +use GuzzleHttp\Psr7\Uri; +use Psr\Http\Message\RequestInterface; /** * Manage large file uploads, which may be media but can be any type @@ -53,7 +54,7 @@ class Google_Http_MediaFileUpload /** @var Google_Client */ private $client; - /** @var Google_Http_Request */ + /** @var Psr\Http\Message\RequestInterface */ private $request; /** @var string */ @@ -74,7 +75,7 @@ class Google_Http_MediaFileUpload */ public function __construct( Google_Client $client, - $request, + RequestInterface $request, $mimeType, $data, $resumable = false, @@ -133,7 +134,7 @@ public function nextChunk($chunk = false) 'PUT', $resumeUri, $headers, - Stream::factory($chunk) + Psr7\stream_for($chunk) ); return $this->makePutRequest($request); @@ -157,19 +158,18 @@ public function getHttpResultCode() * @return false|mixed false when the upload is unfinished or the decoded http response * */ - private function makePutRequest(Request $request) + private function makePutRequest(RequestInterface $request) { - $http = $this->client->getHttpClient(); - $response = $http->send($request); + $response = $this->client->execute($request); $this->httpResultCode = $response->getStatusCode(); if (308 == $this->httpResultCode) { // Track the amount uploaded. - $range = explode('-', $response->getHeader('range')); + $range = explode('-', $response->getHeaderLine('range')); $this->progress = $range[1] + 1; // Allow for changing upload URLs. - $location = $response->getHeader('location'); + $location = $response->getHeaderLine('location'); if ($location) { $this->resumeUri = $location; } @@ -178,14 +178,7 @@ private function makePutRequest(Request $request) return false; } - $result = $response->json(); - $expectedClass = $this->request->getHeader('X-Php-Expected-Class'); - - if ($expectedClass) { - $result = new $expectedClass($result); - } - - return $result; + return Google_Http_REST::decodeHttpResponse($response, $this->request); } /** @@ -199,9 +192,9 @@ public function resume($resumeUri) 'content-range' => "bytes */$this->size", 'content-length' => 0, ); - $httpRequest = new Google_Http_Request( - $this->resumeUri, + $httpRequest = new Request( 'PUT', + $this->resumeUri, $headers ); @@ -209,24 +202,28 @@ public function resume($resumeUri) } /** - * @return array|bool + * @return Psr\Http\Message\RequestInterface $request * @visible for testing */ private function process() { $this->transformToUploadUrl(); + $request = $this->request; $postBody = ''; $contentType = false; - $meta = (string) $this->request->getBody(); + $meta = (string) $request->getBody(); $meta = is_string($meta) ? json_decode($meta, true) : $meta; $uploadType = $this->getUploadType($meta); - $this->request->getQuery()->set('uploadType', $uploadType); + $request = $request->withUri( + Uri::withQueryValue($request->getUri(), 'uploadType', $uploadType) + ); + $mimeType = $this->mimeType ? $this->mimeType : - $this->request->getHeader('content-type'); + $request->getHeaderLine('content-type'); if (self::UPLOAD_RESUMABLE_TYPE == $uploadType) { $contentType = $mimeType; @@ -250,13 +247,13 @@ private function process() $postBody = $related; } - $this->request->setBody(Stream::factory($postBody)); + $request = $request->withBody(Psr7\stream_for($postBody)); if (isset($contentType) && $contentType) { - $this->request->setHeader('content-type', $contentType); + $request = $request->withHeader('content-type', $contentType); } - return $this->request; + return $this->request = $request; } /** @@ -303,12 +300,12 @@ private function fetchResumeUri() 'expect' => '', ); foreach ($headers as $key => $value) { - $this->request->setHeader($key, $value); + $this->request = $this->request->withHeader($key, $value); } } - $response = $this->client->getHttpClient()->send($this->request); - $location = $response->getHeader('location'); + $response = $this->client->execute($this->request, false); + $location = $response->getHeaderLine('location'); $code = $response->getStatusCode(); if (200 == $code && true == $location) { @@ -316,7 +313,7 @@ private function fetchResumeUri() } $message = $code; - $body = $response->json(); + $body = json_decode((string) $this->request->getBody(), true); if (isset($body['error']['errors'])) { $message .= ': '; foreach ($body['error']['errors'] as $error) { @@ -333,17 +330,22 @@ private function fetchResumeUri() private function transformToUploadUrl() { - $parts = parse_url($this->request->getUrl()); + $parts = parse_url((string) $this->request->getUri()); if (!isset($parts['path'])) { $parts['path'] = ''; } $parts['path'] = '/upload' . $parts['path']; - $url = Url::fromString(Url::buildUrl($parts)); - $this->request->setUrl($url); + $uri = Uri::fromParts($parts); + $this->request = $this->request->withUri($uri); } public function setChunkSize($chunkSize) { $this->chunkSize = $chunkSize; } + + public function getRequest() + { + return $this->request; + } } diff --git a/src/Google/Http/Pool.php b/src/Google/Http/Pool.php deleted file mode 100644 index f8ef527e6..000000000 --- a/src/Google/Http/Pool.php +++ /dev/null @@ -1,96 +0,0 @@ -client = $client; - } - - public function add(RequestInterface $request, $key = false) - { - if (false == $key) { - $key = mt_rand(); - } - - $this->requests[$key] = $request; - } - - public function execute() - { - $responses = Pool::batch($this->client->getHttpClient(), $this->requests); - - return $this->parseResponse($responses); - } - - protected function parseResponse(BatchResults $responses) - { - $batchResponses = array(); - $requestKeys = array_keys($this->requests); - $i = 0; - $j = 0; - while ($i < count($responses)) { - $key = 'response-'.$requestKeys[$j]; - if ($this->isRedirect($responses[$i])) { - $batchResponses[$key.'-redirect'] = $responses[$i]; - $i++; - } - $response = $responses[$i]; - if ( - $response instanceof ResponseInterface && - $response->getStatusCode() < 300 && - $class = $this->requests[$requestKeys[$j]]->getHeader('X-Php-Expected-Class') - ) { - $response = new $class($response->json()); - } - $batchResponses[$key] = $response; - $i++; - $j++; - } - - return $batchResponses; - } - - private function isRedirect($request) - { - // Guzzle returns exceptions instead of request objects for batch requests - // when "exceptions" is set to "true" - if ($request instanceof \Exception) { - return false; - } - - $location = $request->getHeader('Location'); - - return in_array($request->getStatusCode(), array(201, 301, 302, 303, 307, 308)) - && !empty($location); - } -} diff --git a/src/Google/Http/REST.php b/src/Google/Http/REST.php index d11d81670..741ede25f 100644 --- a/src/Google/Http/REST.php +++ b/src/Google/Http/REST.php @@ -15,11 +15,11 @@ * limitations under the License. */ -use GuzzleHttp\Message\RequestInterface; -use GuzzleHttp\Message\ResponseInterface; +use Google\Auth\HttpHandler\HttpHandlerFactory; use GuzzleHttp\ClientInterface; use GuzzleHttp\Exception\RequestException; -use GuzzleHttp\Exception\ParseException; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; /** * This class implements the RESTful transport of apiServiceRequest()'s @@ -27,11 +27,11 @@ class Google_Http_REST { /** - * Executes a GuzzleHttp\Message\Request and (if applicable) automatically retries + * Executes a Psr\Http\Message\RequestInterface and (if applicable) automatically retries * when errors occur. * * @param Google_Client $client - * @param GuzzleHttp\Message\Request $req + * @param Psr\Http\Message\RequestInterface $req * @return array decoded result * @throws Google_Service_Exception on server side error (ie: not authenticated, * invalid or malformed post body, invalid url) @@ -39,14 +39,15 @@ class Google_Http_REST public static function execute( ClientInterface $client, RequestInterface $request, + $expectedClass = null, $config = array(), $retryMap = null ) { $runner = new Google_Task_Runner( $config, - sprintf('%s %s', $request->getMethod(), $request->getUrl()), + sprintf('%s %s', $request->getMethod(), (string) $request->getUri()), array(get_class(), 'doExecute'), - array($client, $request) + array($client, $request, $expectedClass) ); if (!is_null($retryMap)) { @@ -57,18 +58,19 @@ public static function execute( } /** - * Executes a GuzzleHttp\Message\RequestInterface + * Executes a Psr\Http\Message\RequestInterface * * @param Google_Client $client - * @param GuzzleHttp\Message\RequestInterface $request + * @param Psr\Http\Message\RequestInterface $request * @return array decoded result * @throws Google_Service_Exception on server side error (ie: not authenticated, * invalid or malformed post body, invalid url) */ - public static function doExecute(ClientInterface $client, RequestInterface $request) + public static function doExecute(ClientInterface $client, RequestInterface $request, $expectedClass = null) { try { - $response = $client->send($request); + $httpHandler = HttpHandlerFactory::build($client); + $response = $httpHandler($request); } catch (RequestException $e) { // if Guzzle throws an exception, catch it and handle the response if (!$e->hasResponse()) { @@ -77,33 +79,34 @@ public static function doExecute(ClientInterface $client, RequestInterface $requ $response = $e->getResponse(); } - return self::decodeHttpResponse($response, $request); + return self::decodeHttpResponse($response, $request, $expectedClass); } /** * Decode an HTTP Response. * @static * @throws Google_Service_Exception - * @param GuzzleHttp\Message\RequestInterface $response The http response to be decoded. - * @param GuzzleHttp\Message\ResponseInterface $response + * @param Psr\Http\Message\RequestInterface $response The http response to be decoded. + * @param Psr\Http\Message\ResponseInterface $response * @return mixed|null */ public static function decodeHttpResponse( ResponseInterface $response, - RequestInterface $request = null + RequestInterface $request = null, + $expectedClass = null ) { $body = (string) $response->getBody(); $code = $response->getStatusCode(); $result = null; // return raw response when "alt" is "media" - $isJson = !($request && 'media' == $request->getQuery()->get('alt')); + $isJson = !($request && 'media' == $request->getUri()->getQuery('alt')); // set the result to the body if it's not set to anything else if ($isJson) { - try { - $result = $response->json(); - } catch (ParseException $e) { + $result = json_decode($body, true); + if (null === $result && 0 !== json_last_error()) { + // in the event of a parse error, return the raw string $result = $body; } } else { @@ -111,15 +114,25 @@ public static function decodeHttpResponse( } // retry strategy - if ((intVal($code)) >= 300) { + if ((intVal($code)) >= 400) { $errors = null; // Specific check for APIs which don't return error details, such as Blogger. - if (isset($result['error']) && isset($result['error']['errors'])) { + if (isset($result['error']['errors'])) { $errors = $result['error']['errors']; } throw new Google_Service_Exception($body, $code, null, $errors); } - return $result; + // use "is_null" because "false" is used to explicitly + // prevent an expected class from being returned + if (is_null($expectedClass) && $request) { + $expectedClass = $request->getHeaderLine('X-Php-Expected-Class'); + } + + if (!empty($expectedClass)) { + return new $expectedClass($result); + } + + return $response; } } diff --git a/src/Google/Service/Resource.php b/src/Google/Service/Resource.php index d539668fd..1103fe436 100644 --- a/src/Google/Service/Resource.php +++ b/src/Google/Service/Resource.php @@ -15,6 +15,8 @@ * limitations under the License. */ +use GuzzleHttp\Psr7\Request; + /** * Implements the actual methods/resources of the discovered Google API using magic function * calling overloading (__call()), which on call will see if the method name (plus.activities.list) @@ -71,11 +73,11 @@ public function __construct($service, $serviceName, $resourceName, $resource) * TODO: This function needs simplifying. * @param $name * @param $arguments - * @param $expected_class - optional, the expected class name - * @return Google_Http_Request|expected_class + * @param $expectedClass - optional, the expected class name + * @return Google_Http_Request|expectedClass * @throws Google_Exception */ - public function call($name, $arguments, $expected_class = null) + public function call($name, $arguments, $expectedClass = null) { if (! isset($this->methods[$name])) { $this->client->getLogger()->error( @@ -183,29 +185,28 @@ public function call($name, $arguments, $expected_class = null) ) ); + // build the service uri $url = $this->createRequestUri( $method['path'], $parameters ); - $http = $this->client->getHttpClient(); - $this->client->authorize($http); - - // Guzzle 5 cannot locate App Engine certs by default, - // so we tell Guzzle where to look - if ($this->client->isAppEngine()) { - $http->setDefaultOption('verify', '/etc/ca-certificates.crt'); - } - - $request = $http->createRequest( + // NOTE: because we're creating the request by hand, + // and because the service has a rootUrl property + // the "base_uri" of the Http Client is not accounted for + $request = new Request( $method['httpMethod'], $url, - ['json' => $postBody] + ['content-type' => 'application/json'], + $postBody ? json_encode($postBody) : '' ); + // if the client is marked for deferring, rather than + // execute the request, return the response if ($this->client->shouldDefer()) { // @TODO find a better way to do this - $request->setHeader('X-Php-Expected-Class', $expected_class); + $request = $request + ->withHeader('X-Php-Expected-Class', $expectedClass); return $request; } @@ -217,13 +218,18 @@ public function call($name, $arguments, $expected_class = null) : 'application/octet-stream'; $data = $parameters['data']['value']; $upload = new Google_Http_MediaFileUpload($this->client, $request, $mimeType, $data); + + // pull down the modified request + $request = $upload->getRequest(); } + // if this is a media type, we will return the raw response + // rather than using an expected class if (isset($parameters['alt']) && $parameters['alt']['value'] == 'media') { - $expected_class = null; + $expectedClass = null; } - - return $this->client->execute($request, $expected_class); + + return $this->client->execute($request, $expectedClass); } protected function convertToArrayAndStripNulls($o) diff --git a/tests/BaseTest.php b/tests/BaseTest.php index d5c352140..70a45726b 100644 --- a/tests/BaseTest.php +++ b/tests/BaseTest.php @@ -15,6 +15,7 @@ * limitations under the License. */ +use GuzzleHttp\ClientInterface; use Symfony\Component\DomCrawler\Crawler; class BaseTest extends PHPUnit_Framework_TestCase @@ -41,17 +42,22 @@ public function getCache() private function createClient() { - $defaults = [ + $options = [ 'auth' => 'google_auth', - 'exceptions' => false + 'exceptions' => false, ]; + if ($proxy = getenv('HTTP_PROXY')) { - $defaults['proxy'] = $proxy; - $defaults['verify'] = false; + $options['proxy'] = $proxy; + $options['verify'] = false; } - $httpClient = new GuzzleHttp\Client([ - 'defaults' => $defaults, - ]); + + // adjust constructor depending on guzzle version + if (!$this->isGuzzle6()) { + $options = ['defaults' => $options]; + } + + $httpClient = new GuzzleHttp\Client($options); $client = new Google_Client(); $client->setApplicationName('google-api-php-client-tests'); @@ -62,11 +68,13 @@ private function createClient() "https://www.googleapis.com/auth/tasks", "https://www.googleapis.com/auth/adsense", "https://www.googleapis.com/auth/youtube", + "https://www.googleapis.com/auth/drive", ]); if ($this->key) { $client->setDeveloperKey($this->key); } + list($clientId, $clientSecret) = $this->getClientIdAndSecret(); $client->setClientId($clientId); $client->setClientSecret($clientSecret); @@ -189,4 +197,32 @@ protected function loadExample($example) return false; } + + protected function isGuzzle6() + { + $version = ClientInterface::VERSION; + + return ('6' === $version[0]); + } + + protected function isGuzzle5() + { + $version = ClientInterface::VERSION; + + return ('5' === $version[0]); + } + + public function onlyGuzzle6() + { + if (!$this->isGuzzle6()) { + $this->markTestSkipped('Guzzle 6 only'); + } + } + + public function onlyGuzzle5() + { + if (!$this->isGuzzle5()) { + $this->markTestSkipped('Guzzle 5 only'); + } + } } diff --git a/tests/Google/AccessToken/RevokeTest.php b/tests/Google/AccessToken/RevokeTest.php index ee5c45588..0a2ee11df 100644 --- a/tests/Google/AccessToken/RevokeTest.php +++ b/tests/Google/AccessToken/RevokeTest.php @@ -1,6 +1,5 @@ getMock('GuzzleHttp\Post\PostBodyInterface'); - $postBody->expects($this->exactly(2)) - ->method('replaceFields') - ->will($this->returnCallback( - function ($fields) use (&$token) { - $token = isset($fields['token']) ? $fields['token'] : null; - } - )); - $request = $this->getMock('GuzzleHttp\Message\RequestInterface'); - $request->expects($this->exactly(2)) - ->method('getBody') - ->will($this->returnValue($postBody)); - $response = $this->getMock('GuzzleHttp\Message\ResponseInterface'); + $response = $this->getMock('Psr\Http\Message\ResponseInterface'); $response->expects($this->exactly(2)) ->method('getStatusCode') ->will($this->returnValue(200)); $http = $this->getMock('GuzzleHttp\ClientInterface'); $http->expects($this->exactly(2)) ->method('send') - ->will($this->returnValue($response)); - $http->expects($this->exactly(2)) - ->method('createRequest') - ->will($this->returnValue($request)); + ->will($this->returnCallback( + function ($request) use (&$token, $response) { + parse_str((string) $request->getBody(), $fields); + $token = isset($fields['token']) ? $fields['token'] : null; + + return $response; + } + )); + + // adds support for extra "createRequest" step (required for Guzzle 5) + if ($this->isGuzzle5()) { + $requestToken = null; + $request = $this->getMock('GuzzleHttp\Message\RequestInterface'); + $request->expects($this->exactly(2)) + ->method('getBody') + ->will($this->returnCallback( + function () use (&$requestToken) { + return 'token='.$requestToken; + })); + $http->expects($this->exactly(2)) + ->method('createRequest') + ->will($this->returnCallback( + function ($method, $url, $params) use (&$requestToken, $request) { + parse_str((string) $params['body'], $fields); + $requestToken = isset($fields['token']) ? $fields['token'] : null; + + return $request; + } + )); + } $t = array( 'access_token' => $accessToken, @@ -77,9 +90,15 @@ function ($fields) use (&$token) { $this->assertEquals($refreshToken, $token); } - /** @expectedException PHPUnit_Framework_Error */ public function testInvalidStringToken() { + $phpVersion = phpversion(); + if ('7' === $phpVersion[0]) { + // primitive type hints actually throw exceptions in PHP7 + $this->setExpectedException('TypeError'); + } else { + $this->setExpectedException('PHPUnit_Framework_Error'); + } // Test with string token $revoke = new Google_AccessToken_Revoke(); $revoke->revokeToken('ACCESS_TOKEN'); diff --git a/tests/Google/AccessToken/VerifyTest.php b/tests/Google/AccessToken/VerifyTest.php index 7befe9d5e..adbfca728 100644 --- a/tests/Google/AccessToken/VerifyTest.php +++ b/tests/Google/AccessToken/VerifyTest.php @@ -1,6 +1,5 @@ getJwtService(); $client = $this->getClient(); + $http = $client->getHttpClient(); $token = $client->getAccessToken(); if ($client->isAccessTokenExpired()) { $token = $client->fetchAccessTokenWithRefreshToken(); @@ -43,7 +43,7 @@ public function testValidateIdToken() $this->assertEquals(3, count($segments)); // Extract the client ID in this case as it wont be set on the test client. $data = json_decode($jwt->urlSafeB64Decode($segments[1])); - $verify = new Google_AccessToken_Verify(); + $verify = new Google_AccessToken_Verify($http); $payload = $verify->verifyIdToken($token['id_token'], $data->aud); $this->assertTrue(isset($payload['sub'])); $this->assertTrue(strlen($payload['sub']) > 0); @@ -52,13 +52,30 @@ public function testValidateIdToken() // caching for this test to make sense. Not sure how to do that // at the moment. $client = $this->getClient(); + $http = $client->getHttpClient(); $data = json_decode($jwt->urlSafeB64Decode($segments[1])); - $verify = new Google_AccessToken_Verify(); + $verify = new Google_AccessToken_Verify($http); $payload = $verify->verifyIdToken($token['id_token'], $data->aud); $this->assertTrue(isset($payload['sub'])); $this->assertTrue(strlen($payload['sub']) > 0); } + public function testRetrieveCertsFromLocation() + { + $client = $this->getClient(); + $verify = new Google_AccessToken_Verify($client->getHttpClient()); + + // make this method public for testing purposes + $method = new ReflectionMethod($verify, 'retrieveCertsFromLocation'); + $method->setAccessible(true); + $certs = $method->invoke($verify, Google_AccessToken_Verify::FEDERATED_SIGNON_CERT_URL); + + $this->assertArrayHasKey('keys', $certs); + $this->assertEquals(2, count($certs['keys'])); + $this->assertArrayHasKey('alg', $certs['keys'][0]); + $this->assertEquals('RS256', $certs['keys'][0]['alg']); + } + private function getJwtService() { if (class_exists('\Firebase\JWT\JWT')) { diff --git a/tests/Google/ClientTest.php b/tests/Google/ClientTest.php index 2c3b9dac4..0ab3a6aec 100644 --- a/tests/Google/ClientTest.php +++ b/tests/Google/ClientTest.php @@ -20,7 +20,7 @@ use GuzzleHttp\Client; use GuzzleHttp\Event\RequestEvents; -use GuzzleHttp\Message\Request; +use Psr\Http\Message\Request; class Google_ClientTest extends BaseTest { @@ -37,10 +37,71 @@ public function testSignAppKey() $http = new Client(); $client->authorize($http); - $listeners = $http->getEmitter()->listeners('before'); - $this->assertEquals(1, count($listeners)); - $this->assertEquals(2, count($listeners[0])); - $this->assertInstanceOf('Google\Auth\Simple', $listeners[0][0]); + $this->checkAuthHandler($http, 'Simple'); + } + + private function checkAuthHandler($http, $className) + { + if ($this->isGuzzle6()) { + $stack = $http->getConfig('handler'); + $class = new ReflectionClass(get_class($stack)); + $property = $class->getProperty('stack'); + $property->setAccessible(true); + $middlewares = $property->getValue($stack); + $middleware = array_pop($middlewares); + + if (is_null($className)) { + // only the default middlewares have been added + $this->assertEquals(3, count($middlewares)); + } else { + $authClass = sprintf('Google\Auth\Middleware\%sMiddleware', $className); + $this->assertInstanceOf($authClass, $middleware[0]); + } + } else { + $listeners = $http->getEmitter()->listeners('before'); + + if (is_null($className)) { + $this->assertEquals(0, count($listeners)); + } else { + $authClass = sprintf('Google\Auth\Subscriber\%sSubscriber', $className); + $this->assertEquals(1, count($listeners)); + $this->assertEquals(2, count($listeners[0])); + $this->assertInstanceOf($authClass, $listeners[0][0]); + } + } + } + + private function checkCredentials($http, $fetcherClass, $sub = null) + { + if ($this->isGuzzle6()) { + $stack = $http->getConfig('handler'); + $class = new ReflectionClass(get_class($stack)); + $property = $class->getProperty('stack'); + $property->setAccessible(true); + $middlewares = $property->getValue($stack); // Works + $middleware = array_pop($middlewares); + $auth = $middleware[0]; + } else { + // access the protected $fetcher property + $listeners = $http->getEmitter()->listeners('before'); + $auth = $listeners[0][0]; + } + + $class = new ReflectionClass(get_class($auth)); + $property = $class->getProperty('fetcher'); + $property->setAccessible(true); + $fetcher = $property->getValue($auth); + $this->assertInstanceOf($fetcherClass, $fetcher); + + if ($sub) { + // access the protected $auth property + $class = new ReflectionClass(get_class($fetcher)); + $property = $class->getProperty('auth'); + $property->setAccessible(true); + $auth = $property->getValue($fetcher); + + $this->assertEquals($sub, $auth->getSub()); + } } public function testSignAccessToken() @@ -56,10 +117,7 @@ public function testSignAccessToken() $client->setScopes('test_scope'); $client->authorize($http); - $listeners = $http->getEmitter()->listeners('before'); - $this->assertEquals(1, count($listeners)); - $this->assertEquals(2, count($listeners[0])); - $this->assertInstanceOf('Google\Auth\ScopedAccessToken', $listeners[0][0]); + $this->checkAuthHandler($http, 'ScopedAccessToken'); } public function testCreateAuthUrl() @@ -78,14 +136,15 @@ public function testCreateAuthUrl() $authUrl = $client->createAuthUrl("http://googleapis.com/scope/foo"); $expected = "https://accounts.google.com/o/oauth2/auth" - . "?access_type=offline" - . "&approval_prompt=force" - . "&login_hint=bob%40example.org" - . "&response_type=code" - . "&scope=http%3A%2F%2Fgoogleapis.com%2Fscope%2Ffoo" - . "&state=xyz" + . "?response_type=code" + . "&access_type=offline" . "&client_id=clientId1" - . "&redirect_uri=http%3A%2F%2Flocalhost"; + . "&redirect_uri=http%3A%2F%2Flocalhost" + . "&state=xyz" + . "&scope=http%3A%2F%2Fgoogleapis.com%2Fscope%2Ffoo" + . "&approval_prompt=force" + . "&login_hint=bob%40example.org"; + $this->assertEquals($expected, $authUrl); // Again with a blank login hint (should remove all traces from authUrl) @@ -96,16 +155,17 @@ public function testCreateAuthUrl() $client->setIncludeGrantedScopes(true); $authUrl = $client->createAuthUrl("http://googleapis.com/scope/foo"); $expected = "https://accounts.google.com/o/oauth2/auth" - . "?access_type=offline" + . "?response_type=code" + . "&access_type=offline" + . "&client_id=clientId1" + . "&redirect_uri=http%3A%2F%2Flocalhost" + . "&state=xyz" + . "&scope=http%3A%2F%2Fgoogleapis.com%2Fscope%2Ffoo" . "&hd=example.com" . "&include_granted_scopes=true" . "&openid.realm=example.com" - . "&prompt=select_account" - . "&response_type=code" - . "&scope=http%3A%2F%2Fgoogleapis.com%2Fscope%2Ffoo" - . "&state=xyz" - . "&client_id=clientId1" - . "&redirect_uri=http%3A%2F%2Flocalhost"; + . "&prompt=select_account"; + $this->assertEquals($expected, $authUrl); } @@ -150,34 +210,34 @@ public function testPrepareService() $this->assertEquals( '' . 'https://accounts.google.com/o/oauth2/auth' - . '?access_type=online' - . '&approval_prompt=auto' - . '&response_type=code' - . '&scope=http%3A%2F%2Ftest.com%20scope2' - . '&state=xyz' + . '?response_type=code' + . '&access_type=online' . '&client_id=test1' - . '&redirect_uri=http%3A%2F%2Flocalhost%2F', + . '&redirect_uri=http%3A%2F%2Flocalhost%2F' + . '&state=xyz' + . '&scope=http%3A%2F%2Ftest.com%20scope2' + . '&approval_prompt=auto', + $client->createAuthUrl() ); - $response = $this->getMock('GuzzleHttp\Message\ResponseInterface'); + $response = $this->getMock('Psr\Http\Message\ResponseInterface'); $response->expects($this->once()) ->method('getBody') - ->will($this->returnValue($this->getMock('GuzzleHttp\Post\PostBody'))); - $response->expects($this->once()) - ->method('json') - ->will($this->returnValue($this->getMock('Google_Model'))); - $request = $this->getMock('GuzzleHttp\Message\RequestInterface'); - $request->expects($this->once()) - ->method('getQuery') - ->will($this->returnValue($this->getMock('GuzzleHttp\Query'))); + ->will($this->returnValue($this->getMock('Psr\Http\Message\StreamInterface'))); $http = $this->getMock('GuzzleHttp\ClientInterface'); - $http->expects($this->once()) - ->method('createRequest') - ->will($this->returnValue($request)); $http->expects($this->once()) ->method('send') ->will($this->returnValue($response)); + + if ($this->isGuzzle5()) { + $guzzle5Request = new GuzzleHttp\Message\Request('POST', '/'); + $http->expects($this->once()) + ->method('createRequest') + ->will($this->returnValue($guzzle5Request)); + } + + $client->setHttpClient($http); $dr_service = new Google_Service_Drive($client); $this->assertInstanceOf('Google_Model', $dr_service->files->listFiles()); @@ -211,12 +271,23 @@ public function testSettersGetters() /** * @requires extension Memcached */ - public function testAppEngineAutoConfig() + public function testAppEngineMemcacheConfig() { $_SERVER['SERVER_SOFTWARE'] = 'Google App Engine'; $client = new Google_Client(); + $this->assertInstanceOf('Google_Cache_Memcache', $client->getCache()); + unset($_SERVER['SERVER_SOFTWARE']); + } + + public function testAppEngineStreamHandlerConfig() + { + $this->onlyGuzzle5(); + + $_SERVER['SERVER_SOFTWARE'] = 'Google App Engine'; + $client = new Google_Client(); + // check Stream Handler is used $http = $client->getHttpClient(); $class = new ReflectionClass(get_class($http)); @@ -234,6 +305,21 @@ public function testAppEngineAutoConfig() unset($_SERVER['SERVER_SOFTWARE']); } + public function testAppEngineVerifyConfig() + { + $this->onlyGuzzle5(); + + $_SERVER['SERVER_SOFTWARE'] = 'Google App Engine'; + $client = new Google_Client(); + + $this->assertEquals( + '/etc/ca-certificates.crt', + $client->getHttpClient()->getDefaultOption('verify') + ); + + unset($_SERVER['SERVER_SOFTWARE']); + } + public function testJsonConfig() { // Device config @@ -291,67 +377,41 @@ public function testNoAuth() $http = new Client(); $client->authorize($http); - $listeners = $http->getEmitter()->listeners('before'); - putenv("GOOGLE_APPLICATION_CREDENTIALS=$GOOGLE_APPLICATION_CREDENTIALS"); putenv("HOME=$HOME"); - $this->assertEquals(0, count($listeners)); + $this->checkAuthHandler($http, null); } public function testApplicationDefaultCredentials() { + $this->checkServiceAccountCredentials(); + $credentialsFile = getenv('GOOGLE_APPLICATION_CREDENTIALS'); + $client = new Google_Client(); - $client->setAuthConfig(__DIR__.'/../config/application-default-credentials.json'); + $client->setAuthConfig($credentialsFile); $http = new Client(); $client->authorize($http); - $listeners = $http->getEmitter()->listeners('before'); - - $this->assertEquals(1, count($listeners)); - $this->assertEquals(2, count($listeners[0])); - $this->assertInstanceOf('Google\Auth\AuthTokenFetcher', $listeners[0][0]); - - // access the protected $fetcher property - $class = new ReflectionClass(get_class($listeners[0][0])); - $property = $class->getProperty('fetcher'); - $property->setAccessible(true); - $fetcher = $property->getValue($listeners[0][0]); - - $this->assertInstanceOf('Google\Auth\ServiceAccountCredentials', $fetcher); + $this->checkAuthHandler($http, 'AuthToken'); + $this->checkCredentials($http, 'Google\Auth\Credentials\ServiceAccountCredentials'); } public function testApplicationDefaultCredentialsWithSubject() { + $this->checkServiceAccountCredentials(); + $credentialsFile = getenv('GOOGLE_APPLICATION_CREDENTIALS'); + $sub = 'sub123'; $client = new Google_Client(); - $client->setAuthConfig(__DIR__.'/../config/application-default-credentials.json'); + $client->setAuthConfig($credentialsFile); $client->setSubject($sub); $http = new Client(); $client->authorize($http); - $listeners = $http->getEmitter()->listeners('before'); - - $this->assertEquals(1, count($listeners)); - $this->assertEquals(2, count($listeners[0])); - $this->assertInstanceOf('Google\Auth\AuthTokenFetcher', $listeners[0][0]); - - // access the protected $fetcher property - $class = new ReflectionClass(get_class($listeners[0][0])); - $property = $class->getProperty('fetcher'); - $property->setAccessible(true); - $fetcher = $property->getValue($listeners[0][0]); - - $this->assertInstanceOf('Google\Auth\ServiceAccountCredentials', $fetcher); - - // access the protected $auth property - $class = new ReflectionClass(get_class($fetcher)); - $property = $class->getProperty('auth'); - $property->setAccessible(true); - $auth = $property->getValue($fetcher); - - $this->assertEquals($sub, $auth->getSub()); + $this->checkAuthHandler($http, 'AuthToken'); + $this->checkCredentials($http, 'Google\Auth\Credentials\ServiceAccountCredentials', $sub); } /** @@ -359,27 +419,30 @@ public function testApplicationDefaultCredentialsWithSubject() */ public function testRefreshTokenSetsValues() { - $request = $this->getMock('GuzzleHttp\Message\RequestInterface'); - $request->expects($this->once()) - ->method('getBody') - ->will($this->returnValue($this->getMock('GuzzleHttp\Post\PostBodyInterface'))); - $response = $this->getMock('GuzzleHttp\Message\ResponseInterface'); - $response->expects($this->once()) - ->method('json') - ->will($this->returnValue(array( - 'access_token' => 'xyz', - 'id_token' => 'ID_TOKEN', - ))); + $token = json_encode(array( + 'access_token' => 'xyz', + 'id_token' => 'ID_TOKEN', + )); + $postBody = $this->getMock('Psr\Http\Message\StreamInterface'); + $postBody->expects($this->once()) + ->method('__toString') + ->will($this->returnValue($token)); + $response = $this->getMock('Psr\Http\Message\ResponseInterface'); $response->expects($this->once()) ->method('getBody') - ->will($this->returnValue($this->getMock('GuzzleHttp\Post\PostBody'))); + ->will($this->returnValue($postBody)); $http = $this->getMock('GuzzleHttp\ClientInterface'); $http->expects($this->once()) ->method('send') ->will($this->returnValue($response)); - $http->expects($this->once()) - ->method('createRequest') - ->will($this->returnValue($request)); + + if ($this->isGuzzle5()) { + $guzzle5Request = new GuzzleHttp\Message\Request('POST', '/', ['body' => $token]); + $http->expects($this->once()) + ->method('createRequest') + ->will($this->returnValue($guzzle5Request)); + } + $client = $this->getClient(); $client->setHttpClient($http); $client->fetchAccessTokenWithRefreshToken("REFRESH_TOKEN"); @@ -418,4 +481,32 @@ public function testFetchAccessTokenWithAssertionFromFile() $this->assertNotNull($token); $this->assertArrayHasKey('access_token', $token); } + + /** + * Test fetching an access token with assertion credentials + * using "setAuthConfig" and "setSubject" but with user credentials + */ + public function testBadSubjectThrowsException() + { + $this->checkServiceAccountCredentials(); + + $client = $this->getClient(); + $client->useApplicationDefaultCredentials(); + $client->setSubject('bad-subject'); + + $authHandler = Google_AuthHandler_AuthHandlerFactory::build(); + + // make this method public for testing purposes + $method = new ReflectionMethod($authHandler, 'createAuthHttp'); + $method->setAccessible(true); + $authHttp = $method->invoke($authHandler, $client->getHttpClient()); + + try { + $token = $client->fetchAccessTokenWithAssertion($authHttp); + $this->fail('no exception thrown'); + } catch (GuzzleHttp\Exception\ClientException $e) { + $response = $e->getResponse(); + $this->assertContains('Invalid impersonation prn email address', (string) $response->getBody()); + } + } } diff --git a/tests/Google/Http/MediaFileUploadTest.php b/tests/Google/Http/MediaFileUploadTest.php new file mode 100644 index 000000000..2458ed9b3 --- /dev/null +++ b/tests/Google/Http/MediaFileUploadTest.php @@ -0,0 +1,200 @@ +getClient(); + $request = new Request('POST', 'http://www.example.com'); + $media = new Google_Http_MediaFileUpload( + $client, + $request, + 'image/png', + base64_decode('data:image/png;base64,a') + ); + $request = $media->getRequest(); + + $this->assertEquals(0, $media->getProgress()); + $this->assertGreaterThan(0, strlen($request->getBody())); + } + + public function testGetUploadType() + { + $client = $this->getClient(); + $request = new Request('POST', 'http://www.example.com'); + + // Test resumable upload + $media = new Google_Http_MediaFileUpload($client, $request, 'image/png', 'a', true); + $this->assertEquals('resumable', $media->getUploadType(null)); + + // Test data *only* uploads + $media = new Google_Http_MediaFileUpload($client, $request, 'image/png', 'a', false); + $this->assertEquals('media', $media->getUploadType(null)); + + // Test multipart uploads + $media = new Google_Http_MediaFileUpload($client, $request, 'image/png', 'a', false); + $this->assertEquals('multipart', $media->getUploadType(array('a' => 'b'))); + } + + public function testProcess() + { + $client = $this->getClient(); + $data = 'foo'; + + // Test data *only* uploads. + $request = new Request('POST', 'http://www.example.com'); + $media = new Google_Http_MediaFileUpload($client, $request, 'image/png', $data, false); + $request = $media->getRequest(); + $this->assertEquals($data, (string) $request->getBody()); + + // Test resumable (meta data) - we want to send the metadata, not the app data. + $request = new Request('POST', 'http://www.example.com'); + $reqData = json_encode("hello"); + $request = $request->withBody(Psr7\stream_for($reqData)); + $media = new Google_Http_MediaFileUpload($client, $request, 'image/png', $data, true); + $request = $media->getRequest(); + $this->assertEquals(json_decode($reqData), (string) $request->getBody()); + + // Test multipart - we are sending encoded meta data and post data + $request = new Request('POST', 'http://www.example.com'); + $reqData = json_encode("hello"); + $request = $request->withBody(Psr7\stream_for($reqData)); + $media = new Google_Http_MediaFileUpload($client, $request, 'image/png', $data, false); + $request = $media->getRequest(); + $this->assertContains($reqData, (string) $request->getBody()); + $this->assertContains(base64_encode($data), (string) $request->getBody()); + } + + public function testGetResumeUri() + { + $this->checkToken(); + + $client = $this->getClient(); + $client->addScope("https://www.googleapis.com/auth/drive"); + $service = new Google_Service_Drive($client); + $file = new Google_Service_Drive_DriveFile(); + $file->title = 'TESTFILE-testGetResumeUri'; + $chunkSizeBytes = 1 * 1024 * 1024; + + // Call the API with the media upload, defer so it doesn't immediately return. + $client->setDefer(true); + $request = $service->files->insert($file); + + // Create a media file upload to represent our upload process. + $media = new Google_Http_MediaFileUpload( + $client, + $request, + 'text/plain', + null, + true, + $chunkSizeBytes + ); + + // request the resumable url + $uri = $media->getResumeUri(); + $this->assertTrue(is_string($uri)); + + // parse the URL + $parts = parse_url($uri); + $this->assertArrayHasKey('query', $parts); + + // parse the querystring + parse_str($parts['query'], $query); + $this->assertArrayHasKey('uploadType', $query); + $this->assertArrayHasKey('upload_id', $query); + $this->assertEquals('resumable', $query['uploadType']); + } + + public function testNextChunk() + { + $this->checkToken(); + + $client = $this->getClient(); + $client->addScope("https://www.googleapis.com/auth/drive"); + $service = new Google_Service_Drive($client); + + $data = 'foo'; + $file = new Google_Service_Drive_DriveFile(); + $file->title = $title = 'TESTFILE-testNextChunk'; + + // Call the API with the media upload, defer so it doesn't immediately return. + $client->setDefer(true); + $request = $service->files->insert($file); + + // Create a media file upload to represent our upload process. + $media = new Google_Http_MediaFileUpload( + $client, + $request, + 'text/plain', + null, + true + ); + $media->setFileSize(strlen($data)); + + // upload the file + $file = $media->nextChunk($data); + $this->assertInstanceOf('Google_Service_Drive_DriveFile', $file); + $this->assertEquals($title, $file->title); + + // remove the file + $client->setDefer(false); + $response = $service->files->delete($file->id); + $this->assertEquals(204, $response->getStatusCode()); + } + + public function testNextChunkWithMoreRemaining() + { + $this->checkToken(); + + $client = $this->getClient(); + $client->addScope("https://www.googleapis.com/auth/drive"); + $service = new Google_Service_Drive($client); + + $chunkSizeBytes = 262144; // smallest chunk size allowed by APIs + $data = str_repeat('.', $chunkSizeBytes+1); + $file = new Google_Service_Drive_DriveFile(); + $file->title = $title = 'TESTFILE-testNextChunkWithMoreRemaining'; + + // Call the API with the media upload, defer so it doesn't immediately return. + $client->setDefer(true); + $request = $service->files->insert($file); + + // Create a media file upload to represent our upload process. + $media = new Google_Http_MediaFileUpload( + $client, + $request, + 'text/plain', + $data, + true, + $chunkSizeBytes + ); + $media->setFileSize(strlen($data)); + + // upload the file + $file = $media->nextChunk(); + // false means we aren't done uploading, which is exactly what we expect! + $this->assertFalse($file); + } +} diff --git a/tests/Google/Http/MediaFuleUploadTest.php b/tests/Google/Http/MediaFuleUploadTest.php deleted file mode 100644 index b4853b805..000000000 --- a/tests/Google/Http/MediaFuleUploadTest.php +++ /dev/null @@ -1,86 +0,0 @@ -getClient(); - $request = new Request('POST', 'http://www.example.com'); - $media = new Google_Http_MediaFileUpload( - $client, - $request, - 'image/png', - base64_decode('data:image/png;base64,a') - ); - - $this->assertEquals(0, $media->getProgress()); - $this->assertGreaterThan(0, strlen($request->getBody())); - } - - public function testGetUploadType() - { - $client = $this->getClient(); - $request = new Request('POST', 'http://www.example.com'); - - // Test resumable upload - $media = new Google_Http_MediaFileUpload($client, $request, 'image/png', 'a', true); - $params = array('mediaUpload' => array('value' => $media)); - $this->assertEquals('resumable', $media->getUploadType(null)); - - // Test data *only* uploads - $media = new Google_Http_MediaFileUpload($client, $request, 'image/png', 'a', false); - $this->assertEquals('media', $media->getUploadType(null)); - - // Test multipart uploads - $media = new Google_Http_MediaFileUpload($client, $request, 'image/png', 'a', false); - $this->assertEquals('multipart', $media->getUploadType(array('a' => 'b'))); - } - - public function testProcess() - { - $client = $this->getClient(); - $data = 'foo'; - - // Test data *only* uploads. - $request = new Request('POST', 'http://www.example.com'); - $media = new Google_Http_MediaFileUpload($client, $request, 'image/png', $data, false); - $this->assertEquals($data, (string) $request->getBody()); - - // Test resumable (meta data) - we want to send the metadata, not the app data. - $request = new Request('POST', 'http://www.example.com'); - $reqData = json_encode("hello"); - $request->setBody(Stream::factory($reqData)); - $media = new Google_Http_MediaFileUpload($client, $request, 'image/png', $data, true); - $this->assertEquals(json_decode($reqData), (string) $request->getBody()); - - // Test multipart - we are sending encoded meta data and post data - $request = new Request('POST', 'http://www.example.com'); - $reqData = json_encode("hello"); - $request->setBody(Stream::factory($reqData)); - $media = new Google_Http_MediaFileUpload($client, $request, 'image/png', $data, false); - $this->assertContains($reqData, (string) $request->getBody()); - $this->assertContains(base64_encode($data), (string) $request->getBody()); - } -} diff --git a/tests/Google/Http/PoolTest.php b/tests/Google/Http/PoolTest.php deleted file mode 100644 index 952bad0a7..000000000 --- a/tests/Google/Http/PoolTest.php +++ /dev/null @@ -1,63 +0,0 @@ -checkToken(); - $client = $this->getClient(); - $client->setUseBatch(true); - $this->parallel = new Google_Http_Pool($client); - $this->plus = new Google_Service_Plus($client); - } - - public function testParallelRequestWithAuth() - { - $this->parallel->add($this->plus->people->get('me'), 'key1'); - $this->parallel->add($this->plus->people->get('me'), 'key2'); - $this->parallel->add($this->plus->people->get('me'), 'key3'); - - $result = $this->parallel->execute(); - $this->assertTrue(isset($result['response-key1'])); - $this->assertTrue(isset($result['response-key2'])); - $this->assertTrue(isset($result['response-key3'])); - $this->assertInstanceOf('Google_Service_Plus_Person', $result['response-key1']); - $this->assertInstanceOf('Google_Service_Plus_Person', $result['response-key2']); - $this->assertInstanceOf('Google_Service_Plus_Person', $result['response-key3']); - } - - public function testInvalidParallelRequest() - { - $this->parallel->add($this->plus->people->get('123456789987654321'), 'key1'); - $this->parallel->add($this->plus->people->get('+LarryPage'), 'key2'); - - $result = $this->parallel->execute(); - $this->assertTrue(isset($result['response-key2'])); - $this->assertInstanceOf( - 'GuzzleHttp\Message\Response', - $result['response-key1'] - ); - $this->assertEquals(404, $result['response-key1']->getStatusCode()); - } -} diff --git a/tests/Google/Http/RESTTest.php b/tests/Google/Http/RESTTest.php index 22664fb13..cd5bb6a64 100644 --- a/tests/Google/Http/RESTTest.php +++ b/tests/Google/Http/RESTTest.php @@ -15,9 +15,9 @@ * limitations under the License. */ -use GuzzleHttp\Message\Request; -use GuzzleHttp\Message\Response; -use GuzzleHttp\Stream\Stream; +use GuzzleHttp\Psr7; +use GuzzleHttp\Psr7\Request; +use GuzzleHttp\Psr7\Response; class Google_HTTP_RESTTest extends BaseTest { @@ -29,22 +29,23 @@ class Google_HTTP_RESTTest extends BaseTest public function setUp() { $this->rest = new Google_Http_REST(); + $this->request = new Request('GET', '/'); } public function testDecodeResponse() { $client = $this->getClient(); $response = new Response(204); - $decoded = $this->rest->decodeHttpResponse($response); - $this->assertEquals(null, $decoded); + $decoded = $this->rest->decodeHttpResponse($response, $this->request); + $this->assertEquals($response, $decoded); foreach (array(200, 201) as $code) { $headers = array('foo', 'bar'); - $stream = Stream::factory('{"a": 1}'); + $stream = Psr7\stream_for('{"a": 1}'); $response = new Response($code, $headers, $stream); - $decoded = $this->rest->decodeHttpResponse($response); - $this->assertEquals(array("a" => 1), $decoded); + $decoded = $this->rest->decodeHttpResponse($response, $this->request); + $this->assertEquals('{"a": 1}', (string) $decoded->getBody()); } } @@ -54,11 +55,11 @@ public function testDecodeMediaResponse() $request = new Request('GET', 'http://www.example.com?alt=media'); $headers = array(); - $stream = Stream::factory('thisisnotvalidjson'); + $stream = Psr7\stream_for('thisisnotvalidjson'); $response = new Response(200, $headers, $stream); $decoded = $this->rest->decodeHttpResponse($response, $request); - $this->assertEquals('thisisnotvalidjson', $decoded); + $this->assertEquals('thisisnotvalidjson', (string) $decoded->getBody()); } @@ -66,16 +67,16 @@ public function testDecodeMediaResponse() public function testDecode500ResponseThrowsException() { $response = new Response(500); - $this->rest->decodeHttpResponse($response); + $this->rest->decodeHttpResponse($response, $this->request); } public function testDecodeEmptyResponse() { - $stream = Stream::factory('{}'); + $stream = Psr7\stream_for('{}'); $response = new Response(200, array(), $stream); - $decoded = $this->rest->decodeHttpResponse($response); - $this->assertEquals(array(), $decoded); + $decoded = $this->rest->decodeHttpResponse($response, $this->request); + $this->assertEquals('{}', (string) $decoded->getBody()); } /** @@ -83,7 +84,7 @@ public function testDecodeEmptyResponse() */ public function testBadErrorFormatting() { - $stream = Stream::factory( + $stream = Psr7\stream_for( '{ "error": { "code": 500, @@ -92,7 +93,7 @@ public function testBadErrorFormatting() }' ); $response = new Response(500, array(), $stream); - $this->rest->decodeHttpResponse($response); + $this->rest->decodeHttpResponse($response, $this->request); } /** @@ -100,7 +101,7 @@ public function testBadErrorFormatting() */ public function tesProperErrorFormatting() { - $stream = Stream::factory( + $stream = Psr7\stream_for( '{ error: { errors: [ @@ -117,7 +118,7 @@ public function tesProperErrorFormatting() }' ); $response = new Response(401, array(), $stream); - $this->rest->decodeHttpResponse($response); + $this->rest->decodeHttpResponse($response, $this->request); } /** @@ -125,8 +126,8 @@ public function tesProperErrorFormatting() */ public function testNotJson404Error() { - $stream = Stream::factory('Not Found'); + $stream = Psr7\stream_for('Not Found'); $response = new Response(404, array(), $stream); - $this->rest->decodeHttpResponse($response); + $this->rest->decodeHttpResponse($response, $this->request); } } diff --git a/tests/Google/Service/ResourceTest.php b/tests/Google/Service/ResourceTest.php index 7c77a2d73..fb3a8c613 100644 --- a/tests/Google/Service/ResourceTest.php +++ b/tests/Google/Service/ResourceTest.php @@ -18,23 +18,23 @@ * under the License. */ -use GuzzleHttp\Message\Request; -use GuzzleHttp\Message\Response; -use GuzzleHttp\Stream\Stream; +use GuzzleHttp\Psr7; +use GuzzleHttp\Psr7\Request; +use GuzzleHttp\Psr7\Response; class Test_Google_Service extends Google_Service { public function __construct(Google_Client $client) { parent::__construct($client); - $this->rootUrl = ""; + $this->rootUrl = "https://test.example.com"; $this->servicePath = ""; $this->version = "v1beta1"; $this->serviceName = "test"; } } -class Google_Service_ResourceTest extends PHPUnit_Framework_TestCase +class Google_Service_ResourceTest extends BaseTest { private $client; private $service; @@ -56,9 +56,7 @@ public function setUp() ->will($this->returnValue(true)); $this->client->expects($this->any()) ->method("getHttpClient") - ->will($this->returnValue(new GuzzleHttp\Client([ - 'base_url' => 'https://test.example.com' - ]))); + ->will($this->returnValue(new GuzzleHttp\Client())); $this->service = new Test_Google_Service($this->client); } @@ -102,7 +100,7 @@ public function testCall() ) ); $request = $resource->call("testMethod", array(array())); - $this->assertEquals("https://test.example.com/method/path", $request->getUrl()); + $this->assertEquals("https://test.example.com/method/path", (string) $request->getUri()); $this->assertEquals("POST", $request->getMethod()); } @@ -124,7 +122,7 @@ public function testCallServiceDefinedRoot() ) ); $request = $resource->call("testMethod", array(array())); - $this->assertEquals("https://sample.example.com/method/path", $request->getUrl()); + $this->assertEquals("https://sample.example.com/method/path", (string) $request->getUri()); $this->assertEquals("POST", $request->getMethod()); } @@ -172,38 +170,12 @@ public function testCreateRequestUri() $this->assertEquals("http://localhost/plus?u=%40me%2F", $value); } - public function testAppEngineSslCerts() - { - $this->client->expects($this->once()) - ->method("isAppEngine") - ->will($this->returnValue(true)); - $resource = new Google_Service_Resource( - $this->service, - "test", - "testResource", - array("methods" => - array( - "testMethod" => array( - "parameters" => array(), - "path" => "method/path", - "httpMethod" => "POST", - ) - ) - ) - ); - $request = $resource->call("testMethod", array(array())); - $this->assertEquals( - '/etc/ca-certificates.crt', - $this->client->getHttpClient()->getDefaultOption('verify') - ); - } - public function testNoExpectedClassForAltMediaWithHttpSuccess() { // set the "alt" parameter to "media" $arguments = [['alt' => 'media']]; $request = new Request('GET', '/?alt=media'); - $body = Stream::factory('thisisnotvalidjson'); + $body = Psr7\stream_for('thisisnotvalidjson'); $response = new Response(200, [], $body); $http = $this->getMockBuilder("GuzzleHttp\Client") @@ -212,9 +184,13 @@ public function testNoExpectedClassForAltMediaWithHttpSuccess() $http->expects($this->once()) ->method('send') ->will($this->returnValue($response)); - $http->expects($this->any()) + + if ($this->isGuzzle5()) { + $http->expects($this->once()) ->method('createRequest') - ->will($this->returnValue($request)); + ->will($this->returnValue(new GuzzleHttp\Message\Request('GET', '/?alt=media'))); + } + $client = new Google_Client(); $client->setHttpClient($http); $service = new Test_Google_Service($client); @@ -236,8 +212,9 @@ public function testNoExpectedClassForAltMediaWithHttpSuccess() ); $expectedClass = 'ThisShouldBeIgnored'; - $decoded = $resource->call('testMethod', $arguments, $expectedClass); - $this->assertEquals('thisisnotvalidjson', $decoded); + $response = $resource->call('testMethod', $arguments, $expectedClass); + $this->assertInstanceOf('Psr\Http\Message\ResponseInterface', $response); + $this->assertEquals('thisisnotvalidjson', (string) $response->getBody()); } public function testNoExpectedClassForAltMediaWithHttpFail() @@ -245,8 +222,8 @@ public function testNoExpectedClassForAltMediaWithHttpFail() // set the "alt" parameter to "media" $arguments = [['alt' => 'media']]; $request = new Request('GET', '/?alt=media'); - $body = Stream::factory('thisisnotvalidjson'); - $response = new Response(300, [], $body); + $body = Psr7\stream_for('thisisnotvalidjson'); + $response = new Response(400, [], $body); $http = $this->getMockBuilder("GuzzleHttp\Client") ->disableOriginalConstructor() @@ -254,12 +231,13 @@ public function testNoExpectedClassForAltMediaWithHttpFail() $http->expects($this->once()) ->method('send') ->will($this->returnValue($response)); - $http->expects($this->any()) + + if ($this->isGuzzle5()) { + $http->expects($this->once()) ->method('createRequest') - ->will($this->returnValue(new Request('GET', '/?alt=media'))); - $http->expects($this->once()) - ->method('send') - ->will($this->returnValue($response)); + ->will($this->returnValue(new GuzzleHttp\Message\Request('GET', '/?alt=media'))); + } + $client = new Google_Client(); $client->setHttpClient($http); $service = new Test_Google_Service($client); diff --git a/tests/Google/Task/RunnerTest.php b/tests/Google/Task/RunnerTest.php index 399e1da92..9f03d1180 100644 --- a/tests/Google/Task/RunnerTest.php +++ b/tests/Google/Task/RunnerTest.php @@ -15,11 +15,11 @@ * limitations under the License. */ -use GuzzleHttp\Message\Request; -use GuzzleHttp\Message\Response; -use GuzzleHttp\Stream\Stream; +use GuzzleHttp\Psr7; +use GuzzleHttp\Psr7\Request; +use GuzzleHttp\Psr7\Response; -class Google_Task_RunnerTest extends PHPUnit_Framework_TestCase +class Google_Task_RunnerTest extends BaseTest { private $client; @@ -75,7 +75,7 @@ public function testOneRestRetryWithSuccess($errorCode, $errorBody = '{}') ->setNextResponse(200, '{"success": true}') ->makeRequest(); - $this->assertEquals(array("success" => true), $result); + $this->assertEquals('{"success": true}', (string) $result->getBody()); } /** @@ -90,7 +90,7 @@ public function testMultipleRestRetriesWithSuccess( ->setNextResponse(200, '{"success": true}') ->makeRequest(); - $this->assertEquals(array("success" => true), $result); + $this->assertEquals('{"success": true}', (string) $result->getBody()); } /** @@ -118,7 +118,7 @@ public function testCustomRestRetryMapAddsNewHandlers() ->setNextResponse(200, '{"success": true}') ->makeRequest(); - $this->assertEquals(array("success" => true), $result); + $this->assertEquals('{"success": true}', (string) $result->getBody()); } /** @@ -196,7 +196,7 @@ public function testOneCurlRetryWithSuccess($errorCode, $errorMessage = '') ->setNextResponse(200, '{"success": true}') ->makeRequest(); - $this->assertEquals(array("success" => true), $result); + $this->assertEquals('{"success": true}', (string) $result->getBody()); } /** @@ -212,7 +212,7 @@ public function testMultipleCurlRetriesWithSuccess( ->setNextResponse(200, '{"success": true}') ->makeRequest(); - $this->assertEquals(array("success" => true), $result); + $this->assertEquals('{"success": true}', (string) $result->getBody()); } /** @@ -244,7 +244,7 @@ public function testCustomCurlRetryMapAddsNewHandlers() ->setNextResponse(200, '{"success": true}') ->makeRequest(); - $this->assertEquals(array("success" => true), $result); + $this->assertEquals('{"success": true}', (string) $result->getBody()); } /** @@ -627,12 +627,17 @@ private function makeRequest() { $request = new Request('GET', '/test'); $http = $this->getMock('GuzzleHttp\ClientInterface'); - $http->expects($this->exactly($this->mockedCallsCount)) ->method('send') ->will($this->returnCallback(array($this, 'getNextMockedCall'))); - return Google_Http_REST::execute($http, $request, $this->retryConfig, $this->retryMap); + if ($this->isGuzzle5()) { + $http->expects($this->exactly($this->mockedCallsCount)) + ->method('createRequest') + ->will($this->returnValue(new GuzzleHttp\Message\Request('GET', '/test'))); + } + + return Google_Http_REST::execute($http, $request, '', $this->retryConfig, $this->retryMap); } /** @@ -650,7 +655,7 @@ public function getNextMockedCall($request) throw $current; } - $stream = Stream::factory($current['body']); + $stream = Psr7\stream_for($current['body']); $response = new Response($current['code'], $current['headers'], $stream); return $response;