Added cors setting outside the app
This commit is contained in:
parent
5153568867
commit
11727e684f
|
|
@ -84,22 +84,11 @@ return [
|
|||
'capacity' => null,
|
||||
'enable_client_messages' => false,
|
||||
'enable_statistics' => true,
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Allowed Origins
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| If not empty, you can whitelist certain origins that will be allowed
|
||||
| to connect to the websocket server.
|
||||
|
|
||||
*/
|
||||
|
||||
'allowed_origins' => [
|
||||
//
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -33,6 +33,9 @@ class App
|
|||
/** @var bool */
|
||||
public $statisticsEnabled = true;
|
||||
|
||||
/** @var array */
|
||||
public $allowedOrigins = [];
|
||||
|
||||
public static function findById($appId)
|
||||
{
|
||||
return app(AppManager::class)->findById($appId);
|
||||
|
|
@ -106,4 +109,11 @@ class App
|
|||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setAllowedOrigins(array $allowedOrigins)
|
||||
{
|
||||
$this->allowedOrigins = $allowedOrigins;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,7 +78,8 @@ class ConfigAppManager implements AppManager
|
|||
$app
|
||||
->enableClientMessages($appAttributes['enable_client_messages'])
|
||||
->enableStatistics($appAttributes['enable_statistics'])
|
||||
->setCapacity($appAttributes['capacity'] ?? null);
|
||||
->setCapacity($appAttributes['capacity'] ?? null)
|
||||
->setAllowedOrigins($appAttributes['allowed_origins'] ?? []);
|
||||
|
||||
return $app;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,60 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace BeyondCode\LaravelWebSockets\Server;
|
||||
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Ratchet\ConnectionInterface;
|
||||
use Ratchet\Http\CloseResponseTrait;
|
||||
use Ratchet\Http\HttpServerInterface;
|
||||
use Ratchet\MessageComponentInterface;
|
||||
|
||||
class OriginCheck implements HttpServerInterface
|
||||
{
|
||||
use CloseResponseTrait;
|
||||
|
||||
/** @var \Ratchet\MessageComponentInterface */
|
||||
protected $_component;
|
||||
|
||||
protected $allowedOrigins = [];
|
||||
|
||||
public function __construct(MessageComponentInterface $component, array $allowedOrigins = [])
|
||||
{
|
||||
$this->_component = $component;
|
||||
|
||||
$this->allowedOrigins = $allowedOrigins;
|
||||
}
|
||||
|
||||
public function onOpen(ConnectionInterface $connection, RequestInterface $request = null)
|
||||
{
|
||||
if ($request->hasHeader('Origin')) {
|
||||
$this->verifyOrigin($connection, $request);
|
||||
}
|
||||
|
||||
return $this->_component->onOpen($connection, $request);
|
||||
}
|
||||
|
||||
public function onMessage(ConnectionInterface $from, $msg)
|
||||
{
|
||||
return $this->_component->onMessage($from, $msg);
|
||||
}
|
||||
|
||||
public function onClose(ConnectionInterface $connection)
|
||||
{
|
||||
return $this->_component->onClose($connection);
|
||||
}
|
||||
|
||||
public function onError(ConnectionInterface $connection, \Exception $e)
|
||||
{
|
||||
return $this->_component->onError($connection, $e);
|
||||
}
|
||||
|
||||
protected function verifyOrigin(ConnectionInterface $connection, RequestInterface $request)
|
||||
{
|
||||
$header = (string) $request->getHeader('Origin')[0];
|
||||
$origin = parse_url($header, PHP_URL_HOST) ?: $header;
|
||||
|
||||
if (! empty($this->allowedOrigins) && ! in_array($origin, $this->allowedOrigins)) {
|
||||
return $this->close($connection, 403);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -79,11 +79,9 @@ class WebSocketServerFactory
|
|||
$socket = new SecureServer($socket, $this->loop, config('websockets.ssl'));
|
||||
}
|
||||
|
||||
$urlMatcher = new UrlMatcher($this->routes, new RequestContext);
|
||||
|
||||
$router = new Router($urlMatcher);
|
||||
|
||||
$app = new OriginCheck($router, config('websockets.allowed_origins', []));
|
||||
$app = new Router(
|
||||
new UrlMatcher($this->routes, new RequestContext)
|
||||
);
|
||||
|
||||
$httpServer = new HttpServer($app, config('websockets.max_request_size_in_kb') * 1024);
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
|
||||
namespace BeyondCode\LaravelWebSockets\WebSockets\Exceptions;
|
||||
|
||||
class OriginNotAllowed extends WebSocketException
|
||||
{
|
||||
public function __construct(string $appKey)
|
||||
{
|
||||
$this->message = "The origin is not allowed for `{$appKey}`.";
|
||||
$this->code = 4009;
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ use BeyondCode\LaravelWebSockets\Facades\StatisticsLogger;
|
|||
use BeyondCode\LaravelWebSockets\QueryParameters;
|
||||
use BeyondCode\LaravelWebSockets\WebSockets\Channels\ChannelManager;
|
||||
use BeyondCode\LaravelWebSockets\WebSockets\Exceptions\ConnectionsOverCapacity;
|
||||
use BeyondCode\LaravelWebSockets\WebSockets\Exceptions\OriginNotAllowed;
|
||||
use BeyondCode\LaravelWebSockets\WebSockets\Exceptions\UnknownAppKey;
|
||||
use BeyondCode\LaravelWebSockets\WebSockets\Exceptions\WebSocketException;
|
||||
use BeyondCode\LaravelWebSockets\WebSockets\Messages\PusherMessageFactory;
|
||||
|
|
@ -30,6 +31,7 @@ class WebSocketHandler implements MessageComponentInterface
|
|||
{
|
||||
$this
|
||||
->verifyAppKey($connection)
|
||||
->verifyOrigin($connection)
|
||||
->limitConcurrentConnections($connection)
|
||||
->generateSocketId($connection)
|
||||
->establishConnection($connection);
|
||||
|
|
@ -77,6 +79,23 @@ class WebSocketHandler implements MessageComponentInterface
|
|||
return $this;
|
||||
}
|
||||
|
||||
protected function verifyOrigin(ConnectionInterface $connection)
|
||||
{
|
||||
if (! $connection->app->allowedOrigins) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$header = (string) ($connection->httpRequest->getHeader('Origin')[0] ?? null);
|
||||
|
||||
$origin = parse_url($header, PHP_URL_HOST) ?: $header;
|
||||
|
||||
if (! $header || ! in_array($origin, $connection->app->allowedOrigins)) {
|
||||
throw new OriginNotAllowed($connection->app->key);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function limitConcurrentConnections(ConnectionInterface $connection)
|
||||
{
|
||||
if (! is_null($capacity = $connection->app->capacity)) {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ namespace BeyondCode\LaravelWebSockets\Tests;
|
|||
use BeyondCode\LaravelWebSockets\Apps\App;
|
||||
use BeyondCode\LaravelWebSockets\Tests\Mocks\Message;
|
||||
use BeyondCode\LaravelWebSockets\WebSockets\Exceptions\ConnectionsOverCapacity;
|
||||
use BeyondCode\LaravelWebSockets\WebSockets\Exceptions\OriginNotAllowed;
|
||||
use BeyondCode\LaravelWebSockets\WebSockets\Exceptions\UnknownAppKey;
|
||||
|
||||
class ConnectionTest extends TestCase
|
||||
|
|
@ -14,7 +15,7 @@ class ConnectionTest extends TestCase
|
|||
{
|
||||
$this->expectException(UnknownAppKey::class);
|
||||
|
||||
$this->pusherServer->onOpen($this->getWebSocketConnection('/?appKey=test'));
|
||||
$this->pusherServer->onOpen($this->getWebSocketConnection('test'));
|
||||
}
|
||||
|
||||
/** @test */
|
||||
|
|
@ -65,4 +66,38 @@ class ConnectionTest extends TestCase
|
|||
|
||||
$connection->assertSentEvent('pusher:pong');
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function origin_validation_should_fail_for_no_origin()
|
||||
{
|
||||
$this->expectException(OriginNotAllowed::class);
|
||||
|
||||
$connection = $this->getWebSocketConnection('TestOrigin');
|
||||
|
||||
$this->pusherServer->onOpen($connection);
|
||||
|
||||
$connection->assertSentEvent('pusher:connection_established');
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function origin_validation_should_fail_for_wrong_origin()
|
||||
{
|
||||
$this->expectException(OriginNotAllowed::class);
|
||||
|
||||
$connection = $this->getWebSocketConnection('TestOrigin', ['Origin' => 'https://google.ro']);
|
||||
|
||||
$this->pusherServer->onOpen($connection);
|
||||
|
||||
$connection->assertSentEvent('pusher:connection_established');
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function origin_validation_should_pass_for_the_right_origin()
|
||||
{
|
||||
$connection = $this->getWebSocketConnection('TestOrigin', ['Origin' => 'https://test.origin.com']);
|
||||
|
||||
$this->pusherServer->onOpen($connection);
|
||||
|
||||
$connection->assertSentEvent('pusher:connection_established');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,6 +70,19 @@ abstract class TestCase extends \Orchestra\Testbench\TestCase
|
|||
'capacity' => null,
|
||||
'enable_client_messages' => false,
|
||||
'enable_statistics' => true,
|
||||
'allowed_origins' => [],
|
||||
],
|
||||
[
|
||||
'name' => 'Origin Test App',
|
||||
'id' => '1234',
|
||||
'key' => 'TestOrigin',
|
||||
'secret' => 'TestSecret',
|
||||
'capacity' => null,
|
||||
'enable_client_messages' => false,
|
||||
'enable_statistics' => true,
|
||||
'allowed_origins' => [
|
||||
'test.origin.com',
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
|
|
@ -107,20 +120,20 @@ abstract class TestCase extends \Orchestra\Testbench\TestCase
|
|||
}
|
||||
}
|
||||
|
||||
protected function getWebSocketConnection(string $url = '/?appKey=TestKey'): Connection
|
||||
protected function getWebSocketConnection(string $appKey = 'TestKey', array $headers = []): Connection
|
||||
{
|
||||
$connection = new Connection();
|
||||
|
||||
$connection->httpRequest = new Request('GET', $url);
|
||||
$connection->httpRequest = new Request('GET', "/?appKey={$appKey}", $headers);
|
||||
|
||||
return $connection;
|
||||
}
|
||||
|
||||
protected function getConnectedWebSocketConnection(array $channelsToJoin = [], string $url = '/?appKey=TestKey'): Connection
|
||||
protected function getConnectedWebSocketConnection(array $channelsToJoin = [], string $appKey = 'TestKey', array $headers = []): Connection
|
||||
{
|
||||
$connection = new Connection();
|
||||
|
||||
$connection->httpRequest = new Request('GET', $url);
|
||||
$connection->httpRequest = new Request('GET', "/?appKey={$appKey}", $headers);
|
||||
|
||||
$this->pusherServer->onOpen($connection);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue