diff --git a/.travis.yml b/.travis.yml index 08adabb..b197d7c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,8 @@ language: php php: - - 5.6 - 5.5 - - 5.4 + - 5.6 - 7.0 - 7.1 - hhvm @@ -13,19 +12,6 @@ install: composer require --no-update guzzlehttp/guzzle $GUZZLE_VERSION; compose script: vendor/bin/phpunit env: - - GUZZLE_VERSION: 4.2 - - GUZZLE_VERSION: 5.0 - - GUZZLE_VERSION: 5.2 - - GUZZLE_VERSION: 5.3 - - GUZZLE_VERSION: 5.3.1 - -matrix: - exclude: - - php: 7.1 - env: GUZZLE_VERSION=4.2 - - php: 7.1 - env: GUZZLE_VERSION=5.0 - - php: 7.1 - env: GUZZLE_VERSION=5.2 - - php: 7.1 - env: GUZZLE_VERSION=5.3 + - GUZZLE_VERSION: 6.0 + - GUZZLE_VERSION: 6.2 + - GUZZLE_VERSION: 6.* diff --git a/README.md b/README.md index a6e7085..2907ad3 100644 --- a/README.md +++ b/README.md @@ -1,38 +1,78 @@ -HTTP Signatures Guzzle 4 +HTTP Signatures Guzzle 6 ======================== -Guzzle 4 support for 99designs http-signatures library +Guzzle 6 support for 99designs http-signatures library [![Build Status](https://travis-ci.org/99designs/http-signatures-guzzlehttp.svg)](https://travis-ci.org/99designs/http-signatures-guzzlehttp) -Adds [99designs/http-signatures][99signatures] support to Guzzle 4. +Adds [99designs/http-signatures][99signatures] support to Guzzle 6. + + +Older Guzzle Versions +--------------------- +For Guzzle 4 & 5 use the `v1.x` release of this repo. For Guzzle 3 see the [99designs/http-signatures-guzzle][99signatures-guzzle] repo. -Signing with Guzzle 4 + +Signing with Guzzle 6 --------------------- -This library includes support for automatically signing Guzzle requests using an event subscriber. +This library includes support for automatically signing Guzzle requests using Middleware. + +You can use `GuzzleHttpSignatures::defaultHandlerFromContext` to easily create the default Guzzle handler with the +middleware added to sign every request. ```php +use GuzzleHttp\Client; use HttpSignatures\Context; -use HttpSignatures\GuzzleHttp\RequestSubscriber; +use HttpSignatures\GuzzleHttpSignatures; -$context = new Context(array( - 'keys' => array('examplekey' => 'secret-key-here'), - 'algorithm' => 'hmac-sha256', - 'headers' => array('(request-target)', 'Date', 'Accept'), -)); +require __DIR__ . "/../vendor/autoload.php"; -$client = new \Guzzle\Http\Client('http://example.org'); -$client->getEmitter()->attach(new RequestSubscriber($context)); +$context = new Context([ + 'keys' => ['examplekey' => 'secret-key-here'], + 'algorithm' => 'hmac-sha256', + 'headers' => ['(request-target)', 'date'], +]); + +$handlerStack = GuzzleHttpSignatures::defaultHandlerFromContext($context); +$client = new Client(['handler' => $handlerStack]); // The below will now send a signed request to: http://example.org/path?query=123 -$client->get('/path?query=123', array( - 'Date' => 'Wed, 30 Jul 2014 16:40:19 -0700', - 'Accept' => 'llamas', -)); +$response = $client->get("http://www.example.com/path?query=123", ['headers' => ['date' => 'today']]); ``` +Or if you're creating a custom `HandlerStack` you can add the Middleware yourself: + +```php + ['examplekey' => 'secret-key-here'], + 'algorithm' => 'hmac-sha256', + 'headers' => ['(request-target)', 'date'], +]); + +$handlerStack = new HandlerStack(); +$stack->setHandler(new CurlHandler()); +$stack->push(GuzzleHttpSignatures::middlewareFromContext($this->context)); +$stack->push(Middleware::history($this->history)); +$client = new Client(['handler' => $handlerStack]); + +// The below will now send a signed request to: http://example.org/path?query=123 +$response = $client->get("http://www.example.com/path?query=123", ['headers' => ['date' => 'today']]); +``` + + ## Contributing Pull Requests are welcome. diff --git a/composer.json b/composer.json index 2920b43..fa2be97 100644 --- a/composer.json +++ b/composer.json @@ -1,8 +1,8 @@ { "name": "99designs/http-signatures-guzzlehttp", - "description": "Sign and verify HTTP messages with Guzzle 4", + "description": "Sign and verify HTTP messages with Guzzle 6", "homepage": "https://github.com/99designs/http-signatures-guzzlehttp", - "keywords": ["http", "https", "signing", "signed", "signature", "hmac", "guzzle 4"], + "keywords": ["http", "https", "signing", "signed", "signature", "hmac", "guzzle 6"], "license": "MIT", "authors": [ { @@ -11,7 +11,7 @@ }, { "name": "Ruben de Vries", - "email": "ruben@blocktrail.com" + "email": "ruben@rubensayshi.com" } ], "autoload": { @@ -20,9 +20,9 @@ } }, "require": { - "php": ">=5.4.0", - "99designs/http-signatures": ">=1.1.0 <3.0.0", - "guzzlehttp/guzzle": ">=4.2.0 <=5.3.1" + "php": ">=5.5.0", + "99designs/http-signatures": ">=3.0.0 <4.0.0", + "guzzlehttp/guzzle": ">=6.0 <7.0.0" }, "require-dev": { "phpunit/phpunit": "~4.1" diff --git a/examples/basic_request_example.php b/examples/basic_request_example.php new file mode 100644 index 0000000..0788c1b --- /dev/null +++ b/examples/basic_request_example.php @@ -0,0 +1,19 @@ + ['examplekey' => 'secret-key-here'], + 'algorithm' => 'hmac-sha256', + 'headers' => ['(request-target)', 'date'], +]); + +$handlerStack = GuzzleHttpSignatures::defaultHandlerFromContext($context); +$client = new Client(['handler' => $handlerStack]); + +// The below will now send a signed request to: http://example.org/path?query=123 +$response = $client->get("http://www.example.com/path?query=123", ['headers' => ['date' => 'today']]); diff --git a/examples/custom_handler_stack_example.php b/examples/custom_handler_stack_example.php new file mode 100644 index 0000000..ebf03ec --- /dev/null +++ b/examples/custom_handler_stack_example.php @@ -0,0 +1,25 @@ + ['examplekey' => 'secret-key-here'], + 'algorithm' => 'hmac-sha256', + 'headers' => ['(request-target)', 'date'], +]); + +$handlerStack = new HandlerStack(); +$stack->setHandler(new CurlHandler()); +$stack->push(GuzzleHttpSignatures::middlewareFromContext($this->context)); +$stack->push(Middleware::history($this->history)); +$client = new Client(['handler' => $handlerStack]); + +// The below will now send a signed request to: http://example.org/path?query=123 +$response = $client->get("http://www.example.com/path?query=123", ['headers' => ['date' => 'today']]); diff --git a/src/HttpSignatures/GuzzleHttp/Message.php b/src/HttpSignatures/GuzzleHttp/Message.php deleted file mode 100644 index ed9c4fc..0000000 --- a/src/HttpSignatures/GuzzleHttp/Message.php +++ /dev/null @@ -1,47 +0,0 @@ -request = $request; - $this->headers = new MessageHeaders($request); - } - - public function getQueryString() - { - $qs = $this->request->getQuery(); - return $qs->count() ? $qs : null; - } - - public function getMethod() - { - return $this->request->getMethod(); - } - - public function getPathInfo() - { - return $this->request->getPath(); - } -} diff --git a/src/HttpSignatures/GuzzleHttp/MessageHeaders.php b/src/HttpSignatures/GuzzleHttp/MessageHeaders.php deleted file mode 100644 index c1f2547..0000000 --- a/src/HttpSignatures/GuzzleHttp/MessageHeaders.php +++ /dev/null @@ -1,40 +0,0 @@ -request = $request; - } - - public function has($header) - { - return $this->request->hasHeader($header); - } - - public function get($header) - { - return $this->request->getHeader($header); - } - - public function set($header, $value) - { - $this->request->setHeader($header, $value); - } -} diff --git a/src/HttpSignatures/GuzzleHttp/RequestSubscriber.php b/src/HttpSignatures/GuzzleHttp/RequestSubscriber.php deleted file mode 100644 index d466a80..0000000 --- a/src/HttpSignatures/GuzzleHttp/RequestSubscriber.php +++ /dev/null @@ -1,32 +0,0 @@ -context = $context; - } - - public function getEvents() - { - return ['before' => ['onBefore', RequestEvents::SIGN_REQUEST]]; - } - - public function onBefore(BeforeEvent $event) - { - $request = $event->getRequest(); - $this->context->signer()->sign(new Message($request)); - } -} diff --git a/src/HttpSignatures/GuzzleHttpSignatures.php b/src/HttpSignatures/GuzzleHttpSignatures.php new file mode 100644 index 0000000..8b774f7 --- /dev/null +++ b/src/HttpSignatures/GuzzleHttpSignatures.php @@ -0,0 +1,40 @@ +push(self::middlewareFromContext($context)); + + return $stack; + } + + /** + * @param Context $context + * @return \Closure + */ + public static function middlewareFromContext(Context $context) + { + return function (callable $handler) use ($context) + { + return function ( + Request $request, + array $options + ) use ($handler, $context) + { + $request = $context->signer()->sign($request); + return $handler($request, $options); + }; + }; + } +} diff --git a/tests/GuzzleHttpSignerTest.php b/tests/GuzzleHttpSignerTest.php index 3c9aac5..c2fe741 100644 --- a/tests/GuzzleHttpSignerTest.php +++ b/tests/GuzzleHttpSignerTest.php @@ -3,12 +3,14 @@ namespace HttpSignatures\Test; use GuzzleHttp\Client; -use GuzzleHttp\Message\Response; -use GuzzleHttp\Subscriber\History; -use GuzzleHttp\Subscriber\Mock; -use HttpSignatures\GuzzleHttp\Message; -use HttpSignatures\GuzzleHttp\RequestSubscriber; +use GuzzleHttp\Handler\CurlHandler; +use GuzzleHttp\Handler\MockHandler; +use GuzzleHttp\HandlerStack; +use GuzzleHttp\Middleware; +use GuzzleHttp\Psr7\Request; +use GuzzleHttp\Psr7\Response; use HttpSignatures\Context; +use HttpSignatures\GuzzleHttpSignatures; class GuzzleHttpSignerTest extends \PHPUnit_Framework_TestCase { @@ -22,26 +24,26 @@ class GuzzleHttpSignerTest extends \PHPUnit_Framework_TestCase */ private $client; + /** + * @var + */ + private $history = []; + public function setUp() { - $this->context = new Context(array( - 'keys' => array('pda' => 'secret'), + $this->context = new Context([ + 'keys' => ['pda' => 'secret'], 'algorithm' => 'hmac-sha256', - 'headers' => array('(request-target)', 'date'), - )); - - $this->client = new Client(); - - $mock = new Mock([ - new Response(200) + 'headers' => ['(request-target)', 'date'], ]); - $this->client->getEmitter()->attach($mock); - - $this->history = new History(); - $this->client->getEmitter()->attach($this->history); - - $this->client->getEmitter()->attach(new RequestSubscriber($this->context)); + $stack = new HandlerStack(); + $stack->setHandler(new MockHandler([ + new Response(200, ['Content-Length' => 0]), + ])); + $stack->push(GuzzleHttpSignatures::middlewareFromContext($this->context)); + $stack->push(Middleware::history($this->history)); + $this->client = new Client(['handler' => $stack]); } /** @@ -49,29 +51,35 @@ class GuzzleHttpSignerTest extends \PHPUnit_Framework_TestCase */ public function testGuzzleRequestHasExpectedHeaders() { - $this->client->get('/path?query=123', array( - 'headers' => array('date' => 'today', 'accept' => 'llamas') - )); - $message = $this->history->getLastRequest(); + $this->client->get('/path?query=123', [ + 'headers' => ['date' => 'today', 'accept' => 'llamas'] + ]); + + // get last request + $message = end($this->history); + /** @var Request $request */ + $request = $message['request']; + /** @var Response $response */ + $response = $message['request']; $expectedString = implode( ',', - array( + [ 'keyId="pda"', 'algorithm="hmac-sha256"', 'headers="(request-target) date"', 'signature="SFlytCGpsqb/9qYaKCQklGDvwgmrwfIERFnwt+yqPJw="', - ) + ] ); $this->assertEquals( - $expectedString, - (string) $message->getHeader('Signature') + [$expectedString], + $request->getHeader('Signature') ); $this->assertEquals( - 'Signature ' . $expectedString, - (string) $message->getHeader('Authorization') + ['Signature ' . $expectedString], + $request->getHeader('Authorization') ); } @@ -80,39 +88,62 @@ class GuzzleHttpSignerTest extends \PHPUnit_Framework_TestCase */ public function testGuzzleRequestHasExpectedHeaders2() { - $this->client->get('/path', array( - 'headers' => array('date' => 'today', 'accept' => 'llamas') - )); - $message = $this->history->getLastRequest(); + $this->client->get('/path', [ + 'headers' => ['date' => 'today', 'accept' => 'llamas'] + ]); + + // get last request + $message = end($this->history); + /** @var Request $request */ + $request = $message['request']; + /** @var Response $response */ + $response = $message['request']; $expectedString = implode( ',', - array( + [ 'keyId="pda"', 'algorithm="hmac-sha256"', 'headers="(request-target) date"', 'signature="DAtF133khP05pS5Gh8f+zF/UF7mVUojMj7iJZO3Xk4o="', - ) + ] ); $this->assertEquals( - $expectedString, - (string) $message->getHeader('Signature') + [$expectedString], + $request->getHeader('Signature') ); $this->assertEquals( - 'Signature ' . $expectedString, - (string) $message->getHeader('Authorization') + ['Signature ' . $expectedString], + $request->getHeader('Authorization') ); } - public function testVerifyGuzzleRequest() - { - $this->client->get('/path?query=123', array( - 'headers' => array('date' => 'today', 'accept' => 'llamas') - )); - $message = $this->history->getLastRequest(); + public function getVerifyGuzzleRequestVectors() { + return [ + /* path, headers */ + ['/path?query=123', ['date' => 'today', 'accept' => 'llamas']], + ['/path?z=zebra&a=antelope', ['date' => 'today']], + ]; + } - $this->assertTrue($this->context->verifier()->isValid(new Message($message))); + /** + * @dataProvider getVerifyGuzzleRequestVectors + * @param string $path + * @param array $headers + */ + public function testVerifyGuzzleRequest($path, $headers) + { + $this->client->get($path, ['headers' => $headers]); + + // get last request + $message = end($this->history); + /** @var Request $request */ + $request = $message['request']; + /** @var Response $response */ + $response = $message['request']; + + $this->assertTrue($this->context->verifier()->isValid($request)); } }