Skip to content

Commit ce8bcb1

Browse files
committed
Add the ability to block IP addresses from connecting to the websocket server
1 parent 9ebf49f commit ce8bcb1

File tree

15 files changed

+394
-57
lines changed

15 files changed

+394
-57
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
- Add support for `doctrine/cache` 2.0
66
- Bump minimum supported `doctrine/dbal` versions
7+
- Add the ability to block IP addresses from connecting to the websocket server
8+
- Deprecated the `Gos\Bundle\WebSocketBundle\Event\ClientRejectedEvent` class and corresponding event, subscribe to `Gos\Bundle\WebSocketBundle\Event\ConnectionRejectedEvent` instead
79

810
## 3.7.2 (2021-04-25)
911

docs/ConfigurationReference.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ gos_web_socket:
3232
# Enables checking the Origin header of websocket connections for allowed values.
3333
origin_check: false
3434

35+
# Enables checking the originating IP address of websocket connections for blocked addresses.
36+
ip_address_check: false
37+
3538
# Flag indicating a keepalive ping should be enabled on the server.
3639
keepalive_ping: false
3740

@@ -47,6 +50,9 @@ gos_web_socket:
4750

4851
# A list of origins allowed to connect to the websocket server, must match the value from the "Origin" header of the HTTP request.
4952
origins: []
53+
54+
# A list of IP addresses which are not allowed to connect to the websocket server.
55+
blocked_ip_addresses: []
5056
ping:
5157
services:
5258

docs/Events.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ The GosWebSocketBundle provides events which can be used to hook into actions pe
99
- `gos_web_socket.client_disconnected` is dispatched when a client disconnects from the websocket server, listeners receive a `Gos\Bundle\WebSocketBundle\Event\ClientDisconnectedEvent` object
1010
- `gos_web_socket.client_error` is dispatched when a client connection has an error, listeners receive a `Gos\Bundle\WebSocketBundle\Event\ClientErrorEvent` object
1111
- `gos_web_socket.client_rejected` is dispatched when a client connection is rejected by the websocket server, listeners receive a `Gos\Bundle\WebSocketBundle\Event\ClientRejectedEvent` object
12+
- `gos_web_socket.connection_rejected` is dispatched when a connection is rejected by the websocket server, listeners receive a `Gos\Bundle\WebSocketBundle\Event\ConnectionRejectedEvent` object
1213
- `gos_web_socket.push_fail` is dispatched when a server push fails, listeners receive a `Gos\Bundle\WebSocketBundle\Event\PushHandlerFailEvent` object
1314
- `gos_web_socket.push_success` is dispatched when a server push succeeds, listeners receive a `Gos\Bundle\WebSocketBundle\Event\PushHandlerSuccessEvent` object
1415

src/DependencyInjection/Configuration.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ public function getConfigTreeBuilder(): TreeBuilder
7070
->defaultValue(false)
7171
->info('Enables checking the Origin header of websocket connections for allowed values.')
7272
->end()
73+
->booleanNode('ip_address_check')
74+
->defaultValue(false)
75+
->info('Enables checking the originating IP address of websocket connections for blocked addresses.')
76+
->end()
7377
->booleanNode('keepalive_ping')
7478
->defaultValue(false)
7579
->info('Flag indicating a keepalive ping should be enabled on the server.')
@@ -133,6 +137,11 @@ public function getConfigTreeBuilder(): TreeBuilder
133137
->end()
134138
->end()
135139
->end()
140+
->arrayNode('blocked_ip_addresses')
141+
->info('A list of IP addresses which are not allowed to connect to the websocket server.')
142+
->scalarPrototype()
143+
->end()
144+
->end()
136145
->arrayNode('ping')
137146
->children()
138147
->arrayNode('services')

src/DependencyInjection/GosWebSocketExtension.php

Lines changed: 58 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -58,21 +58,22 @@ public function load(array $configs, ContainerBuilder $container): void
5858
$loader->load('services.yaml');
5959
$loader->load('aliases.yaml');
6060

