This repository has been archived by the owner on Jan 21, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 32
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #81 from Wilt/feature-xhttp-override-listener
X-HTTP-Method-Override listener
- Loading branch information
Showing
8 changed files
with
330 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
<?php | ||
/** | ||
* @license http://opensource.org/licenses/BSD-3-Clause BSD-3-Clause | ||
* @copyright Copyright (c) 2016 Zend Technologies USA Inc. (http://www.zend.com) | ||
*/ | ||
|
||
namespace ZF\ContentNegotiation\Factory; | ||
|
||
use Interop\Container\ContainerInterface; | ||
use ZF\ContentNegotiation\ContentNegotiationOptions; | ||
use ZF\ContentNegotiation\HttpMethodOverrideListener; | ||
|
||
class HttpMethodOverrideListenerFactory | ||
{ | ||
/** | ||
* @param ContainerInterface $container | ||
* @return HttpMethodOverrideListener | ||
*/ | ||
public function __invoke(ContainerInterface $container) | ||
{ | ||
$options = $container->get(ContentNegotiationOptions::class); | ||
$httpOverrideMethods = $options->getHttpOverrideMethods(); | ||
$listener = new HttpMethodOverrideListener($httpOverrideMethods); | ||
|
||
return $listener; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
<?php | ||
/** | ||
* @license http://opensource.org/licenses/BSD-3-Clause BSD-3-Clause | ||
* @copyright Copyright (c) 2016 Zend Technologies USA Inc. (http://www.zend.com) | ||
*/ | ||
|
||
namespace ZF\ContentNegotiation; | ||
|
||
use Zend\EventManager\AbstractListenerAggregate; | ||
use Zend\EventManager\EventManagerInterface; | ||
use Zend\Mvc\MvcEvent; | ||
use ZF\ApiProblem\ApiProblem; | ||
use ZF\ApiProblem\ApiProblemResponse; | ||
use Zend\Http\Request as HttpRequest; | ||
|
||
class HttpMethodOverrideListener extends AbstractListenerAggregate | ||
{ | ||
/** | ||
* @var array | ||
*/ | ||
protected $httpMethodOverride = []; | ||
|
||
/** | ||
* HttpMethodOverrideListener constructor. | ||
* | ||
* @param array $httpMethodOverride | ||
*/ | ||
public function __construct(array $httpMethodOverride) | ||
{ | ||
$this->httpMethodOverride = $httpMethodOverride; | ||
} | ||
|
||
/** | ||
* Priority is set very high (should be executed before all other listeners that rely on the request method value). | ||
* TODO: Check priority value, maybe value should be even higher?? | ||
* | ||
* @param EventManagerInterface $events | ||
* @param int $priority | ||
*/ | ||
public function attach(EventManagerInterface $events, $priority = 1) | ||
{ | ||
$this->listeners[] = $events->attach(MvcEvent::EVENT_ROUTE, [$this, 'onRoute'], -40); | ||
} | ||
|
||
/** | ||
* Checks for X-HTTP-Method-Override header and sets header inside request object. | ||
* | ||
* @param MvcEvent $event | ||
* @return void|ApiProblemResponse | ||
*/ | ||
public function onRoute(MvcEvent $event) | ||
{ | ||
$request = $event->getRequest(); | ||
|
||
if (! $request instanceof HttpRequest) { | ||
return; | ||
} | ||
|
||
if (! $request->getHeaders()->has('X-HTTP-Method-Override')) { | ||
return; | ||
} | ||
|
||
$method = $request->getMethod(); | ||
|
||
if (! array_key_exists($method, $this->httpMethodOverride)) { | ||
return new ApiProblemResponse(new ApiProblem( | ||
400, | ||
sprintf('Overriding %s method with X-HTTP-Method-Override header is not allowed', $method) | ||
)); | ||
} | ||
|
||
$header = $request->getHeader('X-HTTP-Method-Override'); | ||
$overrideMethod = $header->getFieldValue(); | ||
$allowedMethods = $this->httpMethodOverride[$method]; | ||
|
||
if (! in_array($overrideMethod, $allowedMethods)) { | ||
return new ApiProblemResponse(new ApiProblem( | ||
400, | ||
sprintf('Illegal override method %s in X-HTTP-Method-Override header', $overrideMethod) | ||
)); | ||
} | ||
|
||
$request->setMethod($overrideMethod); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
<?php | ||
/** | ||
* @license http://opensource.org/licenses/BSD-3-Clause BSD-3-Clause | ||
* @copyright Copyright (c) 2016 Zend Technologies USA Inc. (http://www.zend.com) | ||
*/ | ||
|
||
namespace ZFTest\ContentNegotiation\Factory; | ||
|
||
use PHPUnit_Framework_TestCase as TestCase; | ||
use Prophecy\Prophecy\ObjectProphecy; | ||
use Zend\ServiceManager\ServiceLocatorInterface; | ||
use Zend\ServiceManager\ServiceManager; | ||
use ZF\ContentNegotiation\ContentNegotiationOptions; | ||
use ZF\ContentNegotiation\Factory\HttpMethodOverrideListenerFactory; | ||
use ZF\ContentNegotiation\HttpMethodOverrideListener; | ||
|
||
class HttpMethodOverrideListenerFactoryTest extends TestCase | ||
{ | ||
public function testCreateServiceShouldReturnContentTypeFilterListenerInstance() | ||
{ | ||
/** @var ContentNegotiationOptions|ObjectProphecy $options */ | ||
$options = $this->prophesize(ContentNegotiationOptions::class); | ||
$options->getHttpOverrideMethods()->willReturn([]); | ||
|
||
/** @var ServiceManager|ObjectProphecy $container */ | ||
$container = $this->prophesize(ServiceManager::class); | ||
$container->willImplement(ServiceLocatorInterface::class); | ||
$container->get(ContentNegotiationOptions::class)->willReturn($options); | ||
|
||
$factory = new HttpMethodOverrideListenerFactory(); | ||
$service = $factory($container->reveal(), HttpMethodOverrideListener::class); | ||
|
||
$this->assertInstanceOf(HttpMethodOverrideListener::class, $service); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
<?php | ||
/** | ||
* @license http://opensource.org/licenses/BSD-3-Clause BSD-3-Clause | ||
* @copyright Copyright (c) 2016 Zend Technologies USA Inc. (http://www.zend.com) | ||
*/ | ||
|
||
namespace ZFTest\ContentNegotiation; | ||
|
||
use PHPUnit_Framework_TestCase as TestCase; | ||
use Zend\Http\Request as HttpRequest; | ||
use Zend\Mvc\MvcEvent; | ||
use ZF\ApiProblem\ApiProblemResponse; | ||
use ZF\ContentNegotiation\HttpMethodOverrideListener; | ||
|
||
class HttpMethodOverrideListenerTest extends TestCase | ||
{ | ||
use RouteMatchFactoryTrait; | ||
|
||
/** | ||
* @var HttpMethodOverrideListener | ||
*/ | ||
protected $listener; | ||
|
||
/** | ||
* @var array | ||
*/ | ||
protected $httpMethodOverride = [ | ||
HttpRequest::METHOD_GET => [ | ||
HttpRequest::METHOD_HEAD, | ||
HttpRequest::METHOD_POST, | ||
HttpRequest::METHOD_PUT, | ||
HttpRequest::METHOD_DELETE, | ||
HttpRequest::METHOD_PATCH, | ||
], | ||
HttpRequest::METHOD_POST => [ | ||
], | ||
]; | ||
|
||
/** | ||
* Set up test | ||
*/ | ||
public function setUp() | ||
{ | ||
$this->listener = new HttpMethodOverrideListener($this->httpMethodOverride); | ||
} | ||
|
||
/** | ||
* @return array | ||
*/ | ||
public function httpMethods() | ||
{ | ||
return [ | ||
'head' => [HttpRequest::METHOD_HEAD], | ||
'post' => [HttpRequest::METHOD_POST], | ||
'put' => [HttpRequest::METHOD_PUT], | ||
'delete' => [HttpRequest::METHOD_DELETE], | ||
'patch' => [HttpRequest::METHOD_PATCH], | ||
]; | ||
} | ||
|
||
/** | ||
* @dataProvider httpMethods | ||
*/ | ||
public function testHttpMethodOverrideListener($method) | ||
{ | ||
$listener = $this->listener; | ||
|
||
$request = new HttpRequest(); | ||
$request->setMethod('GET'); | ||
$request->getHeaders()->addHeaderLine('X-HTTP-Method-Override', $method); | ||
|
||
$event = new MvcEvent(); | ||
$event->setRequest($request); | ||
$event->setRouteMatch($this->createRouteMatch([])); | ||
|
||
$result = $listener->onRoute($event); | ||
$this->assertEquals($method, $request->getMethod()); | ||
} | ||
|
||
/** | ||
* @dataProvider httpMethods | ||
*/ | ||
public function testHttpMethodOverrideListenerReturnsProblemResponseForMethodNotInConfig($method) | ||
{ | ||
$listener = $this->listener; | ||
|
||
$request = new HttpRequest(); | ||
$request->setMethod('PATCH'); | ||
$request->getHeaders()->addHeaderLine('X-HTTP-Method-Override', $method); | ||
|
||
$event = new MvcEvent(); | ||
$event->setRequest($request); | ||
|
||
$result = $listener->onRoute($event); | ||
$this->assertInstanceOf(ApiProblemResponse::class, $result); | ||
$problem = $result->getApiProblem(); | ||
$this->assertEquals(400, $problem->status); | ||
$this->assertContains( | ||
'Overriding PATCH method with X-HTTP-Method-Override header is not allowed', | ||
$problem->detail | ||
); | ||
} | ||
|
||
/** | ||
* @dataProvider httpMethods | ||
*/ | ||
public function testHttpMethodOverrideListenerReturnsProblemResponseForIllegalOverrideValue($method) | ||
{ | ||
$listener = $this->listener; | ||
|
||
$request = new HttpRequest(); | ||
$request->setMethod('POST'); | ||
$request->getHeaders()->addHeaderLine('X-HTTP-Method-Override', $method); | ||
|
||
$event = new MvcEvent(); | ||
$event->setRequest($request); | ||
|
||
$result = $listener->onRoute($event); | ||
$this->assertInstanceOf(ApiProblemResponse::class, $result); | ||
$problem = $result->getApiProblem(); | ||
$this->assertEquals(400, $problem->status); | ||
$this->assertContains( | ||
sprintf('Illegal override method %s in X-HTTP-Method-Override header', $method), | ||
$problem->detail | ||
); | ||
} | ||
} |