[Http][Security] OriginCheck + same host/origin policy
Added the Http\OriginCheck component that will only allow connections coming from desired Origins Same host+origin policy by default: App.php, by default, will only allow connections from the given hostname Fixes #102
This commit is contained in:
parent
0cf4b614a1
commit
f50af83fa7
@ -10,10 +10,13 @@ CHANGELOG
|
||||
|
||||
* 0.3.0 (2013-xx-xx)
|
||||
|
||||
* Sugar and spice and everything nice: Added the Ratchet\App class for ease of use
|
||||
* Added the `App` class to help making Ratchet so easy to use it's silly
|
||||
* BC: Require hostname to do HTTP Host header match and do Origin HTTP header check, verify same name by default, helping prevent CSRF attacks
|
||||
* Added Symfony/2.2 based HTTP Router component to allowing for a single Ratchet server to handle multiple apps -> Ratchet\Http\Router
|
||||
* BC: Decoupled HTTP from WebSocket component -> Ratchet\Http\HttpServer
|
||||
* Updated dependency to React/0.3
|
||||
* BF: Single sub-protocol selection to conform with RFC6455
|
||||
* BF: Sanity checks on WAMP protocol to prevent errors
|
||||
|
||||
* 0.2.7 (2013-06-09)
|
||||
|
||||
|
@ -4,6 +4,7 @@ use React\EventLoop\LoopInterface;
|
||||
use React\EventLoop\Factory as LoopFactory;
|
||||
use React\Socket\Server as Reactor;
|
||||
use Ratchet\Http\HttpServerInterface;
|
||||
use Ratchet\Http\OriginCheck;
|
||||
use Ratchet\Wamp\WampServerInterface;
|
||||
use Ratchet\Server\IoServer;
|
||||
use Ratchet\Server\FlashPolicy;
|
||||
@ -77,11 +78,13 @@ class App {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @param $path
|
||||
* @param ComponentInterface $controller
|
||||
* @return \Symfony\Component\Routing\Route
|
||||
* @param array $allowedOrigins An array of hosts allowed to connect (same host by default), [*] for any
|
||||
* @param string $httpHost Override the $httpHost variable provided in the __construct
|
||||
* @return ComponentInterface|WsServer
|
||||
*/
|
||||
public function route($path, ComponentInterface $controller) {
|
||||
public function route($path, ComponentInterface $controller, array $allowedOrigins = array(), $httpHost = null) {
|
||||
if ($controller instanceof HttpServerInterface || $controller instanceof WsServer) {
|
||||
$decorated = $controller;
|
||||
} elseif ($controller instanceof WampServerInterface) {
|
||||
@ -92,7 +95,17 @@ class App {
|
||||
$decorated = $controller;
|
||||
}
|
||||
|
||||
$this->routes->add('rr-' . ++$this->_routeCounter, new Route($path, array('_controller' => $decorated), array(), array(), $this->httpHost));
|
||||
$httpHost = $httpHost ?: $this->httpHost;
|
||||
|
||||
if (0 === count($allowedOrigins)) {
|
||||
$allowedOrigins[] = $httpHost;
|
||||
}
|
||||
$allowedOrigins = array_values($allowedOrigins);
|
||||
if ('*' !== $allowedOrigins[0]) {
|
||||
$decorated = new OriginCheck($decorated, $allowedOrigins);
|
||||
}
|
||||
|
||||
$this->routes->add('rr-' . ++$this->_routeCounter, new Route($path, array('_controller' => $decorated), array('Origin' => $this->httpHost), array(), $httpHost));
|
||||
|
||||
return $decorated;
|
||||
}
|
||||
|
69
src/Ratchet/Http/OriginCheck.php
Normal file
69
src/Ratchet/Http/OriginCheck.php
Normal file
@ -0,0 +1,69 @@
|
||||
<?php
|
||||
namespace Ratchet\Http;
|
||||
use Guzzle\Http\Message\RequestInterface;
|
||||
use Ratchet\ConnectionInterface;
|
||||
use Ratchet\MessageComponentInterface;
|
||||
use Guzzle\Http\Message\Response;
|
||||
|
||||
class OriginCheck implements HttpServerInterface {
|
||||
/**
|
||||
* @var \Ratchet\MessageComponentInterface
|
||||
*/
|
||||
protected $_component;
|
||||
|
||||
public $allowedOrigins = array();
|
||||
|
||||
public function __construct(MessageComponentInterface $component, array $allowed = array()) {
|
||||
$this->_component = $component;
|
||||
$this->allowedOrigins += $allowed;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function onOpen(ConnectionInterface $conn, RequestInterface $request = null) {
|
||||
$origin = (string)$request->getHeader('Origin');
|
||||
|
||||
if (!in_array($origin, $this->allowedOrigins)) {
|
||||
return $this->close($conn, 403);
|
||||
}
|
||||
|
||||
return $this->_component->onOpen($conn, $request);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
function onMessage(ConnectionInterface $from, $msg) {
|
||||
return $this->_component->onMessage($from, $msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
function onClose(ConnectionInterface $conn) {
|
||||
return $this->_component->onClose($conn);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
function onError(ConnectionInterface $conn, \Exception $e) {
|
||||
return $this->_component->onError($conn, $e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close a connection with an HTTP response
|
||||
* @param \Ratchet\ConnectionInterface $conn
|
||||
* @param int $code HTTP status code
|
||||
* @return null
|
||||
*/
|
||||
protected function close(ConnectionInterface $conn, $code = 400) {
|
||||
$response = new Response($code, array(
|
||||
'X-Powered-By' => \Ratchet\VERSION
|
||||
));
|
||||
|
||||
$conn->send((string)$response);
|
||||
$conn->close();
|
||||
}
|
||||
}
|
@ -16,7 +16,11 @@ abstract class AbstractMessageComponentTestCase extends \PHPUnit_Framework_TestC
|
||||
$this->_serv = new $decorator($this->_app);
|
||||
$this->_conn = $this->getMock('\Ratchet\ConnectionInterface');
|
||||
|
||||
$this->_serv->onOpen($this->_conn);
|
||||
$this->doOpen($this->_conn);
|
||||
}
|
||||
|
||||
protected function doOpen($conn) {
|
||||
$this->_serv->onOpen($conn);
|
||||
}
|
||||
|
||||
public function isExpectedConnection() {
|
||||
@ -25,7 +29,7 @@ abstract class AbstractMessageComponentTestCase extends \PHPUnit_Framework_TestC
|
||||
|
||||
public function testOpen() {
|
||||
$this->_app->expects($this->once())->method('onOpen')->with($this->isExpectedConnection());
|
||||
$this->_serv->onOpen($this->getMock('\Ratchet\ConnectionInterface'));
|
||||
$this->doOpen($this->getMock('\Ratchet\ConnectionInterface'));
|
||||
}
|
||||
|
||||
public function testOnClose() {
|
||||
@ -38,4 +42,9 @@ abstract class AbstractMessageComponentTestCase extends \PHPUnit_Framework_TestC
|
||||
$this->_app->expects($this->once())->method('onError')->with($this->isExpectedConnection(), $e);
|
||||
$this->_serv->onError($this->_conn, $e);
|
||||
}
|
||||
|
||||
public function passthroughMessageTest($value) {
|
||||
$this->_app->expects($this->once())->method('onMessage')->with($this->isExpectedConnection(), $value);
|
||||
$this->_serv->onMessage($this->_conn, $value);
|
||||
}
|
||||
}
|
46
tests/unit/Http/OriginCheckTest.php
Normal file
46
tests/unit/Http/OriginCheckTest.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
namespace Ratchet\Http;
|
||||
use Ratchet\AbstractMessageComponentTestCase;
|
||||
|
||||
/**
|
||||
* @covers Ratchet\Http\OriginCheck
|
||||
*/
|
||||
class OriginCheckTest extends AbstractMessageComponentTestCase {
|
||||
protected $_reqStub;
|
||||
|
||||
public function setUp() {
|
||||
$this->_reqStub = $this->getMock('Guzzle\Http\Message\RequestInterface');
|
||||
$this->_reqStub->expects($this->any())->method('getHeader')->will($this->returnValue('localhost'));
|
||||
|
||||
parent::setUp();
|
||||
|
||||
$this->_serv->allowedOrigins[] = 'localhost';
|
||||
}
|
||||
|
||||
protected function doOpen($conn) {
|
||||
$this->_serv->onOpen($conn, $this->_reqStub);
|
||||
}
|
||||
|
||||
public function getConnectionClassString() {
|
||||
return '\Ratchet\ConnectionInterface';
|
||||
}
|
||||
|
||||
public function getDecoratorClassString() {
|
||||
return '\Ratchet\Http\OriginCheck';
|
||||
}
|
||||
|
||||
public function getComponentClassString() {
|
||||
return '\Ratchet\Http\HttpServerInterface';
|
||||
}
|
||||
|
||||
public function testCloseOnNonMatchingOrigin() {
|
||||
$this->_serv->allowedOrigins = array('socketo.me');
|
||||
$this->_conn->expects($this->once())->method('close');
|
||||
|
||||
$this->_serv->onOpen($this->_conn, $this->_reqStub);
|
||||
}
|
||||
|
||||
public function testOnMessage() {
|
||||
$this->passthroughMessageTest('Hello World!');
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user