61-
$configs = $this->processConfiguration($this->getConfiguration($configs, $container), $configs);
61+
$config = $this->processConfiguration($this->getConfiguration($configs, $container), $configs);
6262

6363
$container->registerForAutoconfiguration(PeriodicInterface::class)->addTag('gos_web_socket.periodic');
6464
$container->registerForAutoconfiguration(RpcInterface::class)->addTag('gos_web_socket.rpc');
6565
$container->registerForAutoconfiguration(ServerInterface::class)->addTag('gos_web_socket.server');
6666
$container->registerForAutoconfiguration(TopicInterface::class)->addTag('gos_web_socket.topic');
6767

68-
$container->setParameter('gos_web_socket.shared_config', $configs['shared_config']);
68+
$container->setParameter('gos_web_socket.shared_config', $config['shared_config']);
6969

70-
$this->registerClientConfiguration($configs, $container);
71-
$this->registerServerConfiguration($configs, $container);
72-
$this->registerOriginsConfiguration($configs, $container);
73-
$this->registerPingConfiguration($configs, $container);
74-
$this->registerPushersConfiguration($configs, $container);
75-
$this->registerWebsocketClientConfiguration($configs, $container);
70+
$this->registerClientConfiguration($config, $container);
71+
$this->registerServerConfiguration($config, $container);
72+
$this->registerOriginsConfiguration($config, $container);
73+
$this->registerBlockedIpAddressesConfiguration($config, $container);
74+
$this->registerPingConfiguration($config, $container);
75+
$this->registerPushersConfiguration($config, $container);
76+
$this->registerWebsocketClientConfiguration($config, $container);
7677

7778
$this->markAliasesDeprecated($container);
7879
$this->markServicesDeprecated($container);
@@ -130,33 +131,33 @@ private function markServicesDeprecated(ContainerBuilder $container): void
130131
}
131132
}
132133

