diff --git a/README.md b/README.md index cfa5be2..1b3af21 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,9 @@ Guzzle 3 compatibility continues in the [`1.0`](https://github.com/commerceguys/ - Supports refresh tokens (stores them and uses them to get new access tokens). - Handles token expiration (acquires new tokens and retries failed requests). +- You can now optionally deal with token persistence with doctrine cache. Just require doctrine/cache , instantiate a cache implementation. + tokens are now cached for a lifetime corresponding to their expire value. + ## Running the tests First make sure you have all the dependencies in place by running `composer install --prefer-dist`, then simply run `./bin/phpunit`. @@ -50,6 +53,11 @@ $refreshToken = new RefreshToken($oauth2Client, $config); $oauth2 = new Oauth2Subscriber($token, $refreshToken); + +$redis_client = new Predis\Client(); +$doctrine_cache = new \Doctrine\Common\Cache\PredisCache($redis_client); +$oauth2->setCache($doctrine_cache); + $client = new Client([ 'defaults' => [ 'auth' => 'oauth2', diff --git a/composer.json b/composer.json index de21505..1d7203b 100644 --- a/composer.json +++ b/composer.json @@ -12,7 +12,11 @@ } }, "require-dev": { - "phpunit/phpunit": "~4.5" + "phpunit/phpunit": "~4.5", + "doctrine/cache": "~1.3" + }, + "suggest": { + "doctrine/cache": "Allow token persistence in doctrine cache" }, "autoload-dev": { "psr-4": { @@ -28,6 +32,9 @@ }, { "name": "Patrick Dawkins" + }, + { + "name": "Xavier Lembo" } ], "config": { diff --git a/composer.lock b/composer.lock index 787e7ff..e31dbeb 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "bfccacd6b0ab7fd440c008a9fcbd2bec", + "hash": "eed59b946063e41862e4655d5ed572ba", "packages": [ { "name": "firebase/php-jwt", - "version": "v2.1.0", + "version": "v2.2.0", "source": { "type": "git", "url": "https://github.com/firebase/php-jwt.git", - "reference": "fb219727e199dd80a72d5274ebb5c8b24d58dd9b" + "reference": "e0a75bfb6413f22092c99b70f310ccb2cca3efa5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/firebase/php-jwt/zipball/fb219727e199dd80a72d5274ebb5c8b24d58dd9b", - "reference": "fb219727e199dd80a72d5274ebb5c8b24d58dd9b", + "url": "https://api.github.com/repos/firebase/php-jwt/zipball/e0a75bfb6413f22092c99b70f310ccb2cca3efa5", + "reference": "e0a75bfb6413f22092c99b70f310ccb2cca3efa5", "shasum": "" }, "require": { @@ -48,7 +48,7 @@ ], "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.", "homepage": "https://github.com/firebase/php-jwt", - "time": "2015-05-20 19:16:04" + "time": "2015-06-22 23:26:39" }, { "name": "guzzlehttp/guzzle", @@ -211,16 +211,16 @@ }, { "name": "react/promise", - "version": "v2.2.0", + "version": "v2.2.1", "source": { "type": "git", "url": "https://github.com/reactphp/promise.git", - "reference": "365fcee430dfa4ace1fbc75737ca60ceea7eeeef" + "reference": "3b6fca09c7d56321057fa8867c8dbe1abf648627" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/promise/zipball/365fcee430dfa4ace1fbc75737ca60ceea7eeeef", - "reference": "365fcee430dfa4ace1fbc75737ca60ceea7eeeef", + "url": "https://api.github.com/repos/reactphp/promise/zipball/3b6fca09c7d56321057fa8867c8dbe1abf648627", + "reference": "3b6fca09c7d56321057fa8867c8dbe1abf648627", "shasum": "" }, "require": { @@ -247,14 +247,84 @@ "authors": [ { "name": "Jan Sorgalla", - "email": "jsorgalla@googlemail.com" + "email": "jsorgalla@gmail.com" } ], "description": "A lightweight implementation of CommonJS Promises/A for PHP", - "time": "2014-12-30 13:32:42" + "time": "2015-07-03 13:48:55" } ], "packages-dev": [ + { + "name": "doctrine/cache", + "version": "v1.4.2", + "source": { + "type": "git", + "url": "https://github.com/doctrine/cache.git", + "reference": "8c434000f420ade76a07c64cbe08ca47e5c101ca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/cache/zipball/8c434000f420ade76a07c64cbe08ca47e5c101ca", + "reference": "8c434000f420ade76a07c64cbe08ca47e5c101ca", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "conflict": { + "doctrine/common": ">2.2,<2.4" + }, + "require-dev": { + "phpunit/phpunit": ">=3.7", + "predis/predis": "~1.0", + "satooshi/php-coveralls": "~0.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.5.x-dev" + } + }, + "autoload": { + "psr-0": { + "Doctrine\\Common\\Cache\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Caching library offering an object-oriented API for many cache backends", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "cache", + "caching" + ], + "time": "2015-08-31 12:36:41" + }, { "name": "doctrine/instantiator", "version": "1.0.5", @@ -360,16 +430,16 @@ }, { "name": "phpspec/prophecy", - "version": "v1.4.1", + "version": "v1.5.0", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "3132b1f44c7bf2ec4c7eb2d3cb78fdeca760d373" + "reference": "4745ded9307786b730d7a60df5cb5a6c43cf95f7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/3132b1f44c7bf2ec4c7eb2d3cb78fdeca760d373", - "reference": "3132b1f44c7bf2ec4c7eb2d3cb78fdeca760d373", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/4745ded9307786b730d7a60df5cb5a6c43cf95f7", + "reference": "4745ded9307786b730d7a60df5cb5a6c43cf95f7", "shasum": "" }, "require": { @@ -416,20 +486,20 @@ "spy", "stub" ], - "time": "2015-04-27 22:15:08" + "time": "2015-08-13 10:07:40" }, { "name": "phpunit/php-code-coverage", - "version": "2.1.5", + "version": "2.2.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "be2286cb8c7e1773eded49d9719219e6f74f9e3e" + "reference": "2d7c03c0e4e080901b8f33b2897b0577be18a13c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/be2286cb8c7e1773eded49d9719219e6f74f9e3e", - "reference": "be2286cb8c7e1773eded49d9719219e6f74f9e3e", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2d7c03c0e4e080901b8f33b2897b0577be18a13c", + "reference": "2d7c03c0e4e080901b8f33b2897b0577be18a13c", "shasum": "" }, "require": { @@ -437,7 +507,7 @@ "phpunit/php-file-iterator": "~1.3", "phpunit/php-text-template": "~1.2", "phpunit/php-token-stream": "~1.3", - "sebastian/environment": "~1.0", + "sebastian/environment": "^1.3.2", "sebastian/version": "~1.0" }, "require-dev": { @@ -452,7 +522,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.1.x-dev" + "dev-master": "2.2.x-dev" } }, "autoload": { @@ -478,20 +548,20 @@ "testing", "xunit" ], - "time": "2015-06-09 13:05:42" + "time": "2015-08-04 03:42:39" }, { "name": "phpunit/php-file-iterator", - "version": "1.4.0", + "version": "1.4.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "a923bb15680d0089e2316f7a4af8f437046e96bb" + "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/a923bb15680d0089e2316f7a4af8f437046e96bb", - "reference": "a923bb15680d0089e2316f7a4af8f437046e96bb", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/6150bf2c35d3fc379e50c7602b75caceaa39dbf0", + "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0", "shasum": "" }, "require": { @@ -525,20 +595,20 @@ "filesystem", "iterator" ], - "time": "2015-04-02 05:19:05" + "time": "2015-06-21 13:08:43" }, { "name": "phpunit/php-text-template", - "version": "1.2.0", + "version": "1.2.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "206dfefc0ffe9cebf65c413e3d0e809c82fbf00a" + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/206dfefc0ffe9cebf65c413e3d0e809c82fbf00a", - "reference": "206dfefc0ffe9cebf65c413e3d0e809c82fbf00a", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", "shasum": "" }, "require": { @@ -547,20 +617,17 @@ "type": "library", "autoload": { "classmap": [ - "Text/" + "src/" ] }, "notification-url": "https://packagist.org/downloads/", - "include-path": [ - "" - ], "license": [ "BSD-3-Clause" ], "authors": [ { "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", + "email": "sebastian@phpunit.de", "role": "lead" } ], @@ -569,20 +636,20 @@ "keywords": [ "template" ], - "time": "2014-01-30 17:20:04" + "time": "2015-06-21 13:50:34" }, { "name": "phpunit/php-timer", - "version": "1.0.6", + "version": "1.0.7", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "83fe1bdc5d47658b727595c14da140da92b3d66d" + "reference": "3e82f4e9fc92665fafd9157568e4dcb01d014e5b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/83fe1bdc5d47658b727595c14da140da92b3d66d", - "reference": "83fe1bdc5d47658b727595c14da140da92b3d66d", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3e82f4e9fc92665fafd9157568e4dcb01d014e5b", + "reference": "3e82f4e9fc92665fafd9157568e4dcb01d014e5b", "shasum": "" }, "require": { @@ -610,20 +677,20 @@ "keywords": [ "timer" ], - "time": "2015-06-13 07:35:30" + "time": "2015-06-21 08:01:12" }, { "name": "phpunit/php-token-stream", - "version": "1.4.2", + "version": "1.4.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "db63be1159c81df649cd0260e30249a586d4129e" + "reference": "3ab72c62e550370a6cd5dc873e1a04ab57562f5b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/db63be1159c81df649cd0260e30249a586d4129e", - "reference": "db63be1159c81df649cd0260e30249a586d4129e", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/3ab72c62e550370a6cd5dc873e1a04ab57562f5b", + "reference": "3ab72c62e550370a6cd5dc873e1a04ab57562f5b", "shasum": "" }, "require": { @@ -659,20 +726,20 @@ "keywords": [ "tokenizer" ], - "time": "2015-06-12 07:34:24" + "time": "2015-08-16 08:51:00" }, { "name": "phpunit/phpunit", - "version": "4.7.3", + "version": "4.8.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "41fea1e84ed84d373f5ac099a1276c4358c90708" + "reference": "2246830f4a1a551c67933e4171bf2126dc29d357" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/41fea1e84ed84d373f5ac099a1276c4358c90708", - "reference": "41fea1e84ed84d373f5ac099a1276c4358c90708", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/2246830f4a1a551c67933e4171bf2126dc29d357", + "reference": "2246830f4a1a551c67933e4171bf2126dc29d357", "shasum": "" }, "require": { @@ -682,15 +749,15 @@ "ext-reflection": "*", "ext-spl": "*", "php": ">=5.3.3", - "phpspec/prophecy": "~1.3,>=1.3.1", + "phpspec/prophecy": "^1.3.1", "phpunit/php-code-coverage": "~2.1", "phpunit/php-file-iterator": "~1.4", "phpunit/php-text-template": "~1.2", - "phpunit/php-timer": "~1.0", + "phpunit/php-timer": ">=1.0.6", "phpunit/phpunit-mock-objects": "~2.3", "sebastian/comparator": "~1.1", "sebastian/diff": "~1.2", - "sebastian/environment": "~1.2", + "sebastian/environment": "~1.3", "sebastian/exporter": "~1.2", "sebastian/global-state": "~1.0", "sebastian/version": "~1.0", @@ -705,7 +772,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.7.x-dev" + "dev-master": "4.8.x-dev" } }, "autoload": { @@ -731,26 +798,27 @@ "testing", "xunit" ], - "time": "2015-06-11 16:20:25" + "time": "2015-08-24 04:09:38" }, { "name": "phpunit/phpunit-mock-objects", - "version": "2.3.4", + "version": "2.3.7", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", - "reference": "92408bb1968a81b3217a6fdf6c1a198da83caa35" + "reference": "5e2645ad49d196e020b85598d7c97e482725786a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/92408bb1968a81b3217a6fdf6c1a198da83caa35", - "reference": "92408bb1968a81b3217a6fdf6c1a198da83caa35", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/5e2645ad49d196e020b85598d7c97e482725786a", + "reference": "5e2645ad49d196e020b85598d7c97e482725786a", "shasum": "" }, "require": { - "doctrine/instantiator": "~1.0,>=1.0.2", + "doctrine/instantiator": "^1.0.2", "php": ">=5.3.3", - "phpunit/php-text-template": "~1.2" + "phpunit/php-text-template": "~1.2", + "sebastian/exporter": "~1.2" }, "require-dev": { "phpunit/phpunit": "~4.4" @@ -786,20 +854,20 @@ "mock", "xunit" ], - "time": "2015-06-11 15:55:48" + "time": "2015-08-19 09:14:08" }, { "name": "sebastian/comparator", - "version": "1.1.1", + "version": "1.2.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "1dd8869519a225f7f2b9eb663e225298fade819e" + "reference": "937efb279bd37a375bcadf584dec0726f84dbf22" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/1dd8869519a225f7f2b9eb663e225298fade819e", - "reference": "1dd8869519a225f7f2b9eb663e225298fade819e", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/937efb279bd37a375bcadf584dec0726f84dbf22", + "reference": "937efb279bd37a375bcadf584dec0726f84dbf22", "shasum": "" }, "require": { @@ -813,7 +881,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1.x-dev" + "dev-master": "1.2.x-dev" } }, "autoload": { @@ -850,7 +918,7 @@ "compare", "equality" ], - "time": "2015-01-29 16:28:08" + "time": "2015-07-26 15:48:44" }, { "name": "sebastian/diff", @@ -906,16 +974,16 @@ }, { "name": "sebastian/environment", - "version": "1.2.2", + "version": "1.3.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "5a8c7d31914337b69923db26c4221b81ff5a196e" + "reference": "6324c907ce7a52478eeeaede764f48733ef5ae44" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/5a8c7d31914337b69923db26c4221b81ff5a196e", - "reference": "5a8c7d31914337b69923db26c4221b81ff5a196e", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/6324c907ce7a52478eeeaede764f48733ef5ae44", + "reference": "6324c907ce7a52478eeeaede764f48733ef5ae44", "shasum": "" }, "require": { @@ -952,20 +1020,20 @@ "environment", "hhvm" ], - "time": "2015-01-01 10:01:08" + "time": "2015-08-03 06:14:51" }, { "name": "sebastian/exporter", - "version": "1.2.0", + "version": "1.2.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "84839970d05254c73cde183a721c7af13aede943" + "reference": "7ae5513327cb536431847bcc0c10edba2701064e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/84839970d05254c73cde183a721c7af13aede943", - "reference": "84839970d05254c73cde183a721c7af13aede943", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/7ae5513327cb536431847bcc0c10edba2701064e", + "reference": "7ae5513327cb536431847bcc0c10edba2701064e", "shasum": "" }, "require": { @@ -1018,7 +1086,7 @@ "export", "exporter" ], - "time": "2015-01-27 07:23:06" + "time": "2015-06-21 07:55:53" }, { "name": "sebastian/global-state", @@ -1073,16 +1141,16 @@ }, { "name": "sebastian/recursion-context", - "version": "1.0.0", + "version": "1.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "3989662bbb30a29d20d9faa04a846af79b276252" + "reference": "994d4a811bafe801fb06dccbee797863ba2792ba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/3989662bbb30a29d20d9faa04a846af79b276252", - "reference": "3989662bbb30a29d20d9faa04a846af79b276252", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/994d4a811bafe801fb06dccbee797863ba2792ba", + "reference": "994d4a811bafe801fb06dccbee797863ba2792ba", "shasum": "" }, "require": { @@ -1122,20 +1190,20 @@ ], "description": "Provides functionality to recursively process PHP variables", "homepage": "http://www.github.com/sebastianbergmann/recursion-context", - "time": "2015-01-24 09:48:32" + "time": "2015-06-21 08:04:50" }, { "name": "sebastian/version", - "version": "1.0.5", + "version": "1.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/version.git", - "reference": "ab931d46cd0d3204a91e1b9a40c4bc13032b58e4" + "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/ab931d46cd0d3204a91e1b9a40c4bc13032b58e4", - "reference": "ab931d46cd0d3204a91e1b9a40c4bc13032b58e4", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", + "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", "shasum": "" }, "type": "library", @@ -1157,20 +1225,20 @@ ], "description": "Library that helps with managing the version number of Git-hosted PHP projects", "homepage": "https://github.com/sebastianbergmann/version", - "time": "2015-02-24 06:35:25" + "time": "2015-06-21 13:59:46" }, { "name": "symfony/yaml", - "version": "v2.7.1", + "version": "v2.7.4", "source": { "type": "git", "url": "https://github.com/symfony/Yaml.git", - "reference": "9808e75c609a14f6db02f70fccf4ca4aab53c160" + "reference": "2dc7b06c065df96cc686c66da2705e5e18aef661" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Yaml/zipball/9808e75c609a14f6db02f70fccf4ca4aab53c160", - "reference": "9808e75c609a14f6db02f70fccf4ca4aab53c160", + "url": "https://api.github.com/repos/symfony/Yaml/zipball/2dc7b06c065df96cc686c66da2705e5e18aef661", + "reference": "2dc7b06c065df96cc686c66da2705e5e18aef661", "shasum": "" }, "require": { @@ -1206,7 +1274,7 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2015-06-10 15:30:22" + "time": "2015-08-24 07:13:45" } ], "aliases": [], diff --git a/src/GrantType/GrantTypeBase.php b/src/GrantType/GrantTypeBase.php index 360a854..f0cc5bd 100644 --- a/src/GrantType/GrantTypeBase.php +++ b/src/GrantType/GrantTypeBase.php @@ -6,6 +6,7 @@ use GuzzleHttp\ClientInterface; use GuzzleHttp\Collection; + abstract class GrantTypeBase implements GrantTypeInterface { /** @var ClientInterface The token endpoint client */ @@ -17,6 +18,9 @@ abstract class GrantTypeBase implements GrantTypeInterface /** @var string */ protected $grantType = ''; + /** @var \Doctrine\Common\Cache\Cache */ + protected $cache; + /** * @param ClientInterface $client * @param array $config @@ -27,6 +31,29 @@ public function __construct(ClientInterface $client, array $config = []) $this->config = Collection::fromConfig($config, $this->getDefaults(), $this->getRequired()); } + /** + * @param \Doctrine\Common\Cache\Cache $cache + * @return void + * + * @throws \InvalidArgumentException + */ + public function setCache($cache) + { + if (!$cache instanceof \Doctrine\Common\Cache\Cache) { + throw new \InvalidArgumentException('Provided cache must implement Doctrine Cache interface'); + } + + $this->cache = $cache; + } + + /** + * @return mixed \Doctrine\Common\Cache\Cache|null + */ + public function getCache() + { + return $this->cache; + } + /** * Get default configuration items. * @@ -63,12 +90,48 @@ protected function getAdditionalOptions() } /** - * @inheritdoc + * @param bool $forceCache + * + * @return AccessToken */ - public function getToken() + public function getToken($forceCache = false) { $config = $this->config->toArray(); + if ($this->cache) { + $key = $this->getCacheKey($config); + + if ($forceCache || !$data = $this->cache->fetch($key)) { //cache miss + + $lifetime = 0; + $data = $this->getTokenDatas($config); + if (isset($data['expires'])) { + $lifetime = (int) $data['expires'] - time(); + unset($data['expires']); + } elseif (isset($data['expires_in'])) { + $lifetime = (int) $data['expires_in']; + unset($data['expires_in']); + } + + $this->cache->save($key, serialize($data), $lifetime); + } else { + $data = unserialize($data); + } + + } else { + $data = $this->getTokenDatas($config); + } + + return new AccessToken($data['access_token'], $data['token_type'], $data); + } + + /** + * @param $config + * + * @return mixed + */ + protected function getTokenDatas($config) + { $body = $config; $body['grant_type'] = $this->grantType; unset($body['token_url'], $body['auth_location']); @@ -87,8 +150,28 @@ public function getToken() } $response = $this->client->post($config['token_url'], $requestOptions); - $data = $response->json(); - return new AccessToken($data['access_token'], $data['token_type'], $data); + return $response->json(); + } + + /** + * compute the current token cache key + * + * @param $config + * + * @return string + */ + protected function getCacheKey($config) + { + + $tokenIdent = sha1($this->client->getBaseUrl() . '_' . $config['client_id']); + + $key = sprintf( + 'cg_acesstoken_%s_%s', + $this->grantType, + $tokenIdent + ); + + return $key; } } diff --git a/src/GrantType/GrantTypeInterface.php b/src/GrantType/GrantTypeInterface.php index 8f1b00a..e6f3269 100644 --- a/src/GrantType/GrantTypeInterface.php +++ b/src/GrantType/GrantTypeInterface.php @@ -4,12 +4,23 @@ use CommerceGuys\Guzzle\Oauth2\AccessToken; + interface GrantTypeInterface { /** * Get the token data returned by the OAuth2 server. + * if cache is activated, we might need to force cache invalidation (ie 401) + * + * @param bool $forceCache * * @return AccessToken */ - public function getToken(); + public function getToken($forceCache = false); + + /** + * @param $cache + * + * @return mixed + */ + public function setCache($cache); } diff --git a/src/GrantType/JwtBearer.php b/src/GrantType/JwtBearer.php index edf8eef..682083a 100755 --- a/src/GrantType/JwtBearer.php +++ b/src/GrantType/JwtBearer.php @@ -3,6 +3,7 @@ namespace CommerceGuys\Guzzle\Oauth2\GrantType; use GuzzleHttp\ClientInterface; +use CommerceGuys\Guzzle\Oauth2\GrantType\GrantTypeBase; use JWT; use SplFileObject; use InvalidArgumentException; @@ -68,8 +69,6 @@ protected function computeJwt() * Read private key * * @param SplFileObject $privateKey - * - * @return string */ protected function readPrivateKey(SplFileObject $privateKey) { diff --git a/src/GrantType/RefreshToken.php b/src/GrantType/RefreshToken.php index b3e7d3a..02c0802 100644 --- a/src/GrantType/RefreshToken.php +++ b/src/GrantType/RefreshToken.php @@ -38,12 +38,12 @@ public function hasRefreshToken() /** * @inheritdoc */ - public function getToken() + public function getToken($forceCache = false) { if (!$this->hasRefreshToken()) { throw new \RuntimeException("Refresh token not available"); } - return parent::getToken(); + return parent::getToken($forceCache); } } diff --git a/src/Oauth2Subscriber.php b/src/Oauth2Subscriber.php index 475852e..4a466e5 100644 --- a/src/Oauth2Subscriber.php +++ b/src/Oauth2Subscriber.php @@ -8,6 +8,7 @@ use GuzzleHttp\Event\ErrorEvent; use GuzzleHttp\Event\RequestEvents; use GuzzleHttp\Event\SubscriberInterface; +use Doctrine\Common\Cache\Cache; class Oauth2Subscriber implements SubscriberInterface { @@ -22,6 +23,9 @@ class Oauth2Subscriber implements SubscriberInterface /** @var RefreshTokenGrantTypeInterface */ protected $refreshTokenGrantType; + /** @var Cache */ + protected $cache; + /** * Create a new Oauth2 subscriber. * @@ -54,7 +58,7 @@ public function onError(ErrorEvent $event) if ($response && 401 == $response->getStatusCode()) { $request = $event->getRequest(); if ($request->getConfig()->get('auth') == 'oauth2' && !$request->getConfig()->get('retried')) { - if ($token = $this->acquireAccessToken()) { + if ($token = $this->acquireAccessToken(true)) { $this->accessToken = $token; $request->getConfig()->set('retried', true); $event->intercept($event->getClient()->send($request)); @@ -66,9 +70,11 @@ public function onError(ErrorEvent $event) /** * Get a new access token. * + * @param bool $forceCache + * * @return AccessToken|null */ - protected function acquireAccessToken() + protected function acquireAccessToken($forceCache = false) { $accessToken = null; @@ -84,7 +90,7 @@ protected function acquireAccessToken() if (!$accessToken && $this->grantType) { // Get a new access token. - $accessToken = $this->grantType->getToken(); + $accessToken = $this->grantType->getToken($forceCache); } return $accessToken ?: null; @@ -170,4 +176,26 @@ public function setRefreshToken($refreshToken) } $this->refreshToken = $refreshToken; } + + /** + * @param \Doctrine\Common\Cache\Cache $cache Doctrine cache instance + * + * @throws \InvalidArgumentException + */ + public function setCache($cache) + { + if (!$cache instanceof \Doctrine\Common\Cache\Cache) { + throw new \InvalidArgumentException('Provided cache must implement Doctrine Cache interface'); + } + + $this->cache = $cache; + + if (null !== $this->grantType) { + $this->grantType->setCache($cache); + } + + if (null !== $this->refreshTokenGrantType) { + $this->refreshTokenGrantType->setCache($cache); + } + } } diff --git a/tests/OAuth2SubscriberTest.php b/tests/OAuth2SubscriberTest.php index f2c41c4..43daafc 100644 --- a/tests/OAuth2SubscriberTest.php +++ b/tests/OAuth2SubscriberTest.php @@ -6,6 +6,7 @@ use CommerceGuys\Guzzle\Oauth2\GrantType\RefreshToken; use CommerceGuys\Guzzle\Oauth2\Oauth2Subscriber; use CommerceGuys\Guzzle\Oauth2\Tests\TestBase; +use Doctrine\Common\Cache\ArrayCache; class OAuth2SubscriberTest extends TestBase { @@ -96,4 +97,121 @@ public function testSettingManualRefreshToken() $this->assertEquals('refresh_token', $subscriber->getRefreshToken()->getType()); $this->assertEquals('testRefreshToken', $subscriber->getRefreshToken()->getToken()); } + + public function testBadCacheInstanciation() + { + $this->setExpectedException('\\InvalidArgumentException', 'Provided cache must implement Doctrine Cache interface'); + + $dummyCache = new \stdClass(); + $subscriber = new Oauth2Subscriber(); + $subscriber->setCache($dummyCache); + } + + public function testSubscriberCacheInstanciation() + { + $credentials = [ + 'client_id' => 'test', + 'client_secret' => 'testSecret', + ]; + + $accessTokenGrantType = new ClientCredentials( + $this->getClient([], ['tokenExpires' => 0]), + $credentials + ); + + $subscriber = new Oauth2Subscriber( + $accessTokenGrantType, + new RefreshToken($this->getClient(), $credentials) + ); + + $cache = new ArrayCache(); + $subscriber->setCache($cache); + + $this->assertSame($accessTokenGrantType->getCache(), $cache); + } + + public function testCacheSuccessfullyPopulated() + { + $cache = new ArrayCache(); + $computedCacheKey = 'cg_acesstoken_client_credentials_0a8508877b473c528ee9e510db3e49aafac51145'; + + $credentials = [ + 'client_id' => 'test', + 'client_secret' => 'testSecret', + ]; + + $accessTokenGrantType = new ClientCredentials( + $this->getClient([], ['tokenExpires' => 0]), + $credentials + ); + + $subscriber = new Oauth2Subscriber( + $accessTokenGrantType, + new RefreshToken($this->getClient(), $credentials) + ); + $subscriber->setCache($cache); + + $client = $this->getClient([ + 'defaults' => [ + 'subscribers' => [$subscriber], + 'auth' => 'oauth2', + 'exceptions' => false, + ], + ]); + + $this->assertFalse($cache->contains($computedCacheKey)); + + $response = $client->get('api/collection'); + $this->assertEquals(200, $response->getStatusCode()); + $this->assertTrue($cache->contains($computedCacheKey)); + + $serializedCacheContent = $cache->fetch($computedCacheKey); + $tokenDatas = unserialize($serializedCacheContent); + + $this->assertInternalType('array', $tokenDatas); + $this->assertArrayHasKey('access_token', $tokenDatas); + } + + public function testCacheShouldBeUSedIfAlreadyPopulated() + { + $cache = new ArrayCache(); + $computedCacheKey = 'cg_acesstoken_client_credentials_0a8508877b473c528ee9e510db3e49aafac51145'; + + $cache->save($computedCacheKey, serialize([ + 'access_token' => 'cachedToken', + 'token_type' => 'bearer' + ]) + ); + + $credentials = [ + 'client_id' => 'test', + 'client_secret' => 'testSecret', + ]; + + $accessTokenGrantType = new ClientCredentials( + $this->getClient([], ['tokenExpires' => 0]), + $credentials + ); + + $subscriber = new Oauth2Subscriber( + $accessTokenGrantType, + new RefreshToken($this->getClient(), $credentials) + ); + $subscriber->setCache($cache); + + $client = $this->getClient([ + 'defaults' => [ + 'subscribers' => [$subscriber], + 'auth' => 'oauth2', + 'exceptions' => false, + ], + ]); + + + $response = $client->get('api/collection'); + $token = $subscriber->getAccessToken(); + + $this->assertEquals('cachedToken', $token->getToken()); + $this->assertEquals('bearer', $token->getType()); + } }