From f872fea738b8d2deea0a41f070929a67797e5349 Mon Sep 17 00:00:00 2001 From: Brent Shaffer Date: Fri, 13 May 2016 13:44:11 -0700 Subject: [PATCH] adds PSR-6 and removes custom caching (#943) * adds PSR-6 and removes custom caching * Adds callback * documents these changes in the README --- README.md | 33 ++- composer.json | 13 +- src/Google/AccessToken/Verify.php | 45 +-- src/Google/AuthHandler/AuthHandlerFactory.php | 6 +- src/Google/AuthHandler/Guzzle5AuthHandler.php | 18 +- src/Google/AuthHandler/Guzzle6AuthHandler.php | 18 +- src/Google/Cache/Apc.php | 125 --------- src/Google/Cache/Exception.php | 20 -- src/Google/Cache/File.php | 259 ------------------ src/Google/Cache/Memcache.php | 184 ------------- src/Google/Cache/Memory.php | 51 ---- src/Google/Cache/Null.php | 50 ---- src/Google/Client.php | 49 +++- tests/BaseTest.php | 13 +- tests/Google/AccessToken/VerifyTest.php | 2 +- tests/Google/CacheTest.php | 190 ------------- tests/Google/ClientTest.php | 48 +++- tests/clearToken.php | 5 +- tests/examples/batchTest.php | 2 +- tests/examples/serviceAccountTest.php | 2 +- 20 files changed, 190 insertions(+), 943 deletions(-) delete mode 100644 src/Google/Cache/Apc.php delete mode 100644 src/Google/Cache/Exception.php delete mode 100644 src/Google/Cache/File.php delete mode 100644 src/Google/Cache/Memcache.php delete mode 100644 src/Google/Cache/Memory.php delete mode 100644 src/Google/Cache/Null.php delete mode 100644 tests/Google/CacheTest.php diff --git a/README.md b/README.md index ff747d4bf..ff27d02ed 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ $ php -S localhost:8000 -t examples/ And then browsing to the host and port you specified (in the above example, `http://localhost:8000`). -```PHP +```php // include your composer dependencies require_once 'vendor/autoload.php'; @@ -79,6 +79,33 @@ foreach ($results as $item) { } ``` +### Caching ### + +It is recommended to use another caching library to improve performance. This can be done by passing a [PSR-6](http://www.php-fig.org/psr/psr-6/) compatible library to the client: + +```php +$cache = new Stash\Pool(new Stash\Driver\FileSystem); +$client->setCache($cache); +``` + +In this example we use [StashPHP](http://www.stashphp.com/). Add this to your project with composer: + +``` +composer require tedivm/stash +``` + +### Updating Tokens ### + +When using [Refresh Tokens](https://developers.google.com/identity/protocols/OAuth2InstalledApp#refresh) or [Service Account Credentials](https://developers.google.com/identity/protocols/OAuth2ServiceAccount#overview), it may be useful to perform some action when a new access token is granted. To do this, pass a callable to the `setTokenCallback` method on the client: + +```php +$logger = new Monolog\Logger; +$tokenCallback = function ($cacheKey, $accessToken) use ($logger) { + $logger->debug(sprintf('new access token received at cache key %s', $cacheKey)); +}; +$client->setTokenCallback($tokenCallback); +``` + ### Service Specific Examples ### YouTube: https://github.com/youtube/api-samples/tree/master/php @@ -87,9 +114,9 @@ YouTube: https://github.com/youtube/api-samples/tree/master/php ### What do I do if something isn't working? ### -For support with the library the best place to ask is via the google-api-php-client tag on StackOverflow: http://stackoverflow.com/questions/tagged/google-api-php-client +For support with the library the best place to ask is via the google-api-php-client tag on StackOverflow: http://stackoverflow.com/questions/tagged/google-api-php-client -If there is a specific bug with the library, please file a issue in the Github issues tracker, including a (minimal) example of the failing code and any specific errors retrieved. Feature requests can also be filed, as long as they are core library requests, and not-API specific: for those, refer to the documentation for the individual APIs for the best place to file requests. Please try to provide a clear statement of the problem that the feature would address. +If there is a specific bug with the library, please [file a issue](/Google/google-api-php-client/issues) in the Github issues tracker, including an example of the failing code and any specific errors retrieved. Feature requests can also be filed, as long as they are core library requests, and not-API specific: for those, refer to the documentation for the individual APIs for the best place to file requests. Please try to provide a clear statement of the problem that the feature would address. ### How do I contribute? ### diff --git a/composer.json b/composer.json index 000b3f358..409a48d23 100644 --- a/composer.json +++ b/composer.json @@ -7,20 +7,23 @@ "license": "Apache-2.0", "require": { "php": ">=5.4", - "google/auth": "0.7", + "google/auth": "0.8", "google/apiclient-services": "*@dev", "firebase/php-jwt": "~2.0|~3.0", "monolog/monolog": "^1.17", "phpseclib/phpseclib": "~2.0", "guzzlehttp/guzzle": "~5.2|~6.0", - "guzzlehttp/psr7": "1.2.*", - "psr/http-message": "1.0.*" + "guzzlehttp/psr7": "^1.2" }, "require-dev": { "phpunit/phpunit": "~4", "squizlabs/php_codesniffer": "~2.3", - "symfony/dom-crawler": "~2.0", - "symfony/css-selector": "~2.0" + "symfony/dom-crawler": "~2.1", + "symfony/css-selector": "~2.1", + "tedivm/stash": "^0.14.1" + }, + "suggest": { + "tedivm/stash": "For caching certs and tokens (using Google_Client::setCache)" }, "autoload": { "psr-0": { diff --git a/src/Google/AccessToken/Verify.php b/src/Google/AccessToken/Verify.php index 3e35f2f04..2e8d4770d 100644 --- a/src/Google/AccessToken/Verify.php +++ b/src/Google/AccessToken/Verify.php @@ -17,11 +17,13 @@ */ use Firebase\JWT\ExpiredException as ExpiredExceptionV3; -use Google\Auth\CacheInterface; use GuzzleHttp\Client; use GuzzleHttp\ClientInterface; use phpseclib\Crypt\RSA; use phpseclib\Math\BigInteger; +use Psr\Cache\CacheItemPoolInterface; +use Stash\Driver\FileSystem; +use Stash\Pool; /** * Wrapper around Google Access Tokens which provides convenience functions @@ -39,7 +41,7 @@ class Google_AccessToken_Verify private $http; /** - * @var Google\Auth\CacheInterface cache class + * @var Psr\Cache\CacheItemPoolInterface cache class */ private $cache; @@ -47,12 +49,16 @@ class Google_AccessToken_Verify * Instantiates the class, but does not initiate the login flow, leaving it * to the discretion of the caller. */ - public function __construct(ClientInterface $http = null, CacheInterface $cache = null) + public function __construct(ClientInterface $http = null, CacheItemPoolInterface $cache = null) { if (is_null($http)) { $http = new Client(); } + if (is_null($cache) && class_exists('Stash\Pool')) { + $cache = new Pool(new FileSystem); + } + $this->http = $http; $this->cache = $cache; $this->jwt = $this->getJwtService(); @@ -120,20 +126,9 @@ public function verifyIdToken($idToken, $audience = null) private function getCache() { - if (!$this->cache) { - $this->cache = $this->createDefaultCache(); - } - return $this->cache; } - private function createDefaultCache() - { - return new Google_Cache_File( - sys_get_temp_dir().'/google-api-php-client' - ); - } - /** * Retrieve and cache a certificates file. * @@ -174,14 +169,22 @@ private function retrieveCertsFromLocation($url) // are PEM encoded certificates. private function getFederatedSignOnCerts() { - $cache = $this->getCache(); + $certs = null; + if ($cache = $this->getCache()) { + $cacheItem = $cache->getItem('federated_signon_certs_v3', 3600); + $certs = $cacheItem->get(); + } - if (!$certs = $cache->get('federated_signon_certs_v3', 3600)) { + + if (!$certs) { $certs = $this->retrieveCertsFromLocation( self::FEDERATED_SIGNON_CERT_URL ); - $cache->set('federated_signon_certs_v3', $certs); + if ($cache) { + $cacheItem->set($certs); + $cache->save($cacheItem); + } } if (!isset($certs['keys'])) { @@ -200,9 +203,11 @@ private function getJwtService() $jwtClass = 'Firebase\JWT\JWT'; } - // adds 1 second to JWT leeway - // @see https://github.com/google/google-api-php-client/issues/827 - $jwtClass::$leeway = 1; + if (property_exists($jwtClass, 'leeway')) { + // adds 1 second to JWT leeway + // @see https://github.com/google/google-api-php-client/issues/827 + $jwtClass::$leeway = 1; + } return new $jwtClass; } diff --git a/src/Google/AuthHandler/AuthHandlerFactory.php b/src/Google/AuthHandler/AuthHandlerFactory.php index 4f2155a63..f1a3229ae 100644 --- a/src/Google/AuthHandler/AuthHandlerFactory.php +++ b/src/Google/AuthHandler/AuthHandlerFactory.php @@ -26,15 +26,15 @@ class Google_AuthHandler_AuthHandlerFactory * @return Google_AuthHandler_Guzzle5AuthHandler|Google_AuthHandler_Guzzle6AuthHandler * @throws Exception */ - public static function build($cache = null) + public static function build($cache = null, array $cacheConfig = []) { $version = ClientInterface::VERSION; switch ($version[0]) { case '5': - return new Google_AuthHandler_Guzzle5AuthHandler($cache); + return new Google_AuthHandler_Guzzle5AuthHandler($cache, $cacheConfig); case '6': - return new Google_AuthHandler_Guzzle6AuthHandler($cache); + return new Google_AuthHandler_Guzzle6AuthHandler($cache, $cacheConfig); default: throw new Exception('Version not supported'); } diff --git a/src/Google/AuthHandler/Guzzle5AuthHandler.php b/src/Google/AuthHandler/Guzzle5AuthHandler.php index 2d4d620bf..d3a4b0379 100644 --- a/src/Google/AuthHandler/Guzzle5AuthHandler.php +++ b/src/Google/AuthHandler/Guzzle5AuthHandler.php @@ -1,6 +1,5 @@ cache = $cache; + $this->cacheConfig = $cacheConfig; } - public function attachCredentials(ClientInterface $http, CredentialsLoader $credentials) - { + public function attachCredentials( + ClientInterface $http, + CredentialsLoader $credentials, + callable $tokenCallback = 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. @@ -30,9 +35,10 @@ public function attachCredentials(ClientInterface $http, CredentialsLoader $cred $authHttpHandler = HttpHandlerFactory::build($authHttp); $subscriber = new AuthTokenSubscriber( $credentials, - [], + $this->cacheConfig, $this->cache, - $authHttpHandler + $authHttpHandler, + $tokenCallback ); $http->setDefaultOption('auth', 'google_auth'); diff --git a/src/Google/AuthHandler/Guzzle6AuthHandler.php b/src/Google/AuthHandler/Guzzle6AuthHandler.php index 696723b5d..fb743e601 100644 --- a/src/Google/AuthHandler/Guzzle6AuthHandler.php +++ b/src/Google/AuthHandler/Guzzle6AuthHandler.php @@ -1,6 +1,5 @@ cache = $cache; + $this->cacheConfig = $cacheConfig; } - public function attachCredentials(ClientInterface $http, CredentialsLoader $credentials) - { + public function attachCredentials( + ClientInterface $http, + CredentialsLoader $credentials, + callable $tokenCallback = 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. @@ -30,9 +35,10 @@ public function attachCredentials(ClientInterface $http, CredentialsLoader $cred $authHttpHandler = HttpHandlerFactory::build($authHttp); $middleware = new AuthTokenMiddleware( $credentials, - [], + $this->cacheConfig, $this->cache, - $authHttpHandler + $authHttpHandler, + $tokenCallback ); $config = $http->getConfig(); diff --git a/src/Google/Cache/Apc.php b/src/Google/Cache/Apc.php deleted file mode 100644 index f3bb1afe3..000000000 --- a/src/Google/Cache/Apc.php +++ /dev/null @@ -1,125 +0,0 @@ - - */ -class Google_Cache_Apc implements CacheInterface -{ - /** - * @var Psr\Log\LoggerInterface logger - */ - private $logger; - - public function __construct(LoggerInterface $logger = null) - { - $this->logger = $logger; - - if (! function_exists('apc_add') ) { - $error = "Apc functions not available"; - - $this->log('error', $error); - throw new Google_Cache_Exception($error); - } - } - - /** - * @inheritDoc - */ - public function get($key, $expiration = false) - { - $ret = apc_fetch($key); - if ($ret === false) { - $this->log( - 'debug', - 'APC cache miss', - array('key' => $key) - ); - return false; - } - if (is_numeric($expiration) && (time() - $ret['time'] > $expiration)) { - $this->log( - 'debug', - 'APC cache miss (expired)', - array('key' => $key, 'var' => $ret) - ); - $this->delete($key); - return false; - } - - $this->log( - 'debug', - 'APC cache hit', - array('key' => $key, 'var' => $ret) - ); - - return $ret['data']; - } - - /** - * @inheritDoc - */ - public function set($key, $value) - { - $var = array('time' => time(), 'data' => $value); - $rc = apc_store($key, $var); - - if ($rc == false) { - $this->log( - 'error', - 'APC cache set failed', - array('key' => $key, 'var' => $var) - ); - throw new Google_Cache_Exception("Couldn't store data"); - } - - $this->log( - 'debug', - 'APC cache set', - array('key' => $key, 'var' => $var) - ); - } - - /** - * @inheritDoc - * @param String $key - */ - public function delete($key) - { - $this->log( - 'debug', - 'APC cache delete', - array('key' => $key) - ); - apc_delete($key); - } - - private function log($level, $message, $context = array()) - { - if ($this->logger) { - $this->logger->log($level, $message, $context); - } - } -} diff --git a/src/Google/Cache/Exception.php b/src/Google/Cache/Exception.php deleted file mode 100644 index 6e4102e43..000000000 --- a/src/Google/Cache/Exception.php +++ /dev/null @@ -1,20 +0,0 @@ - - */ -class Google_Cache_File implements CacheInterface -{ - const MAX_LOCK_RETRIES = 10; - private $path; - private $fh; - - /** - * @var use Psr\Log\LoggerInterface logger - */ - private $logger; - - public function __construct($path, LoggerInterface $logger = null) - { - $this->path = $path; - $this->logger = $logger; - } - - public function get($key, $expiration = false) - { - $storageFile = $this->getCacheFile($key); - $data = false; - - if (!file_exists($storageFile)) { - $this->log( - 'debug', - 'File cache miss', - array('key' => $key, 'file' => $storageFile) - ); - return false; - } - - if ($expiration) { - $mtime = filemtime($storageFile); - if ((time() - $mtime) >= $expiration) { - $this->log( - 'debug', - 'File cache miss (expired)', - array('key' => $key, 'file' => $storageFile) - ); - $this->delete($key); - return false; - } - } - - if ($this->acquireReadLock($storageFile)) { - if (filesize($storageFile) > 0) { - $data = fread($this->fh, filesize($storageFile)); - $data = unserialize($data); - } else { - $this->log( - 'debug', - 'Cache file was empty', - array('file' => $storageFile) - ); - } - $this->unlock(); - } - - $this->log( - 'debug', - 'File cache hit', - array('key' => $key, 'file' => $storageFile, 'var' => $data) - ); - - return $data; - } - - public function set($key, $value) - { - $storageFile = $this->getWriteableCacheFile($key); - if ($this->acquireWriteLock($storageFile)) { - // We serialize the whole request object, since we don't only want the - // responseContent but also the postBody used, headers, size, etc. - $data = serialize($value); - fwrite($this->fh, $data); - $this->unlock(); - - $this->log( - 'debug', - 'File cache set', - array('key' => $key, 'file' => $storageFile, 'var' => $value) - ); - } else { - $this->log( - 'notice', - 'File cache set failed', - array('key' => $key, 'file' => $storageFile) - ); - } - } - - public function delete($key) - { - $file = $this->getCacheFile($key); - if (file_exists($file) && !unlink($file)) { - $this->log( - 'error', - 'File cache delete failed', - array('key' => $key, 'file' => $file) - ); - throw new Google_Cache_Exception("Cache file could not be deleted"); - } - - $this->log( - 'debug', - 'File cache delete', - array('key' => $key, 'file' => $file) - ); - } - - private function getWriteableCacheFile($file) - { - return $this->getCacheFile($file, true); - } - - private function getCacheFile($file, $forWrite = false) - { - return $this->getCacheDir($file, $forWrite) . '/' . md5($file); - } - - private function getCacheDir($file, $forWrite) - { - // use the first 2 characters of the hash as a directory prefix - // this should prevent slowdowns due to huge directory listings - // and thus give some basic amount of scalability - $fileHash = substr(md5($file), 0, 2); - $processUser = null; - if (function_exists('posix_geteuid')) { - $processUser = posix_getpwuid(posix_geteuid())['name']; - } elseif (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { - $processUser = get_current_user(); - } - if (empty($processUser)) { - $this->log( - 'notice', - 'Process User get failed' - ); - } - $userHash = md5($processUser); - $dirHash = $userHash . DIRECTORY_SEPARATOR . $fileHash; - - // trim the directory separator from the path to prevent double separators - $rootCacheDir = rtrim($this->path, DIRECTORY_SEPARATOR); - $storageDir = $rootCacheDir . DIRECTORY_SEPARATOR . $dirHash; - - if ($forWrite && !is_dir($storageDir)) { - // create root dir - if (!is_dir($rootCacheDir)) { - if (!mkdir($rootCacheDir, 0777, true)) { - $this->log( - 'error', - 'File cache creation failed', - array('dir' => $rootCacheDir) - ); - throw new Google_Cache_Exception("Could not create cache directory: $rootCacheDir"); - } - } - - // create dir for file - if (!mkdir($storageDir, 0700, true)) { - $this->log( - 'error', - 'File cache creation failed', - array('dir' => $storageDir) - ); - throw new Google_Cache_Exception("Could not create cache directory: $storageDir"); - } - } - - return $storageDir; - } - - private function acquireReadLock($storageFile) - { - return $this->acquireLock(LOCK_SH, $storageFile); - } - - private function acquireWriteLock($storageFile) - { - $rc = $this->acquireLock(LOCK_EX, $storageFile); - if (!$rc) { - $this->log( - 'notice', - 'File cache write lock failed', - array('file' => $storageFile) - ); - $this->delete($storageFile); - } - return $rc; - } - - private function acquireLock($type, $storageFile) - { - $mode = $type == LOCK_EX ? "w" : "r"; - $this->fh = fopen($storageFile, $mode); - if (!$this->fh) { - $this->log( - 'error', - 'Failed to open file during lock acquisition', - array('file' => $storageFile) - ); - return false; - } - if ($type == LOCK_EX) { - chmod($storageFile, 0600); - } - $count = 0; - while (!flock($this->fh, $type | LOCK_NB)) { - // Sleep for 10ms. - usleep(10000); - if (++$count < self::MAX_LOCK_RETRIES) { - return false; - } - } - return true; - } - - public function unlock() - { - if ($this->fh) { - flock($this->fh, LOCK_UN); - } - } - - private function log($level, $message, $context = array()) - { - if ($this->logger) { - $this->logger->log($level, $message, $context); - } - } -} diff --git a/src/Google/Cache/Memcache.php b/src/Google/Cache/Memcache.php deleted file mode 100644 index ba4c6514b..000000000 --- a/src/Google/Cache/Memcache.php +++ /dev/null @@ -1,184 +0,0 @@ - - */ -class Google_Cache_Memcache implements CacheInterface -{ - private $connection = false; - private $mc = false; - private $host; - private $port; - - /** - * @var use Psr\Log\LoggerInterface logger - */ - private $logger; - - public function __construct($host = null, $port = null, LoggerInterface $logger = null) - { - $this->logger = $logger; - - if (!function_exists('memcache_connect') && !class_exists("Memcached")) { - $error = "Memcache functions not available"; - - $this->log('error', $error); - throw new Google_Cache_Exception($error); - } - - $this->host = $host; - $this->port = $port; - } - - /** - * @inheritDoc - */ - public function get($key, $expiration = false) - { - $this->connect(); - $ret = false; - if ($this->mc) { - $ret = $this->mc->get($key); - } else { - $ret = memcache_get($this->connection, $key); - } - if ($ret === false) { - $this->log( - 'debug', - 'Memcache cache miss', - array('key' => $key) - ); - return false; - } - if (is_numeric($expiration) && (time() - $ret['time'] > $expiration)) { - $this->log( - 'debug', - 'Memcache cache miss (expired)', - array('key' => $key, 'var' => $ret) - ); - $this->delete($key); - return false; - } - - $this->log( - 'debug', - 'Memcache cache hit', - array('key' => $key, 'var' => $ret) - ); - - return $ret['data']; - } - - /** - * @inheritDoc - * @param string $key - * @param string $value - * @throws Google_Cache_Exception - */ - public function set($key, $value) - { - $this->connect(); - // we store it with the cache_time default expiration so objects will at - // least get cleaned eventually. - $data = array('time' => time(), 'data' => $value); - $rc = false; - if ($this->mc) { - $rc = $this->mc->set($key, $data); - } else { - $rc = memcache_set($this->connection, $key, $data, false); - } - if ($rc == false) { - $this->log( - 'error', - 'Memcache cache set failed', - array('key' => $key, 'var' => $data) - ); - - throw new Google_Cache_Exception("Couldn't store data in cache"); - } - - $this->log( - 'debug', - 'Memcache cache set', - array('key' => $key, 'var' => $data) - ); - } - - /** - * @inheritDoc - * @param String $key - */ - public function delete($key) - { - $this->connect(); - if ($this->mc) { - $this->mc->delete($key, 0); - } else { - memcache_delete($this->connection, $key, 0); - } - - $this->log( - 'debug', - 'Memcache cache delete', - array('key' => $key) - ); - } - - /** - * Lazy initialiser for memcache connection. Uses pconnect for to take - * advantage of the persistence pool where possible. - */ - private function connect() - { - if ($this->connection) { - return; - } - - if (class_exists("Memcached")) { - $this->mc = new Memcached(); - $this->mc->addServer($this->host, $this->port); - $this->connection = true; - } else { - $this->connection = memcache_pconnect($this->host, $this->port); - } - - if (! $this->connection) { - $error = "Couldn't connect to memcache server"; - - $this->log('error', $error); - throw new Google_Cache_Exception($error); - } - } - - private function log($level, $message, $context = array()) - { - if ($this->logger) { - $this->logger->log($level, $message, $context); - } - } -} diff --git a/src/Google/Cache/Memory.php b/src/Google/Cache/Memory.php deleted file mode 100644 index 97e10696b..000000000 --- a/src/Google/Cache/Memory.php +++ /dev/null @@ -1,51 +0,0 @@ -cache[$key]) ? $this->cache[$key] : false; - } - - /** - * @inheritDoc - */ - public function set($key, $value) - { - $this->cache[$key] = $value; - } - - /** - * @inheritDoc - * @param String $key - */ - public function delete($key) - { - unset($this->cache[$key]); - } -} diff --git a/src/Google/Cache/Null.php b/src/Google/Cache/Null.php deleted file mode 100644 index 723df9f93..000000000 --- a/src/Google/Cache/Null.php +++ /dev/null @@ -1,50 +0,0 @@ - array(), + + // cache config for downstream auth caching + 'cache_config' => [], + + // function to be called when an access token is fetched + // follows the signature function ($cacheKey, $accessToken) + 'token_callback' => null, ], $config ); @@ -359,7 +366,8 @@ public function authorize(ClientInterface $http = null, ClientInterface $authHtt $authHandler = $this->getAuthHandler(); if ($credentials) { - $http = $authHandler->attachCredentials($http, $credentials); + $callback = $this->config['token_callback']; + $http = $authHandler->attachCredentials($http, $credentials, $callback); } elseif ($token) { $http = $authHandler->attachToken($http, $token, (array) $scopes); } elseif ($key = $this->config['developer_key']) { @@ -604,6 +612,7 @@ public function setHostedDomain($hd) { $this->config['hd'] = $hd; } + /** * Set the prompt hint. Valid values are none, consent and select_account. * If no value is specified and the user has not previously authorized @@ -614,6 +623,7 @@ public function setPrompt($prompt) { $this->config['prompt'] = $prompt; } + /** * openid.realm is a parameter from the OpenID 2.0 protocol, not from OAuth * 2.0. It is used in OpenID 2.0 requests to signify the URL-space for which @@ -624,6 +634,7 @@ public function setOpenidRealm($realm) { $this->config['openid.realm'] = $realm; } + /** * If this is provided with the value true, and the authorization request is * granted, the authorization will include any previous authorizations @@ -635,6 +646,15 @@ public function setIncludeGrantedScopes($include) $this->config['include_granted_scopes'] = $include; } + /** + * sets function to be called when an access token is fetched + * @param callable $tokenCallback - function ($cacheKey, $accessToken) + */ + public function setTokenCallback(callable $tokenCallback) + { + $this->config['token_callback'] = $tokenCallback; + } + /** * Revoke an OAuth2 access token or refresh token. This method will revoke the current access * token, if a token isn't provided. @@ -918,25 +938,29 @@ protected function createOAuth2Service() /** * Set the Cache object - * @param Google\Auth\CacheInterface $cache + * @param Psr\Cache\CacheItemPoolInterface $cache */ - public function setCache(CacheInterface $cache) + public function setCache(CacheItemPoolInterface $cache) { $this->cache = $cache; } /** - * @return Google\Auth\CacheInterface Cache implementation + * @return Psr\Cache\CacheItemPoolInterface Cache implementation */ public function getCache() { - if (is_null($this->cache)) { - $this->cache = new Google_Cache_Memory(); - } - return $this->cache; } + /** + * @return Google\Auth\CacheInterface Cache implementation + */ + public function setCacheConfig(array $cacheConfig) + { + $this->config['cache_config'] = $cacheConfig; + } + /** * Set the Logger object * @param Psr\Log\LoggerInterface $logger @@ -1050,7 +1074,10 @@ protected function getAuthHandler() // sessions. // // @see https://github.com/google/google-api-php-client/issues/821 - return Google_AuthHandler_AuthHandlerFactory::build($this->getCache()); + return Google_AuthHandler_AuthHandlerFactory::build( + $this->getCache(), + $this->config['cache_config'] + ); } private function createUserRefreshCredentials($scope, $refreshToken) diff --git a/tests/BaseTest.php b/tests/BaseTest.php index 70a45726b..905e8b7c3 100644 --- a/tests/BaseTest.php +++ b/tests/BaseTest.php @@ -17,6 +17,8 @@ use GuzzleHttp\ClientInterface; use Symfony\Component\DomCrawler\Crawler; +use Stash\Driver\FileSystem; +use Stash\Pool; class BaseTest extends PHPUnit_Framework_TestCase { @@ -35,9 +37,10 @@ public function getClient() return $this->client; } - public function getCache() + public function getCache($path = null) { - return new Google_Cache_File(sys_get_temp_dir().'/google-api-php-client-tests'); + $path = $path ?: sys_get_temp_dir().'/google-api-php-client-tests'; + return new Pool(new FileSystem(['path' => $path])); } private function createClient() @@ -87,12 +90,14 @@ public function checkToken() { $client = $this->getClient(); $cache = $client->getCache(); + $cacheItem = $cache->getItem('access_token'); - if (!$token = $cache->get('access_token')) { + if (!$token = $cacheItem->get()) { if (!$token = $this->tryToGetAnAccessToken($client)) { return $this->markTestSkipped("Test requires access token"); } - $cache->set('access_token', $token); + $cacheItem->set($token); + $cache->save($cacheItem); } $client->setAccessToken($token); diff --git a/tests/Google/AccessToken/VerifyTest.php b/tests/Google/AccessToken/VerifyTest.php index cce448144..a6f4ef01d 100644 --- a/tests/Google/AccessToken/VerifyTest.php +++ b/tests/Google/AccessToken/VerifyTest.php @@ -98,7 +98,7 @@ public function testRetrieveCertsFromLocation() $certs = $method->invoke($verify, Google_AccessToken_Verify::FEDERATED_SIGNON_CERT_URL); $this->assertArrayHasKey('keys', $certs); - $this->assertEquals(2, count($certs['keys'])); + $this->assertGreaterThan(1, count($certs['keys'])); $this->assertArrayHasKey('alg', $certs['keys'][0]); $this->assertEquals('RS256', $certs['keys'][0]['alg']); } diff --git a/tests/Google/CacheTest.php b/tests/Google/CacheTest.php deleted file mode 100644 index dd684e9ba..000000000 --- a/tests/Google/CacheTest.php +++ /dev/null @@ -1,190 +0,0 @@ -set('foo', 'bar'); - $this->assertEquals($cache->get('foo'), 'bar'); - - $this->getSetDelete($cache); - } - - public function testFileWithTrailingSlash() - { - $dir = sys_get_temp_dir() . '/google-api-php-client/tests/'; - $cache = new Google_Cache_File($dir); - $cache->set('foo', 'bar'); - $this->assertEquals($cache->get('foo'), 'bar'); - - $this->getSetDelete($cache); - } - - public function testBaseCacheDirectoryPermissions() - { - $dir = sys_get_temp_dir() . '/google-api-php-client/tests/' . rand(); - $cache = new Google_Cache_File($dir); - $cache->set('foo', 'bar'); - - $method = new ReflectionMethod($cache, 'getWriteableCacheFile'); - $method->setAccessible(true); - $filename = $method->invoke($cache, 'foo'); - $stat = stat($dir); - - $this->assertEquals(0777 & ~umask(), $stat['mode'] & 0777); - } - - public function testCacheDirectoryPermissions() - { - $dir = sys_get_temp_dir() . '/google-api-php-client/tests/' . rand(); - $cache = new Google_Cache_File($dir); - $cache->set('foo', 'bar'); - - $method = new ReflectionMethod($cache, 'getWriteableCacheFile'); - $method->setAccessible(true); - $filename = $method->invoke($cache, 'foo'); - $stat = stat(dirname($filename)); - - $this->assertEquals(0700, $stat['mode'] & 0777); - } - - public function testCacheFilePermissions() - { - $dir = sys_get_temp_dir() . '/google-api-php-client/tests/'; - $cache = new Google_Cache_File($dir); - $cache->set('foo', 'bar'); - - $method = new ReflectionMethod($cache, 'getWriteableCacheFile'); - $method->setAccessible(true); - $filename = $method->invoke($cache, 'foo'); - $stat = stat($filename); - - $this->assertEquals(0600, $stat['mode'] & 0777); - } - - public function testNull() - { - $cache = new Google_Cache_Null(); - $cache->set('foo', 'bar'); - $cache->delete('foo'); - $this->assertEquals(false, $cache->get('foo')); - - $cache->set('foo.1', 'bar.1'); - $this->assertEquals($cache->get('foo.1'), false); - - $cache->set('foo', 'baz'); - $this->assertEquals($cache->get('foo'), false); - - $cache->set('foo', null); - $cache->delete('foo'); - $this->assertEquals($cache->get('foo'), false); - } - - public function testMemory() - { - $cache = new Google_Cache_Memory(); - $cache->set('foo', 'bar'); - $cache->delete('foo'); - $this->assertEquals(false, $cache->get('foo')); - - $cache->set('foo.1', 'bar.1'); - $this->assertEquals($cache->get('foo.1'), 'bar.1'); - - $cache->set('foo', 'baz'); - $this->assertEquals($cache->get('foo'), 'baz'); - - $cache->delete('foo'); - $this->assertEquals($cache->get('foo'), false); - } - - /** - * @requires extension Memcache - */ - public function testMemcache() - { - $host = getenv('MEMCACHE_HOST') ? getenv('MEMCACHE_HOST') : null; - $port = getenv('MEMCACHE_PORT') ? getenv('MEMCACHE_PORT') : null; - if (!($host && $port)) { - $this->markTestSkipped('Test requires memcache host and port specified'); - } - - $cache = new Google_Cache_Memcache($host, $port); - - $this->getSetDelete($cache); - } - - /** - * @requires extension APC - */ - public function testAPC() - { - if (!ini_get('apc.enable_cli')) { - $this->markTestSkipped('Test requires APC enabled for CLI'); - } - $cache = new Google_Cache_Apc(); - - $this->getSetDelete($cache); - } - - public function getSetDelete($cache) - { - $cache->set('foo', 'bar'); - $cache->delete('foo'); - $this->assertEquals(false, $cache->get('foo')); - - $cache->set('foo.1', 'bar.1'); - $cache->delete('foo.1'); - $this->assertEquals($cache->get('foo.1'), false); - - $cache->set('foo', 'baz'); - $cache->delete('foo'); - $this->assertEquals($cache->get('foo'), false); - - $cache->set('foo', null); - $cache->delete('foo'); - $this->assertEquals($cache->get('foo'), false); - - $obj = new stdClass(); - $obj->foo = 'bar'; - $cache->set('foo', $obj); - $cache->delete('foo'); - $this->assertEquals($cache->get('foo'), false); - - $cache->set('foo.1', 'bar.1'); - $this->assertEquals($cache->get('foo.1'), 'bar.1'); - - $cache->set('foo', 'baz'); - $this->assertEquals($cache->get('foo'), 'baz'); - - $cache->set('foo', null); - $this->assertEquals($cache->get('foo'), null); - - $cache->set('1/2/3', 'bar'); - $this->assertEquals($cache->get('1/2/3'), 'bar'); - - $obj = new stdClass(); - $obj->foo = 'bar'; - $cache->set('foo', $obj); - $this->assertEquals($cache->get('foo'), $obj); - } -} diff --git a/tests/Google/ClientTest.php b/tests/Google/ClientTest.php index 8369b4eb0..576daf432 100644 --- a/tests/Google/ClientTest.php +++ b/tests/Google/ClientTest.php @@ -254,7 +254,7 @@ public function testSettersGetters() $client->setRedirectUri('localhost'); $client->setConfig('application_name', 'me'); - $client->setCache(new Google_Cache_Null()); + $client->setCache($this->getMock('Psr\Cache\CacheItemPoolInterface')); $this->assertEquals('object', gettype($client->getCache())); try { @@ -497,4 +497,50 @@ public function testBadSubjectThrowsException() $this->assertContains('Invalid impersonation prn email address', (string) $response->getBody()); } } + + public function testTokenCallback() + { + $this->checkToken(); + + $client = $this->getClient(); + $accessToken = $client->getAccessToken(); + + if (!isset($accessToken['refresh_token'])) { + $this->markTestSkipped('Refresh Token required'); + } + + // make the auth library think the token is expired + $accessToken['expires_in'] = 0; + $cache = $client->getCache(); + $path = sys_get_temp_dir().'/google-api-php-client-tests-'.time(); + $client->setCache($this->getCache($path)); + $client->setAccessToken($accessToken); + + // create the callback function + $phpunit = $this; + $called = false; + $callback = function ($key, $value) use ($client, $cache, $phpunit, &$called) { + // go back to the previous cache + $client->setCache($cache); + + // assert the expected keys and values + $phpunit->assertContains('https---www.googleapis.com-auth-', $key); + $phpunit->assertNotNull($value); + $called = true; + }; + + // set the token callback to the client + $client->setTokenCallback($callback); + + // make a silly request to obtain a new token + $http = $client->authorize(); + $http->get('https://www.googleapis.com/books/v1/volumes?q=Voltaire'); + $newToken = $client->getAccessToken(); + + // go back to the previous cache + // (in case callback wasn't called) + $client->setCache($cache); + + $this->assertTrue($called); + } } diff --git a/tests/clearToken.php b/tests/clearToken.php index cd6fc5b2d..c0ef312fc 100644 --- a/tests/clearToken.php +++ b/tests/clearToken.php @@ -2,7 +2,8 @@ include_once __DIR__ . '/bootstrap.php'; $test = new BaseTest(); -print_r($test->getCache()->get('access_token')); -$test->getCache()->delete('access_token'); +$cacheItem = $test->getCache()->getItem('access_token'); +print_r($cacheItem->get()); +$test->getCache()->deleteItem('access_token'); echo "SUCCESS\n"; diff --git a/tests/examples/batchTest.php b/tests/examples/batchTest.php index b721ffd8a..c16ef8bea 100644 --- a/tests/examples/batchTest.php +++ b/tests/examples/batchTest.php @@ -29,7 +29,7 @@ public function testBatch() $nodes = $crawler->filter('br'); $this->assertEquals(20, count($nodes)); - $this->assertContains('The Life of Henry David Thoreau', $crawler->text()); + $this->assertContains('Life of Henry David Thoreau', $crawler->text()); $this->assertContains('George Bernard Shaw His Life and Works', $crawler->text()); } } \ No newline at end of file diff --git a/tests/examples/serviceAccountTest.php b/tests/examples/serviceAccountTest.php index 3bef7133d..b12f6557d 100644 --- a/tests/examples/serviceAccountTest.php +++ b/tests/examples/serviceAccountTest.php @@ -29,6 +29,6 @@ public function testServiceAccount() $nodes = $crawler->filter('br'); $this->assertEquals(10, count($nodes)); - $this->assertContains('The Life of Henry David Thoreau', $crawler->text()); + $this->assertContains('Life of Henry David Thoreau', $crawler->text()); } } \ No newline at end of file