133-
private function registerClientConfiguration(array $configs, ContainerBuilder $container): void
134+
private function registerClientConfiguration(array $config, ContainerBuilder $container): void
134135
{
135-
if (!isset($configs['client'])) {
136+
if (!isset($config['client'])) {
136137
return;
137138
}
138139

139-
$container->setParameter('gos_web_socket.client.storage.ttl', $configs['client']['storage']['ttl']);
140-
$container->setParameter('gos_web_socket.firewall', (array) $configs['client']['firewall']);
140+
$container->setParameter('gos_web_socket.client.storage.ttl', $config['client']['storage']['ttl']);
141+
$container->setParameter('gos_web_socket.firewall', (array) $config['client']['firewall']);
141142

142143
// @deprecated to be removed in 4.0, parameter is unused
143-
$container->setParameter('gos_web_socket.client.storage.prefix', $configs['client']['storage']['prefix']);
144+
$container->setParameter('gos_web_socket.client.storage.prefix', $config['client']['storage']['prefix']);
144145

145-
if (isset($configs['client']['session_handler'])) {
146-
$sessionHandler = ltrim($configs['client']['session_handler'], '@');
146+
if (isset($config['client']['session_handler'])) {
147+
$sessionHandler = ltrim($config['client']['session_handler'], '@');
147148

148149
$container->getDefinition('gos_web_socket.server.builder')
149150
->addMethodCall('setSessionHandler', [new Reference($sessionHandler)]);
150151

151152
$container->setAlias('gos_web_socket.session_handler', $sessionHandler);
152153
}
153154

154-
if (isset($configs['client']['storage']['driver'])) {
155-
$driverRef = ltrim($configs['client']['storage']['driver'], '@');
155+
if (isset($config['client']['storage']['driver'])) {
156+
$driverRef = ltrim($config['client']['storage']['driver'], '@');
156157
$storageDriver = $driverRef;
157158

158-
if (isset($configs['client']['storage']['decorator'])) {
159-
$decoratorRef = ltrim($configs['client']['storage']['decorator'], '@');
159+
if (isset($config['client']['storage']['decorator'])) {
160+
$decoratorRef = ltrim($config['client']['storage']['decorator'], '@');
160161
$container->getDefinition($decoratorRef)
161162
->addArgument(new Reference($driverRef));
162163

@@ -171,38 +172,42 @@ private function registerClientConfiguration(array $configs, ContainerBuilder $c
171172
}
172173
}
173174

174-
private function registerServerConfiguration(array $configs, ContainerBuilder $container): void
175+
private function registerServerConfiguration(array $config, ContainerBuilder $container): void
175176
{
176-
if (!isset($configs['server'])) {
177+
if (!isset($config['server'])) {
177178
return;
178179
}
179180

180-
if (isset($configs['server']['port'])) {
181-
$container->setParameter('gos_web_socket.server.port', $configs['server']['port']);
181+
if (isset($config['server']['port'])) {
182+
$container->setParameter('gos_web_socket.server.port', $config['server']['port']);
182183
}
183184

184-
if (isset($configs['server']['host'])) {
185-
$container->setParameter('gos_web_socket.server.host', $configs['server']['host']);
185+
if (isset($config['server']['host'])) {
186+
$container->setParameter('gos_web_socket.server.host', $config['server']['host']);
186187
}
187188

188-
if (isset($configs['server']['origin_check'])) {
189-
$container->setParameter('gos_web_socket.server.origin_check', $configs['server']['origin_check']);
189+
if (isset($config['server']['origin_check'])) {
190+
$container->setParameter('gos_web_socket.server.origin_check', $config['server']['origin_check']);
190191
}
191192

192-
if (isset($configs['server']['keepalive_ping'])) {
193-
$container->setParameter('gos_web_socket.server.keepalive_ping', $configs['server']['keepalive_ping']);
193+
if (isset($config['server']['ip_address_check'])) {
194+
$container->setParameter('gos_web_socket.server.ip_address_check', $config['server']['ip_address_check']);
194195
}
195196

196-
if (isset($configs['server']['keepalive_interval'])) {
197-
$container->setParameter('gos_web_socket.server.keepalive_interval', $configs['server']['keepalive_interval']);
197+
if (isset($config['server']['keepalive_ping'])) {
198+
$container->setParameter('gos_web_socket.server.keepalive_ping', $config['server']['keepalive_ping']);
198199
}
199200

200-
if (isset($configs['server']['router'])) {
201+
if (isset($config['server']['keepalive_interval'])) {
202+
$container->setParameter('gos_web_socket.server.keepalive_interval', $config['server']['keepalive_interval']);
203+
}
204+
205+
if (isset($config['server']['router'])) {
201206
$routerConfig = [];
202207

203208
// Adapt configuration based on the version of GosPubSubRouterBundle installed, if the XML loader is available the newer configuration structure is used
204-
if (isset($configs['server']['router']['resources'])) {
205-
foreach ($configs['server']['router']['resources'] as $resource) {
209+
if (isset($config['server']['router']['resources'])) {
210+
foreach ($config['server']['router']['resources'] as $resource) {
206211
if (\is_array($resource)) {
207212
$routerConfig[] = $resource;
208213
} else {
@@ -218,25 +223,30 @@ private function registerServerConfiguration(array $configs, ContainerBuilder $c
218223
}
219224
}
220225

221-
private function registerOriginsConfiguration(array $configs, ContainerBuilder $container): void
226+
private function registerOriginsConfiguration(array $config, ContainerBuilder $container): void
222227
{
223228
$originsRegistryDef = $container->getDefinition('gos_web_socket.registry.origins');
224229

225-
foreach ($configs['origins'] as $origin) {
230+
foreach ($config['origins'] as $origin) {
226231
$originsRegistryDef->addMethodCall('addOrigin', [$origin]);
227232
}
228233
}
229234

235+
private function registerBlockedIpAddressesConfiguration(array $config, ContainerBuilder $container): void
236+
{
237+
$container->setParameter('gos_web_socket.blocked_ip_addresses', $config['blocked_ip_addresses']);
238+
}
239+
230240
/**
231241
* @throws InvalidArgumentException if an unsupported ping service type is given
232242
*/
233-
private function registerPingConfiguration(array $configs, ContainerBuilder $container): void
243+
private function registerPingConfiguration(array $config, ContainerBuilder $container): void
234244
{
235-
if (!isset($configs['ping'])) {
245+
if (!isset($config['ping'])) {
236246
return;
237247
}
238248

239-
foreach ((array) $configs['ping']['services'] as $pingService) {
249+
foreach ((array) $config['ping']['services'] as $pingService) {
240250
switch ($pingService['type']) {
241251
case Configuration::PING_SERVICE_TYPE_DOCTRINE:
242252
$serviceRef = ltrim($pingService['name'], '@');
@@ -266,11 +276,11 @@ private function registerPingConfiguration(array $configs, ContainerBuilder $con
266276
}
267277
}
268278

269-
private function registerPushersConfiguration(array $configs, ContainerBuilder $container): void
279+
private function registerPushersConfiguration(array $config, ContainerBuilder $container): void
270280
{
271281
$usesSymfony51Api = method_exists(Definition::class, 'getDeprecation');
272282

273-
if (!isset($configs['pushers'])) {
283+
if (!isset($config['pushers'])) {
274284
// Remove all of the pushers
275285
foreach (['gos_web_socket.pusher.amqp', 'gos_web_socket.pusher.wamp'] as $pusher) {
276286
$container->removeDefinition($pusher);
@@ -283,9 +293,9 @@ private function registerPushersConfiguration(array $configs, ContainerBuilder $
283293
return;
284294
}
285295

286-
if (isset($configs['pushers']['amqp']) && $this->isConfigEnabled($container, $configs['pushers']['amqp'])) {
296+
if (isset($config['pushers']['amqp']) && $this->isConfigEnabled($container, $config['pushers']['amqp'])) {
287297
// Pull the 'enabled' field out of the pusher's config
288-
$factoryConfig = $configs['pushers']['amqp'];
298+
$factoryConfig = $config['pushers']['amqp'];
289299
unset($factoryConfig['enabled']);
290300

291301
$connectionFactoryDef = new Definition(
@@ -321,9 +331,9 @@ private function registerPushersConfiguration(array $configs, ContainerBuilder $
321331
$container->removeDefinition('gos_web_socket.pusher.amqp.push_handler');
322332
}
323333

324-
if (isset($configs['pushers']['wamp']) && $this->isConfigEnabled($container, $configs['pushers']['wamp'])) {
334+
if (isset($config['pushers']['wamp']) && $this->isConfigEnabled($container, $config['pushers']['wamp'])) {
325335
// Pull the 'enabled' field out of the pusher's config
326-
$factoryConfig = $configs['pushers']['wamp'];
336+
$factoryConfig = $config['pushers']['wamp'];
327337
unset($factoryConfig['enabled']);
328338

329339
$connectionFactoryDef = new Definition(
@@ -358,16 +368,16 @@ private function registerPushersConfiguration(array $configs, ContainerBuilder $
358368
}
359369
}
360370

361-
private function registerWebsocketClientConfiguration(array $configs, ContainerBuilder $container): void
371+
private function registerWebsocketClientConfiguration(array $config, ContainerBuilder $container): void
362372
{
363373
$usesSymfony51Api = method_exists(Definition::class, 'getDeprecation');
364374

365-
if (!isset($configs['websocket_client']) || !$configs['websocket_client']['enabled']) {
375+
if (!isset($config['websocket_client']) || !$config['websocket_client']['enabled']) {
366376
return;
367377
}
368378

369379
// Pull the 'enabled' field out of the client's config
370-
$factoryConfig = $configs['websocket_client'];
380+
$factoryConfig = $config['websocket_client'];
371381
unset($factoryConfig['enabled']);
372382

373383
$clientFactoryDef = new Definition(

src/Event/ClientRejectedEvent.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@
55
use Psr\Http\Message\RequestInterface;
66
use Symfony\Contracts\EventDispatcher\Event;
77

8+
trigger_deprecation('gos/web-socket-bundle', '3.8', 'The "%s" class is deprecated and will be removed in 4.0, subscribe to the "%s" event instead.', ClientRejectedEvent::class, ConnectionRejectedEvent::class);
9+
810
/**
911
* @author Johann Saunier <[email protected]>
12+
* @deprecated to be removed in 4.0, subscribe to the `Gos\Bundle\WebSocketBundle\Event\ConnectionRejectedEvent` event instead
1013
*/
1114
final class ClientRejectedEvent extends Event
1215
{
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace Gos\Bundle\WebSocketBundle\Event;
4+
5+
use Psr\Http\Message\RequestInterface;
6+
use Ratchet\ConnectionInterface;
7+
use Symfony\Contracts\EventDispatcher\Event;
8+
9+
final class ConnectionRejectedEvent extends Event
10+
{
11+
private ConnectionInterface $connection;
12+
private ?RequestInterface $request;
13+
14+
public function __construct(ConnectionInterface $connection, ?RequestInterface $request = null)
15+
{
16+
$this->connection = $connection;
17+
$this->request = $request;
18+
}
19+
20+
public function getConnection(): ConnectionInterface
21+
{
22+
return $this->connection;
23+
}
24+
25+
public function getRequest(): ?RequestInterface
26+
{
27+
return $this->request;
28+
}
29+
30+
public function hasRequest(): bool
31+
{
32+
return null !== $this->request;
33+
}
34+
}

src/GosWebSocketEvents.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use Gos\Bundle\WebSocketBundle\Event\ClientDisconnectedEvent;
77
use Gos\Bundle\WebSocketBundle\Event\ClientErrorEvent;
88
use Gos\Bundle\WebSocketBundle\Event\ClientRejectedEvent;
9+
use Gos\Bundle\WebSocketBundle\Event\ConnectionRejectedEvent;
910
use Gos\Bundle\WebSocketBundle\Event\PushHandlerFailEvent;
1011
use Gos\Bundle\WebSocketBundle\Event\PushHandlerSuccessEvent;
1112
use Gos\Bundle\WebSocketBundle\Event\ServerLaunchedEvent;
@@ -48,9 +49,18 @@ final class GosWebSocketEvents
4849
* The CLIENT_REJECTED event occurs when a client connection is rejected.
4950
*
5051
* @Event("Gos\Bundle\WebSocketBundle\Event\ClientRejectedEvent")
52+
*
53+
* @deprecated to be removed in 4.0, subscribe to the "gos_web_socket.connection_rejected" event instead
5154
*/
5255
public const CLIENT_REJECTED = 'gos_web_socket.client_rejected';
5356

57+
/**
58+
* The CLIENT_REJECTED event occurs when a connection is rejected.
59+
*
60+
* @Event("Gos\Bundle\WebSocketBundle\Event\ConnectionRejectedEvent")
61+
*/
62+
public const CONNECTION_REJECTED = 'gos_web_socket.connection_rejected';
63+
5464
/**
5565
* The PUSHER_FAIL event occurs when a push handler has an error pushing a message to a server.
5666
*
@@ -80,6 +90,7 @@ final class GosWebSocketEvents
8090
ClientDisconnectedEvent::class => self::CLIENT_DISCONNECTED,
8191
ClientErrorEvent::class => self::CLIENT_ERROR,
8292
ClientRejectedEvent::class => self::CLIENT_REJECTED,
93+
ConnectionRejectedEvent::class => self::CONNECTION_REJECTED,
8394
PushHandlerFailEvent::class => self::PUSHER_FAIL,
8495
PushHandlerSuccessEvent::class => self::PUSHER_SUCCESS,
8596
];

0 commit comments

Comments
 (0)