Skip to content
This repository was archived by the owner on Feb 7, 2024. It is now read-only.

[2.x] Per-app CORS #469

Merged
merged 4 commits into from
Aug 18, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 3 additions & 14 deletions config/websockets.php
Original file line number Diff line number Diff line change
Expand Up @@ -84,23 +84,12 @@
'capacity' => null,
'enable_client_messages' => false,
'enable_statistics' => true,
'allowed_origins' => [
//
],
],
],

/*
|--------------------------------------------------------------------------
| Allowed Origins
|--------------------------------------------------------------------------
|
| If not empty, you can whitelist certain origins that will be allowed
| to connect to the websocket server.
|
*/

'allowed_origins' => [
//
],

/*
|--------------------------------------------------------------------------
| Maximum Request Size
Expand Down
1 change: 1 addition & 0 deletions docs/basic-usage/pusher.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ You may add additional apps in your `config/websockets.php` file.
'capacity' => null,
'enable_client_messages' => false,
'enable_statistics' => true,
'allowed_origins' => [],
],
],
```
Expand Down
10 changes: 10 additions & 0 deletions src/Apps/App.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -106,4 +109,11 @@ public function enableStatistics(bool $enabled = true)

return $this;
}

public function setAllowedOrigins(array $allowedOrigins)
{
$this->allowedOrigins = $allowedOrigins;

return $this;
}
}
3 changes: 2 additions & 1 deletion src/Apps/ConfigAppManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@ protected function instantiate(?array $appAttributes): ?App
$app
->enableClientMessages($appAttributes['enable_client_messages'])
->enableStatistics($appAttributes['enable_statistics'])
->setCapacity($appAttributes['capacity'] ?? null);
->setCapacity($appAttributes['capacity'] ?? null)
->setAllowedOrigins($appAttributes['allowed_origins'] ?? []);

return $app;
}
Expand Down
60 changes: 0 additions & 60 deletions src/Server/OriginCheck.php

This file was deleted.

8 changes: 3 additions & 5 deletions src/Server/WebSocketServerFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,9 @@ public function createServer(): IoServer
$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);

Expand Down
12 changes: 12 additions & 0 deletions src/WebSockets/Exceptions/OriginNotAllowed.php
Original file line number Diff line number Diff line change
@@ -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;
}
}
19 changes: 19 additions & 0 deletions src/WebSockets/WebSocketHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
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;
Expand All @@ -30,6 +31,7 @@ public function onOpen(ConnectionInterface $connection)
{
$this
->verifyAppKey($connection)
->verifyOrigin($connection)
->limitConcurrentConnections($connection)
->generateSocketId($connection)
->establishConnection($connection);
Expand Down Expand Up @@ -77,6 +79,23 @@ protected function verifyAppKey(ConnectionInterface $connection)
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)) {
Expand Down
2 changes: 1 addition & 1 deletion tests/ClientProviders/ConfigAppManagerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public function it_can_get_apps_from_the_config_file()
{
$apps = $this->appManager->all();

$this->assertCount(1, $apps);
$this->assertCount(2, $apps);

/** @var $app */
$app = $apps[0];
Expand Down
37 changes: 36 additions & 1 deletion tests/ConnectionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
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
Expand All @@ -14,7 +15,7 @@ public function unknown_app_keys_can_not_connect()
{
$this->expectException(UnknownAppKey::class);

$this->pusherServer->onOpen($this->getWebSocketConnection('/?appKey=test'));
$this->pusherServer->onOpen($this->getWebSocketConnection('test'));
}

/** @test */
Expand Down Expand Up @@ -65,4 +66,38 @@ public function ping_returns_pong()

$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');
}
}
21 changes: 17 additions & 4 deletions tests/TestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,19 @@ protected function getEnvironmentSetUp($app)
'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',
],
],
]);

Expand Down Expand Up @@ -107,20 +120,20 @@ protected function getEnvironmentSetUp($app)
}
}

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);

Expand Down