Skip to content

Commit 6231abc

Browse files
committed
Replace Server::setAuth() with optional constructor parameter
1 parent 5a14c29 commit 6231abc

5 files changed

Lines changed: 137 additions & 92 deletions

File tree

README.md

Lines changed: 27 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -628,6 +628,10 @@ $server->listen($socket);
628628
$loop->run();
629629
```
630630

631+
Additionally, the `Server` constructor accepts optional parameters to explicitly
632+
configure the [connector](#server-connector) to use and to require
633+
[authentication](#server-authentication). For more details, read on...
634+
631635
#### Server connector
632636

633637
The `Server` uses an instance of ReactPHP's
@@ -674,32 +678,43 @@ If a client tries to use any other protocol version, does not send along
674678
authentication details or if authentication details can not be verified,
675679
the connection will be rejected.
676680

677-
Because your authentication mechanism might take some time to actually check
678-
the provided authentication credentials (like querying a remote database or webservice),
679-
the server side uses a [Promise](https://github.com/reactphp/promise)-based interface.
680-
While this might seem complex at first, it actually provides a very simple way
681-
to handle simultanous connections in a non-blocking fashion and increases overall performance.
681+
If you only want to accept static authentication details, you can simply pass an
682+
additional assoc array with your authentication details to the `Server` like this:
682683

683-
You can use the `setAuth(callable $authenticator)` method to configure a callable
684+
```php
685+
$server = new Clue\React\Socks\Server($loop, null, array(
686+
'tom' => 'password',
687+
'admin' => 'root'
688+
));
689+
```
690+
691+
See also [example #12](examples).
692+
693+
If you want more control over authentication, you can pass an authenticator
684694
function that should return a `bool` value like this synchronous example:
685695

686696
```php
687-
$server->setAuth(function ($username, $password, $remote) {
697+
$server = new Clue\React\Socks\Server($loop, null, function ($user, $pass, $remote) {
688698
// $remote is a full URI à la socks5://user:pass@192.168.1.1:1234
689699
// or socks5s://user:pass@192.168.1.1:1234 for SOCKS over TLS
690700
// useful for logging or extracting parts, such as the remote IP
691701
$ip = parse_url($remote, PHP_URL_HOST);
692702

693-
return ($username === 'root' && $password === 'secret' && $ip === '127.0.0.1');
703+
return ($user === 'root' && $pass === 'secret' && $ip === '127.0.0.1');
694704
});
695705
```
696706

697-
Similarly, you can return a [Promise](https://github.com/reactphp/promise) from
698-
the authenticator function that will fulfill with a `bool` value like this async
699-
example:
707+
Because your authentication mechanism might take some time to actually check the
708+
provided authentication credentials (like querying a remote database or webservice),
709+
the server also supports a [Promise](https://github.com/reactphp/promise)-based
710+
interface. While this might seem more complex at first, it actually provides a
711+
very powerful way of handling a large number of connections concurrently without
712+
ever blocking any connections. You can return a [Promise](https://github.com/reactphp/promise)
713+
from the authenticator function that will fulfill with a `bool` value like this
714+
async example:
700715

701716
```php
702-
$server->setAuth(function ($username, $password) use ($db) {
717+
$server = new Clue\React\Socks\Server($loop, null, function ($user, $pass) use ($db) {
703718
// pseudo-code: query database for given authentication details
704719
return $db->query(
705720
'SELECT 1 FROM users WHERE name = ? AND password = ?',
@@ -711,24 +726,6 @@ $server->setAuth(function ($username, $password) use ($db) {
711726
});
712727
```
713728

714-
Or if you only accept static authentication details, you can use the simple
715-
array-based authentication method as a shortcut:
716-
717-
```PHP
718-
$server->setAuthArray(array(
719-
'tom' => 'password',
720-
'admin' => 'root'
721-
));
722-
```
723-
724-
See also [example #12](examples).
725-
726-
If you do not want to use authentication anymore:
727-
728-
```PHP
729-
$server->unsetAuth();
730-
```
731-
732729
#### Server proxy chaining
733730

734731
The `Server` is responsible for creating connections to the target host.

examples/12-server-with-password.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@
99

1010
// start a new SOCKS proxy server
1111
// require authentication and hence make this a SOCKS5-only server
12-
$server = new Server($loop);
13-
$server->setAuthArray(array(
12+
$server = new Server($loop, null, array(
1413
'tom' => 'god',
1514
'user' => 'p@ssw0rd'
1615
));

src/Server.php

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,40 @@ final class Server
3737

3838
private $connector;
3939

40-
private $auth = null;
40+
/**
41+
* @var null|callable
42+
*/
43+
private $auth;
4144

42-
public function __construct(LoopInterface $loop, ConnectorInterface $connector = null)
45+
/**
46+
* @param LoopInterface $loop
47+
* @param null|ConnectorInterface $connector
48+
* @param null|array|callable $auth
49+
*/
50+
public function __construct(LoopInterface $loop, ConnectorInterface $connector = null, $auth = null)
4351
{
4452
if ($connector === null) {
4553
$connector = new Connector($loop);
4654
}
4755

56+
if (\is_array($auth)) {
57+
// wrap authentication array in authentication callback
58+
$this->auth = function ($username, $password) use ($auth) {
59+
return \React\Promise\resolve(
60+
isset($auth[$username]) && (string)$auth[$username] === $password
61+
);
62+
};
63+
} elseif (\is_callable($auth)) {
64+
// wrap authentication callback in order to cast its return value to a promise
65+
$this->auth = function($username, $password, $remote) use ($auth) {
66+
return \React\Promise\resolve(
67+
\call_user_func($auth, $username, $password, $remote)
68+
);
69+
};
70+
} elseif ($auth !== null) {
71+
throw new \InvalidArgumentException('Invalid authenticator given');
72+
}
73+
4874
$this->loop = $loop;
4975
$this->connector = $connector;
5076
}
@@ -61,32 +87,6 @@ public function listen(ServerInterface $socket)
6187
});
6288
}
6389

64-
public function setAuth($auth)
65-
{
66-
if (!is_callable($auth)) {
67-
throw new InvalidArgumentException('Given authenticator is not a valid callable');
68-
}
69-
70-
// wrap authentication callback in order to cast its return value to a promise
71-
$this->auth = function($username, $password, $remote) use ($auth) {
72-
return \React\Promise\resolve(
73-
call_user_func($auth, $username, $password, $remote)
74-
);
75-
};
76-
}
77-
78-
public function setAuthArray(array $login)
79-
{
80-
$this->setAuth(function ($username, $password) use ($login) {
81-
return (isset($login[$username]) && (string)$login[$username] === $password);
82-
});
83-
}
84-
85-
public function unsetAuth()
86-
{
87-
$this->auth = null;
88-
}
89-
9090
/** @internal */
9191
public function onConnection(ConnectionInterface $connection)
9292
{

tests/FunctionalTest.php

Lines changed: 62 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -180,9 +180,8 @@ public function testConnectionSocksWithAuthenticationOverUnix()
180180

181181
$path = sys_get_temp_dir() . '/test' . mt_rand(1000, 9999) . '.sock';
182182
$socket = new UnixServer($path, $this->loop);
183-
$this->server = new Server($this->loop);
183+
$this->server = new Server($this->loop, null, array('name' => 'pass'));
184184
$this->server->listen($socket);
185-
$this->server->setAuthArray(array('name' => 'pass'));
186185

187186
$this->connector = new Connector($this->loop);
188187
$this->client = new Client('socks+unix://name:pass@' . $path, $this->connector);
@@ -195,7 +194,11 @@ public function testConnectionSocksWithAuthenticationOverUnix()
195194
/** @group internet */
196195
public function testConnectionAuthenticationFromUri()
197196
{
198-
$this->server->setAuthArray(array('name' => 'pass'));
197+
$this->server = new Server($this->loop, null, array('name' => 'pass'));
198+
199+
$socket = new React\Socket\Server(0, $this->loop);
200+
$this->server->listen($socket);
201+
$this->port = parse_url($socket->getAddress(), PHP_URL_PORT);
199202

200203
$this->client = new Client('name:pass@127.0.0.1:' . $this->port, $this->connector);
201204

@@ -207,7 +210,7 @@ public function testConnectionAuthenticationCallback()
207210
{
208211
$called = 0;
209212
$that = $this;
210-
$this->server->setAuth(function ($name, $pass, $remote) use ($that, &$called) {
213+
$this->server = new Server($this->loop, null, function ($name, $pass, $remote) use ($that, &$called) {
211214
++$called;
212215
$that->assertEquals('name', $name);
213216
$that->assertEquals('pass', $pass);
@@ -216,6 +219,10 @@ public function testConnectionAuthenticationCallback()
216219
return true;
217220
});
218221

222+
$socket = new React\Socket\Server(0, $this->loop);
223+
$this->server->listen($socket);
224+
$this->port = parse_url($socket->getAddress(), PHP_URL_PORT);
225+
219226
$this->client = new Client('name:pass@127.0.0.1:' . $this->port, $this->connector);
220227

221228
$this->assertResolveStream($this->client->connect('www.google.com:80'));
@@ -226,12 +233,16 @@ public function testConnectionAuthenticationCallback()
226233
public function testConnectionAuthenticationCallbackWillNotBeInvokedIfClientsSendsNoAuth()
227234
{
228235
$called = 0;
229-
$this->server->setAuth(function () use (&$called) {
236+
$this->server = new Server($this->loop, null, function () use (&$called) {
230237
++$called;
231238

232239
return true;
233240
});
234241

242+
$socket = new React\Socket\Server(0, $this->loop);
243+
$this->server->listen($socket);
244+
$this->port = parse_url($socket->getAddress(), PHP_URL_PORT);
245+
235246
$this->client = new Client('127.0.0.1:' . $this->port, $this->connector);
236247

237248
$this->assertRejectPromise($this->client->connect('www.google.com:80'));
@@ -241,7 +252,11 @@ public function testConnectionAuthenticationCallbackWillNotBeInvokedIfClientsSen
241252
/** @group internet */
242253
public function testConnectionAuthenticationFromUriEncoded()
243254
{
244-
$this->server->setAuthArray(array('name' => 'p@ss:w0rd'));
255+
$this->server = new Server($this->loop, null, array('name' => 'p@ss:w0rd'));
256+
257+
$socket = new React\Socket\Server(0, $this->loop);
258+
$this->server->listen($socket);
259+
$this->port = parse_url($socket->getAddress(), PHP_URL_PORT);
245260

246261
$this->client = new Client(rawurlencode('name') . ':' . rawurlencode('p@ss:w0rd') . '@127.0.0.1:' . $this->port, $this->connector);
247262

@@ -251,7 +266,11 @@ public function testConnectionAuthenticationFromUriEncoded()
251266
/** @group internet */
252267
public function testConnectionAuthenticationFromUriWithOnlyUserAndNoPassword()
253268
{
254-
$this->server->setAuthArray(array('empty' => ''));
269+
$this->server = new Server($this->loop, null, array('empty' => ''));
270+
271+
$socket = new React\Socket\Server(0, $this->loop);
272+
$this->server->listen($socket);
273+
$this->port = parse_url($socket->getAddress(), PHP_URL_PORT);
255274

256275
$this->client = new Client('empty@127.0.0.1:' . $this->port, $this->connector);
257276

@@ -261,7 +280,12 @@ public function testConnectionAuthenticationFromUriWithOnlyUserAndNoPassword()
261280
/** @group internet */
262281
public function testConnectionAuthenticationEmptyPassword()
263282
{
264-
$this->server->setAuthArray(array('user' => ''));
283+
$this->server = new Server($this->loop, null, array('user' => ''));
284+
285+
$socket = new React\Socket\Server(0, $this->loop);
286+
$this->server->listen($socket);
287+
$this->port = parse_url($socket->getAddress(), PHP_URL_PORT);
288+
265289
$this->client = new Client('user@127.0.0.1:' . $this->port, $this->connector);
266290

267291
$this->assertResolveStream($this->client->connect('www.google.com:80'));
@@ -277,7 +301,11 @@ public function testConnectionAuthenticationUnused()
277301

278302
public function testConnectionInvalidNoAuthenticationOverLegacySocks4()
279303
{
280-
$this->server->setAuthArray(array('name' => 'pass'));
304+
$this->server = new Server($this->loop, null, array('name' => 'pass'));
305+
306+
$socket = new React\Socket\Server(0, $this->loop);
307+
$this->server->listen($socket);
308+
$this->port = parse_url($socket->getAddress(), PHP_URL_PORT);
281309

282310
$this->client = new Client('socks4://127.0.0.1:' . $this->port, $this->connector);
283311

@@ -286,7 +314,11 @@ public function testConnectionInvalidNoAuthenticationOverLegacySocks4()
286314

287315
public function testConnectionInvalidNoAuthentication()
288316
{
289-
$this->server->setAuthArray(array('name' => 'pass'));
317+
$this->server = new Server($this->loop, null, array('name' => 'pass'));
318+
319+
$socket = new React\Socket\Server(0, $this->loop);
320+
$this->server->listen($socket);
321+
$this->port = parse_url($socket->getAddress(), PHP_URL_PORT);
290322

291323
$this->client = new Client('socks5://127.0.0.1:' . $this->port, $this->connector);
292324

@@ -295,7 +327,11 @@ public function testConnectionInvalidNoAuthentication()
295327

296328
public function testConnectionInvalidAuthenticationMismatch()
297329
{
298-
$this->server->setAuthArray(array('name' => 'pass'));
330+
$this->server = new Server($this->loop, null, array('name' => 'pass'));
331+
332+
$socket = new React\Socket\Server(0, $this->loop);
333+
$this->server->listen($socket);
334+
$this->port = parse_url($socket->getAddress(), PHP_URL_PORT);
299335

300336
$this->client = new Client('user:pass@127.0.0.1:' . $this->port, $this->connector);
301337

@@ -304,32 +340,44 @@ public function testConnectionInvalidAuthenticationMismatch()
304340

305341
public function testConnectionInvalidAuthenticatorReturnsFalse()
306342
{
307-
$this->server->setAuth(function () {
343+
$this->server = new Server($this->loop, null, function () {
308344
return false;
309345
});
310346

347+
$socket = new React\Socket\Server(0, $this->loop);
348+
$this->server->listen($socket);
349+
$this->port = parse_url($socket->getAddress(), PHP_URL_PORT);
350+
311351
$this->client = new Client('user:pass@127.0.0.1:' . $this->port, $this->connector);
312352

313353
$this->assertRejectPromise($this->client->connect('www.google.com:80'), null, SOCKET_EACCES);
314354
}
315355

316356
public function testConnectionInvalidAuthenticatorReturnsPromiseFulfilledWithFalse()
317357
{
318-
$this->server->setAuth(function () {
358+
$this->server = new Server($this->loop, null, function () {
319359
return \React\Promise\resolve(false);
320360
});
321361

362+
$socket = new React\Socket\Server(0, $this->loop);
363+
$this->server->listen($socket);
364+
$this->port = parse_url($socket->getAddress(), PHP_URL_PORT);
365+
322366
$this->client = new Client('user:pass@127.0.0.1:' . $this->port, $this->connector);
323367

324368
$this->assertRejectPromise($this->client->connect('www.google.com:80'), null, SOCKET_EACCES);
325369
}
326370

327371
public function testConnectionInvalidAuthenticatorReturnsPromiseRejected()
328372
{
329-
$this->server->setAuth(function () {
373+
$this->server = new Server($this->loop, null, function () {
330374
return \React\Promise\reject();
331375
});
332376

377+
$socket = new React\Socket\Server(0, $this->loop);
378+
$this->server->listen($socket);
379+
$this->port = parse_url($socket->getAddress(), PHP_URL_PORT);
380+
333381
$this->client = new Client('user:pass@127.0.0.1:' . $this->port, $this->connector);
334382

335383
$this->assertRejectPromise($this->client->connect('www.google.com:80'), null, SOCKET_EACCES);

0 commit comments

Comments
 (0)