-
Notifications
You must be signed in to change notification settings - Fork 32
X-HTTP-Method-Override listener #81
Changes from 11 commits
4677f11
9db72f2
e36c3eb
da71cb0
4b1f500
ac6e42e
d9586c5
43768f7
30887a7
37a4d74
3023a4a
0277845
8bac35c
dabc4e0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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) 2014-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; | ||
} | ||
} |
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?? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is an appropriate value. The OPTIONS handling (i.e., HTTP method negotiation) happens at -100. Currently, we already have versioning (for Accept headers) happening at -40 (route-based version checking happens at -41). The main thing is that it happens before the OPTIONS handling, so this works fine. |
||
* | ||
* @param EventManagerInterface $events | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. one space after |
||
* @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); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -48,6 +48,11 @@ public function onBootstrap(MvcEvent $e) | |
$services->get(AcceptFilterListener::class)->attach($eventManager); | ||
$services->get(ContentTypeFilterListener::class)->attach($eventManager); | ||
|
||
$contentNegotiationOptions = $services->get(ContentNegotiationOptions::class); | ||
if ($contentNegotiationOptions->getXHttpMethodOverrideEnabled()) { | ||
$services->get(HttpMethodOverrideListener::class)->attach($eventManager); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I very much like this conditional attachment. |
||
} | ||
|
||
$sharedEventManager = $eventManager->getSharedManager(); | ||
$sharedEventManager->attach( | ||
DispatchableInterface::class, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
<?php | ||
/** | ||
* @license http://opensource.org/licenses/BSD-3-Clause BSD-3-Clause | ||
* @copyright Copyright (c) 2014 Zend Technologies USA Inc. (http://www.zend.com) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
*/ | ||
|
||
namespace ZFTest\ContentNegotiation\Factory; | ||
|
||
use PHPUnit_Framework_TestCase as TestCase; | ||
use Zend\ServiceManager\ServiceManager; | ||
use ZF\ContentNegotiation\ContentNegotiationOptions; | ||
use ZF\ContentNegotiation\Factory\HttpMethodOverrideListenerFactory; | ||
use ZF\ContentNegotiation\HttpMethodOverrideListener; | ||
|
||
class HttpMethodOverrideListenerFactoryTest extends TestCase | ||
{ | ||
public function testCreateServiceShouldReturnContentTypeFilterListenerInstance() | ||
{ | ||
$serviceManager = new ServiceManager(); | ||
$serviceManager->setService( | ||
ContentNegotiationOptions::class, | ||
new ContentNegotiationOptions() | ||
); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can use |
||
|
||
$factory = new HttpMethodOverrideListenerFactory(); | ||
|
||
$service = $factory($serviceManager, 'HttpMethodOverrideListener'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please use here as second parameter |
||
|
||
$this->assertInstanceOf(HttpMethodOverrideListener::class, $service); | ||
} | ||
} |
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 => [ | ||
] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Trailing comma please. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please add trailing comma |
||
]; | ||
|
||
/** | ||
* 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()); | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please remove empty line above. |
||
/** | ||
* @dataProvider httpMethods | ||
*/ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What does it for? |
||
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 | ||
); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use only
2016
year in new files.