Skip to content

Commit 5a14c29

Browse files
committed
Change Server::setAuth() to use bool as async promise resolution value
1 parent ace3394 commit 5a14c29

3 files changed

Lines changed: 73 additions & 23 deletions

File tree

README.md

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -676,21 +676,38 @@ the connection will be rejected.
676676

677677
Because your authentication mechanism might take some time to actually check
678678
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.
679+
the server side uses a [Promise](https://github.com/reactphp/promise)-based interface.
680680
While this might seem complex at first, it actually provides a very simple way
681681
to handle simultanous connections in a non-blocking fashion and increases overall performance.
682682

683-
```PHP
684-
$server->setAuth(function ($username, $password, $remote) {
685-
// either return a boolean success value right away
686-
// or use promises for delayed authentication
683+
You can use the `setAuth(callable $authenticator)` method to configure a callable
684+
function that should return a `bool` value like this synchronous example:
687685

686+
```php
687+
$server->setAuth(function ($username, $password, $remote) {
688688
// $remote is a full URI à la socks5://user:pass@192.168.1.1:1234
689689
// or socks5s://user:pass@192.168.1.1:1234 for SOCKS over TLS
690690
// useful for logging or extracting parts, such as the remote IP
691691
$ip = parse_url($remote, PHP_URL_HOST);
692692

693-
return ($username === 'root' && $ip === '127.0.0.1');
693+
return ($username === 'root' && $password === 'secret' && $ip === '127.0.0.1');
694+
});
695+
```
696+
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:
700+
701+
```php
702+
$server->setAuth(function ($username, $password) use ($db) {
703+
// pseudo-code: query database for given authentication details
704+
return $db->query(
705+
'SELECT 1 FROM users WHERE name = ? AND password = ?',
706+
array($username, $password)
707+
)->then(function (QueryResult $result) {
708+
// ensure we find exactly one match in the database
709+
return count($result->resultRows) === 1;
710+
});
694711
});
695712
```
696713

src/Server.php

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33
namespace Clue\React\Socks;
44

55
use React\Socket\ServerInterface;
6-
use React\Promise;
7-
use React\Promise\Deferred;
86
use React\Promise\PromiseInterface;
97
use React\Socket\ConnectorInterface;
108
use React\Socket\Connector;
@@ -71,13 +69,9 @@ public function setAuth($auth)
7169

7270
// wrap authentication callback in order to cast its return value to a promise
7371
$this->auth = function($username, $password, $remote) use ($auth) {
74-
$ret = call_user_func($auth, $username, $password, $remote);
75-
if ($ret instanceof PromiseInterface) {
76-
return $ret;
77-
}
78-
$deferred = new Deferred();
79-
$ret ? $deferred->resolve() : $deferred->reject();
80-
return $deferred->promise();
72+
return \React\Promise\resolve(
73+
call_user_func($auth, $username, $password, $remote)
74+
);
8175
};
8276
}
8377

@@ -215,7 +209,7 @@ public function handleSocks4(ConnectionInterface $stream, StreamReader $reader)
215209
}
216210

217211
/** @internal */
218-
public function handleSocks5(ConnectionInterface $stream, $auth=null, StreamReader $reader)
212+
public function handleSocks5(ConnectionInterface $stream, $auth, StreamReader $reader)
219213
{
220214
$remote = $stream->getRemoteAddress();
221215
if ($remote !== null) {
@@ -255,13 +249,19 @@ public function handleSocks5(ConnectionInterface $stream, $auth=null, StreamRead
255249
$remote = str_replace('://', '://' . rawurlencode($username) . ':' . rawurlencode($password) . '@', $remote);
256250
}
257251

258-
return $auth($username, $password, $remote)->then(function () use ($stream) {
259-
// accept
260-
$stream->write(pack('C2', 0x01, 0x00));
261-
}, function() use ($stream) {
262-
// reject => send any code but 0x00
252+
return $auth($username, $password, $remote)->then(function ($authenticated) use ($stream) {
253+
if ($authenticated) {
254+
// accept auth
255+
$stream->write(pack('C2', 0x01, 0x00));
256+
} else {
257+
// reject auth => send any code but 0x00
258+
$stream->end(pack('C2', 0x01, 0xFF));
259+
throw new UnexpectedValueException('Authentication denied');
260+
}
261+
}, function ($e) use ($stream) {
262+
// reject failed authentication => send any code but 0x00
263263
$stream->end(pack('C2', 0x01, 0xFF));
264-
throw new UnexpectedValueException('Unable to authenticate');
264+
throw new UnexpectedValueException('Authentication error', 0, $e);
265265
});
266266
});
267267
});
@@ -336,7 +336,7 @@ public function connectTarget(ConnectionInterface $stream, array $target)
336336
// validate URI so a string hostname can not pass excessive URI parts
337337
$parts = parse_url('tcp://' . $uri);
338338
if (!$parts || !isset($parts['scheme'], $parts['host'], $parts['port']) || count($parts) !== 3) {
339-
return Promise\reject(new InvalidArgumentException('Invalid target URI given'));
339+
return \React\Promise\reject(new InvalidArgumentException('Invalid target URI given'));
340340
}
341341

342342
if (isset($target[2])) {

tests/FunctionalTest.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,39 @@ public function testConnectionInvalidAuthenticationMismatch()
302302
$this->assertRejectPromise($this->client->connect('www.google.com:80'), null, SOCKET_EACCES);
303303
}
304304

305+
public function testConnectionInvalidAuthenticatorReturnsFalse()
306+
{
307+
$this->server->setAuth(function () {
308+
return false;
309+
});
310+
311+
$this->client = new Client('user:pass@127.0.0.1:' . $this->port, $this->connector);
312+
313+
$this->assertRejectPromise($this->client->connect('www.google.com:80'), null, SOCKET_EACCES);
314+
}
315+
316+
public function testConnectionInvalidAuthenticatorReturnsPromiseFulfilledWithFalse()
317+
{
318+
$this->server->setAuth(function () {
319+
return \React\Promise\resolve(false);
320+
});
321+
322+
$this->client = new Client('user:pass@127.0.0.1:' . $this->port, $this->connector);
323+
324+
$this->assertRejectPromise($this->client->connect('www.google.com:80'), null, SOCKET_EACCES);
325+
}
326+
327+
public function testConnectionInvalidAuthenticatorReturnsPromiseRejected()
328+
{
329+
$this->server->setAuth(function () {
330+
return \React\Promise\reject();
331+
});
332+
333+
$this->client = new Client('user:pass@127.0.0.1:' . $this->port, $this->connector);
334+
335+
$this->assertRejectPromise($this->client->connect('www.google.com:80'), null, SOCKET_EACCES);
336+
}
337+
305338
/** @group internet */
306339
public function testConnectorOkay()
307340
{

0 commit comments

Comments
 (0)