mirror of
https://github.com/bitinflow/expose.git
synced 2026-03-15 14:35:55 +00:00
Allow specifying maximum connection counts per user
This commit is contained in:
@@ -29,4 +29,8 @@ interface ConnectionManager
|
|||||||
public function getConnectionsForAuthToken(string $authToken): array;
|
public function getConnectionsForAuthToken(string $authToken): array;
|
||||||
|
|
||||||
public function getTcpConnectionsForAuthToken(string $authToken): array;
|
public function getTcpConnectionsForAuthToken(string $authToken): array;
|
||||||
|
|
||||||
|
public function findControlConnectionsForIp(string $ip): array;
|
||||||
|
|
||||||
|
public function findControlConnectionsForAuthToken(string $token): array;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use App\Contracts\ConnectionManager as ConnectionManagerContract;
|
|||||||
use App\Contracts\SubdomainGenerator;
|
use App\Contracts\SubdomainGenerator;
|
||||||
use App\Http\QueryParameters;
|
use App\Http\QueryParameters;
|
||||||
use App\Server\Exceptions\NoFreePortAvailable;
|
use App\Server\Exceptions\NoFreePortAvailable;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
use Ratchet\ConnectionInterface;
|
use Ratchet\ConnectionInterface;
|
||||||
use React\EventLoop\LoopInterface;
|
use React\EventLoop\LoopInterface;
|
||||||
use React\Socket\Server;
|
use React\Socket\Server;
|
||||||
@@ -157,6 +158,20 @@ class ConnectionManager implements ConnectionManagerContract
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function findControlConnectionsForIp(string $ip): array
|
||||||
|
{
|
||||||
|
return collect($this->connections)->filter(function (ControlConnection $connection) use ($ip) {
|
||||||
|
return $connection->socket->remoteAddress == $ip;
|
||||||
|
})->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function findControlConnectionsForAuthToken(string $token): array
|
||||||
|
{
|
||||||
|
return collect($this->connections)->filter(function (ControlConnection $connection) use ($token) {
|
||||||
|
return $connection->authToken === $token;
|
||||||
|
})->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
public function getConnections(): array
|
public function getConnections(): array
|
||||||
{
|
{
|
||||||
return $this->connections;
|
return $this->connections;
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ class StoreUsersController extends AdminController
|
|||||||
'auth_token' => (string) Str::uuid(),
|
'auth_token' => (string) Str::uuid(),
|
||||||
'can_specify_subdomains' => (int) $request->get('can_specify_subdomains'),
|
'can_specify_subdomains' => (int) $request->get('can_specify_subdomains'),
|
||||||
'can_share_tcp_ports' => (int) $request->get('can_share_tcp_ports'),
|
'can_share_tcp_ports' => (int) $request->get('can_share_tcp_ports'),
|
||||||
|
'max_connections' => (int) $request->get('max_connections'),
|
||||||
];
|
];
|
||||||
|
|
||||||
$this->userRepository
|
$this->userRepository
|
||||||
|
|||||||
@@ -7,11 +7,13 @@ use App\Contracts\SubdomainRepository;
|
|||||||
use App\Contracts\UserRepository;
|
use App\Contracts\UserRepository;
|
||||||
use App\Http\QueryParameters;
|
use App\Http\QueryParameters;
|
||||||
use App\Server\Exceptions\NoFreePortAvailable;
|
use App\Server\Exceptions\NoFreePortAvailable;
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
use Ratchet\ConnectionInterface;
|
use Ratchet\ConnectionInterface;
|
||||||
use Ratchet\WebSocket\MessageComponentInterface;
|
use Ratchet\WebSocket\MessageComponentInterface;
|
||||||
use React\Promise\Deferred;
|
use React\Promise\Deferred;
|
||||||
use React\Promise\PromiseInterface;
|
use React\Promise\PromiseInterface;
|
||||||
use stdClass;
|
use stdClass;
|
||||||
|
use function React\Promise\reject;
|
||||||
|
|
||||||
class ControlMessageController implements MessageComponentInterface
|
class ControlMessageController implements MessageComponentInterface
|
||||||
{
|
{
|
||||||
@@ -85,7 +87,35 @@ class ControlMessageController implements MessageComponentInterface
|
|||||||
|
|
||||||
protected function authenticate(ConnectionInterface $connection, $data)
|
protected function authenticate(ConnectionInterface $connection, $data)
|
||||||
{
|
{
|
||||||
|
if (! isset($data->subdomain)) {
|
||||||
|
$data->subdomain = null;
|
||||||
|
}
|
||||||
|
|
||||||
$this->verifyAuthToken($connection)
|
$this->verifyAuthToken($connection)
|
||||||
|
->then(function ($user) use ($connection) {
|
||||||
|
$maximumConnectionCount = config('expose.admin.maximum_open_connections_per_user', 0);
|
||||||
|
|
||||||
|
if (is_null($user)) {
|
||||||
|
$connectionCount = count($this->connectionManager->findControlConnectionsForIp($connection->remoteAddress));
|
||||||
|
} else {
|
||||||
|
$maximumConnectionCount = Arr::get($user, 'max_connections', $maximumConnectionCount);
|
||||||
|
|
||||||
|
$connectionCount = count($this->connectionManager->findControlConnectionsForAuthToken($user['auth_token']));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($maximumConnectionCount > 0 && $connectionCount + 1 > $maximumConnectionCount) {
|
||||||
|
$connection->send(json_encode([
|
||||||
|
'event' => 'authenticationFailed',
|
||||||
|
'data' => [
|
||||||
|
'message' => config('expose.admin.messages.maximum_connection_count'),
|
||||||
|
],
|
||||||
|
]));
|
||||||
|
$connection->close();
|
||||||
|
|
||||||
|
reject(null);
|
||||||
|
}
|
||||||
|
return $user;
|
||||||
|
})
|
||||||
->then(function ($user) use ($connection, $data) {
|
->then(function ($user) use ($connection, $data) {
|
||||||
if ($data->type === 'http') {
|
if ($data->type === 'http') {
|
||||||
$this->handleHttpConnection($connection, $data, $user);
|
$this->handleHttpConnection($connection, $data, $user);
|
||||||
|
|||||||
@@ -132,8 +132,8 @@ class DatabaseUserRepository implements UserRepository
|
|||||||
$deferred = new Deferred();
|
$deferred = new Deferred();
|
||||||
|
|
||||||
$this->database->query("
|
$this->database->query("
|
||||||
INSERT INTO users (name, auth_token, can_specify_subdomains, can_share_tcp_ports, created_at)
|
INSERT INTO users (name, auth_token, can_specify_subdomains, can_share_tcp_ports, max_connections, created_at)
|
||||||
VALUES (:name, :auth_token, :can_specify_subdomains, :can_share_tcp_ports, DATETIME('now'))
|
VALUES (:name, :auth_token, :can_specify_subdomains, :can_share_tcp_ports, :max_connections, DATETIME('now'))
|
||||||
", $data)
|
", $data)
|
||||||
->then(function (Result $result) use ($deferred) {
|
->then(function (Result $result) use ($deferred) {
|
||||||
$this->database->query('SELECT * FROM users WHERE id = :id', ['id' => $result->insertId])
|
$this->database->query('SELECT * FROM users WHERE id = :id', ['id' => $result->insertId])
|
||||||
|
|||||||
BIN
builds/expose
BIN
builds/expose
Binary file not shown.
@@ -224,6 +224,21 @@ return [
|
|||||||
*/
|
*/
|
||||||
'maximum_connection_length' => 0,
|
'maximum_connection_length' => 0,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Maximum number of open connections
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| You can limit the amount of connections that one client/user can have
|
||||||
|
| open. A maximum connection count of 0 means that clients can open
|
||||||
|
| as many connections as they want.
|
||||||
|
|
|
||||||
|
| When creating users with the API/admin interface, you can
|
||||||
|
| override this setting per user.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
'maximum_open_connections_per_user' => 0,
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Subdomain
|
| Subdomain
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE users ADD max_connections INTEGER NOT NULL DEFAULT 0;
|
||||||
@@ -63,6 +63,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:pt-5">
|
||||||
|
<label for="max_connections"
|
||||||
|
class="block text-sm font-medium leading-5 text-gray-700 sm:mt-px sm:pt-2">
|
||||||
|
Maximum Open Connections
|
||||||
|
</label>
|
||||||
|
<div class="mt-1 sm:mt-0 sm:col-span-2">
|
||||||
|
<div class="max-w-lg flex rounded-md shadow-sm">
|
||||||
|
<input id="max_connections"
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
v-model="userForm.max_connections"
|
||||||
|
class="flex-1 border-gray-300 block w-full rounded-md transition duration-150 ease-in-out sm:text-sm sm:leading-5"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-8 border-t border-gray-200 pt-5">
|
<div class="mt-8 border-t border-gray-200 pt-5">
|
||||||
<div class="flex justify-end">
|
<div class="flex justify-end">
|
||||||
@@ -99,6 +114,9 @@
|
|||||||
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
|
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
|
||||||
Auth-Token
|
Auth-Token
|
||||||
</th>
|
</th>
|
||||||
|
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
|
||||||
|
Max Connections
|
||||||
|
</th>
|
||||||
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
|
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
|
||||||
Custom Subdomains
|
Custom Subdomains
|
||||||
</th>
|
</th>
|
||||||
@@ -119,6 +137,9 @@
|
|||||||
<td class="px-6 py-4 whitespace-no-wrap border-b border-gray-200 text-sm leading-5 text-gray-500">
|
<td class="px-6 py-4 whitespace-no-wrap border-b border-gray-200 text-sm leading-5 text-gray-500">
|
||||||
@{ user.auth_token }
|
@{ user.auth_token }
|
||||||
</td>
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap border-b border-gray-200 text-sm leading-5 text-gray-500">
|
||||||
|
@{ user.max_connections }
|
||||||
|
</td>
|
||||||
<td class="px-6 py-4 whitespace-no-wrap border-b border-gray-200 text-sm leading-5 text-gray-500">
|
<td class="px-6 py-4 whitespace-no-wrap border-b border-gray-200 text-sm leading-5 text-gray-500">
|
||||||
<span v-if="user.can_specify_subdomains === 0">
|
<span v-if="user.can_specify_subdomains === 0">
|
||||||
No
|
No
|
||||||
@@ -186,6 +207,7 @@
|
|||||||
name: '',
|
name: '',
|
||||||
can_specify_subdomains: true,
|
can_specify_subdomains: true,
|
||||||
can_share_tcp_ports: true,
|
can_share_tcp_ports: true,
|
||||||
|
max_connections: 0,
|
||||||
errors: {},
|
errors: {},
|
||||||
},
|
},
|
||||||
paginated: {{ paginated|json_encode|raw }}
|
paginated: {{ paginated|json_encode|raw }}
|
||||||
@@ -242,6 +264,7 @@
|
|||||||
this.userForm.name = '';
|
this.userForm.name = '';
|
||||||
this.userForm.can_specify_subdomains = true;
|
this.userForm.can_specify_subdomains = true;
|
||||||
this.userForm.can_share_tcp_ports = true;
|
this.userForm.can_share_tcp_ports = true;
|
||||||
|
this.userForm.max_connections = 0;
|
||||||
this.userForm.errors = {};
|
this.userForm.errors = {};
|
||||||
this.users.unshift(data.user);
|
this.users.unshift(data.user);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -135,16 +135,10 @@ class TunnelTest extends TestCase
|
|||||||
|
|
||||||
$this->app['config']['expose.admin.validate_auth_tokens'] = true;
|
$this->app['config']['expose.admin.validate_auth_tokens'] = true;
|
||||||
|
|
||||||
$response = $this->await($this->browser->post('http://127.0.0.1:8080/api/users', [
|
$user = $this->createUser([
|
||||||
'Host' => 'expose.localhost',
|
|
||||||
'Authorization' => base64_encode('username:secret'),
|
|
||||||
'Content-Type' => 'application/json',
|
|
||||||
], json_encode([
|
|
||||||
'name' => 'Marcel',
|
'name' => 'Marcel',
|
||||||
'can_share_tcp_ports' => 0,
|
'can_share_tcp_ports' => 0,
|
||||||
])));
|
]);
|
||||||
|
|
||||||
$user = json_decode($response->getBody()->getContents())->user;
|
|
||||||
|
|
||||||
$this->expectException(\UnexpectedValueException::class);
|
$this->expectException(\UnexpectedValueException::class);
|
||||||
|
|
||||||
@@ -163,16 +157,10 @@ class TunnelTest extends TestCase
|
|||||||
|
|
||||||
$this->app['config']['expose.admin.validate_auth_tokens'] = true;
|
$this->app['config']['expose.admin.validate_auth_tokens'] = true;
|
||||||
|
|
||||||
$response = $this->await($this->browser->post('http://127.0.0.1:8080/api/users', [
|
$user = $this->createUser([
|
||||||
'Host' => 'expose.localhost',
|
|
||||||
'Authorization' => base64_encode('username:secret'),
|
|
||||||
'Content-Type' => 'application/json',
|
|
||||||
], json_encode([
|
|
||||||
'name' => 'Marcel',
|
'name' => 'Marcel',
|
||||||
'can_share_tcp_ports' => 1,
|
'can_share_tcp_ports' => 1,
|
||||||
])));
|
]);
|
||||||
|
|
||||||
$user = json_decode($response->getBody()->getContents())->user;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* We create an expose client that connects to our server and shares
|
* We create an expose client that connects to our server and shares
|
||||||
@@ -213,16 +201,10 @@ class TunnelTest extends TestCase
|
|||||||
{
|
{
|
||||||
$this->app['config']['expose.admin.validate_auth_tokens'] = true;
|
$this->app['config']['expose.admin.validate_auth_tokens'] = true;
|
||||||
|
|
||||||
$response = $this->await($this->browser->post('http://127.0.0.1:8080/api/users', [
|
$user = $this->createUser([
|
||||||
'Host' => 'expose.localhost',
|
|
||||||
'Authorization' => base64_encode('username:secret'),
|
|
||||||
'Content-Type' => 'application/json',
|
|
||||||
], json_encode([
|
|
||||||
'name' => 'Marcel',
|
'name' => 'Marcel',
|
||||||
'can_specify_subdomains' => 1,
|
'can_specify_subdomains' => 1,
|
||||||
])));
|
]);
|
||||||
|
|
||||||
$user = json_decode($response->getBody()->getContents())->user;
|
|
||||||
|
|
||||||
$this->createTestHttpServer();
|
$this->createTestHttpServer();
|
||||||
|
|
||||||
@@ -241,16 +223,10 @@ class TunnelTest extends TestCase
|
|||||||
{
|
{
|
||||||
$this->app['config']['expose.admin.validate_auth_tokens'] = true;
|
$this->app['config']['expose.admin.validate_auth_tokens'] = true;
|
||||||
|
|
||||||
$response = $this->await($this->browser->post('http://127.0.0.1:8080/api/users', [
|
$user = $this->createUser([
|
||||||
'Host' => 'expose.localhost',
|
|
||||||
'Authorization' => base64_encode('username:secret'),
|
|
||||||
'Content-Type' => 'application/json',
|
|
||||||
], json_encode([
|
|
||||||
'name' => 'Marcel',
|
'name' => 'Marcel',
|
||||||
'can_specify_subdomains' => 0,
|
'can_specify_subdomains' => 0,
|
||||||
])));
|
]);
|
||||||
|
|
||||||
$user = json_decode($response->getBody()->getContents())->user;
|
|
||||||
|
|
||||||
$this->createTestHttpServer();
|
$this->createTestHttpServer();
|
||||||
|
|
||||||
@@ -269,16 +245,10 @@ class TunnelTest extends TestCase
|
|||||||
{
|
{
|
||||||
$this->app['config']['expose.admin.validate_auth_tokens'] = true;
|
$this->app['config']['expose.admin.validate_auth_tokens'] = true;
|
||||||
|
|
||||||
$response = $this->await($this->browser->post('http://127.0.0.1:8080/api/users', [
|
$user = $this->createUser([
|
||||||
'Host' => 'expose.localhost',
|
|
||||||
'Authorization' => base64_encode('username:secret'),
|
|
||||||
'Content-Type' => 'application/json',
|
|
||||||
], json_encode([
|
|
||||||
'name' => 'Marcel',
|
'name' => 'Marcel',
|
||||||
'can_specify_subdomains' => 1,
|
'can_specify_subdomains' => 1,
|
||||||
])));
|
]);
|
||||||
|
|
||||||
$user = json_decode($response->getBody()->getContents())->user;
|
|
||||||
|
|
||||||
$response = $this->await($this->browser->post('http://127.0.0.1:8080/api/subdomains', [
|
$response = $this->await($this->browser->post('http://127.0.0.1:8080/api/subdomains', [
|
||||||
'Host' => 'expose.localhost',
|
'Host' => 'expose.localhost',
|
||||||
@@ -289,16 +259,10 @@ class TunnelTest extends TestCase
|
|||||||
'auth_token' => $user->auth_token,
|
'auth_token' => $user->auth_token,
|
||||||
])));
|
])));
|
||||||
|
|
||||||
$response = $this->await($this->browser->post('http://127.0.0.1:8080/api/users', [
|
$user = $this->createUser([
|
||||||
'Host' => 'expose.localhost',
|
|
||||||
'Authorization' => base64_encode('username:secret'),
|
|
||||||
'Content-Type' => 'application/json',
|
|
||||||
], json_encode([
|
|
||||||
'name' => 'Test-User',
|
'name' => 'Test-User',
|
||||||
'can_specify_subdomains' => 1,
|
'can_specify_subdomains' => 1,
|
||||||
])));
|
]);
|
||||||
|
|
||||||
$user = json_decode($response->getBody()->getContents())->user;
|
|
||||||
|
|
||||||
$this->createTestHttpServer();
|
$this->createTestHttpServer();
|
||||||
|
|
||||||
@@ -318,16 +282,10 @@ class TunnelTest extends TestCase
|
|||||||
{
|
{
|
||||||
$this->app['config']['expose.admin.validate_auth_tokens'] = true;
|
$this->app['config']['expose.admin.validate_auth_tokens'] = true;
|
||||||
|
|
||||||
$response = $this->await($this->browser->post('http://127.0.0.1:8080/api/users', [
|
$user = $this->createUser([
|
||||||
'Host' => 'expose.localhost',
|
|
||||||
'Authorization' => base64_encode('username:secret'),
|
|
||||||
'Content-Type' => 'application/json',
|
|
||||||
], json_encode([
|
|
||||||
'name' => 'Marcel',
|
'name' => 'Marcel',
|
||||||
'can_specify_subdomains' => 1,
|
'can_specify_subdomains' => 1,
|
||||||
])));
|
]);
|
||||||
|
|
||||||
$user = json_decode($response->getBody()->getContents())->user;
|
|
||||||
|
|
||||||
$response = $this->await($this->browser->post('http://127.0.0.1:8080/api/subdomains', [
|
$response = $this->await($this->browser->post('http://127.0.0.1:8080/api/subdomains', [
|
||||||
'Host' => 'expose.localhost',
|
'Host' => 'expose.localhost',
|
||||||
@@ -350,20 +308,11 @@ class TunnelTest extends TestCase
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** @test */
|
/** @test */
|
||||||
public function it_allows_clients_to_use_random_subdomains_if_custom_subdomains_are_forbidden()
|
public function it_rejects_clients_with_too_many_connections()
|
||||||
{
|
{
|
||||||
$this->app['config']['expose.admin.validate_auth_tokens'] = true;
|
$this->expectException(\UnexpectedValueException::class);
|
||||||
|
$this->app['config']['expose.admin.validate_auth_tokens'] = false;
|
||||||
$response = $this->await($this->browser->post('http://127.0.0.1:8080/api/users', [
|
$this->app['config']['expose.admin.maximum_open_connections_per_user'] = 1;
|
||||||
'Host' => 'expose.localhost',
|
|
||||||
'Authorization' => base64_encode('username:secret'),
|
|
||||||
'Content-Type' => 'application/json',
|
|
||||||
], json_encode([
|
|
||||||
'name' => 'Marcel',
|
|
||||||
'can_specify_subdomains' => 0,
|
|
||||||
])));
|
|
||||||
|
|
||||||
$user = json_decode($response->getBody()->getContents())->user;
|
|
||||||
|
|
||||||
$this->createTestHttpServer();
|
$this->createTestHttpServer();
|
||||||
|
|
||||||
@@ -372,9 +321,51 @@ class TunnelTest extends TestCase
|
|||||||
* the created test HTTP server.
|
* the created test HTTP server.
|
||||||
*/
|
*/
|
||||||
$client = $this->createClient();
|
$client = $this->createClient();
|
||||||
$response = $this->await($client->connectToServer('127.0.0.1:8085', '', $user->auth_token));
|
$this->await($client->connectToServer('127.0.0.1:8085', 'tunnel-1'));
|
||||||
|
$this->await($client->connectToServer('127.0.0.1:8085', 'tunnel-2'));
|
||||||
|
}
|
||||||
|
|
||||||
$this->assertInstanceOf(\stdClass::class, $response);
|
/** @test */
|
||||||
|
public function it_rejects_users_with_custom_max_connection_limit()
|
||||||
|
{
|
||||||
|
$this->expectException(\UnexpectedValueException::class);
|
||||||
|
$this->app['config']['expose.admin.validate_auth_tokens'] = true;
|
||||||
|
$this->app['config']['expose.admin.maximum_open_connections_per_user'] = 5;
|
||||||
|
|
||||||
|
$user = $this->createUser([
|
||||||
|
'name' => 'Marcel',
|
||||||
|
'can_specify_subdomains' => 1,
|
||||||
|
'max_connections' => 2,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->createTestHttpServer();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We create an expose client that connects to our server and shares
|
||||||
|
* the created test HTTP server.
|
||||||
|
*/
|
||||||
|
$client = $this->createClient();
|
||||||
|
$this->await($client->connectToServer('127.0.0.1:8085', 'tunnel-1', $user->auth_token));
|
||||||
|
$this->await($client->connectToServer('127.0.0.1:8085', 'tunnel-2', $user->auth_token));
|
||||||
|
$this->await($client->connectToServer('127.0.0.1:8085', 'tunnel-3', $user->auth_token));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function it_allows_clients_to_use_random_subdomains_if_custom_subdomains_are_forbidden()
|
||||||
|
{
|
||||||
|
$this->app['config']['expose.admin.validate_auth_tokens'] = true;
|
||||||
|
|
||||||
|
$user = $this->createUser([
|
||||||
|
'name' => 'Marcel',
|
||||||
|
'can_specify_subdomains' => 0,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->createTestHttpServer();
|
||||||
|
|
||||||
|
$client = $this->createClient();
|
||||||
|
$response = $this->await($client->connectToServer('127.0.0.1:8085', 'foo', $user->auth_token));
|
||||||
|
|
||||||
|
$this->assertNotSame('foo', $response->subdomain);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function startServer()
|
protected function startServer()
|
||||||
@@ -405,6 +396,17 @@ class TunnelTest extends TestCase
|
|||||||
return app(Client::class);
|
return app(Client::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function createUser(array $data)
|
||||||
|
{
|
||||||
|
$response = $this->await($this->browser->post('http://127.0.0.1:8080/api/users', [
|
||||||
|
'Host' => 'expose.localhost',
|
||||||
|
'Authorization' => base64_encode('username:secret'),
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
], json_encode($data)));
|
||||||
|
|
||||||
|
return json_decode($response->getBody()->getContents())->user;
|
||||||
|
}
|
||||||
|
|
||||||
protected function createTestHttpServer()
|
protected function createTestHttpServer()
|
||||||
{
|
{
|
||||||
$server = new Server($this->loop, function (ServerRequestInterface $request) {
|
$server = new Server($this->loop, function (ServerRequestInterface $request) {
|
||||||
|
|||||||
Reference in New Issue
Block a user