Merge branch 'master' into tcp

This commit is contained in:
Marcel Pociot
2020-09-07 20:52:19 +02:00
19 changed files with 763 additions and 282 deletions

View File

@@ -8,12 +8,12 @@ class ShareCurrentWorkingDirectoryCommand extends ShareCommand
public function handle()
{
$host = $this->prepareSharedHost(basename(getcwd()).'.'.$this->detectTld());
$subdomain = $this->detectName();
$host = $this->prepareSharedHost($subdomain.'.'.$this->detectTld());
$this->input->setArgument('host', $host);
if (! $this->option('subdomain')) {
$subdomain = str_replace('.', '-', basename(getcwd()));
$this->input->setOption('subdomain', $subdomain);
}
@@ -33,6 +33,32 @@ class ShareCurrentWorkingDirectoryCommand extends ShareCommand
return config('expose.default_tld', 'test');
}
protected function detectName(): string
{
$projectPath = getcwd();
$valetSitesPath = ($_SERVER['HOME'] ?? $_SERVER['USERPROFILE']).DIRECTORY_SEPARATOR.'.config'.DIRECTORY_SEPARATOR.'valet'.DIRECTORY_SEPARATOR.'Sites';
if (is_dir($valetSitesPath)) {
$site = collect(scandir($valetSitesPath))
->skip(2)
->map(function ($site) use ($valetSitesPath) {
return $valetSitesPath.DIRECTORY_SEPARATOR.$site;
})->mapWithKeys(function ($site) {
return [$site => readlink($site)];
})->filter(function ($sourcePath) use ($projectPath) {
return $sourcePath === $projectPath;
})
->keys()
->first();
if ($site) {
$projectPath = $site;
}
}
return str_replace('.', '-', basename($projectPath));
}
protected function prepareSharedHost($host): string
{
$certificateFile = ($_SERVER['HOME'] ?? $_SERVER['USERPROFILE']).DIRECTORY_SEPARATOR.'.config'.DIRECTORY_SEPARATOR.'valet'.DIRECTORY_SEPARATOR.'Certificates'.DIRECTORY_SEPARATOR.$host.'.crt';

View File

@@ -25,4 +25,6 @@ interface ConnectionManager
public function findControlConnectionForClientId(string $clientId): ?ControlConnection;
public function getConnections(): array;
public function getConnectionsForAuthToken(string $authToken): array;
}

View File

@@ -4,6 +4,7 @@ namespace App\Server\Connections;
use App\Contracts\ConnectionManager as ConnectionManagerContract;
use App\Contracts\SubdomainGenerator;
use App\Http\QueryParameters;
use Ratchet\ConnectionInterface;
use React\EventLoop\LoopInterface;
use React\Socket\Server;
@@ -47,7 +48,13 @@ class ConnectionManager implements ConnectionManagerContract
$connection->client_id = $clientId;
$storedConnection = new ControlConnection($connection, $host, $subdomain ?? $this->subdomainGenerator->generateSubdomain(), $clientId);
$storedConnection = new ControlConnection(
$connection,
$host,
$subdomain ?? $this->subdomainGenerator->generateSubdomain(),
$clientId,
$this->getAuthTokenFromConnection($connection)
);
$this->connections[] = $storedConnection;
@@ -60,7 +67,13 @@ class ConnectionManager implements ConnectionManagerContract
$connection->client_id = $clientId;
$storedConnection = new TcpControlConnection($connection, $port, $this->getSharedTcpServer(), $clientId);
$storedConnection = new TcpControlConnection(
$connection,
$port,
$this->getSharedTcpServer(),
$clientId,
$this->getAuthTokenFromConnection($connection)
);
$this->connections[] = $storedConnection;
@@ -118,4 +131,21 @@ class ConnectionManager implements ConnectionManagerContract
{
return $this->connections;
}
protected function getAuthTokenFromConnection(ConnectionInterface $connection): string
{
return QueryParameters::create($connection->httpRequest)->get('authToken');
}
public function getConnectionsForAuthToken(string $authToken): array
{
return collect($this->connections)
->filter(function ($connection) use ($authToken) {
return $connection->authToken === $authToken;
})
->map(function ($connection) {
return $connection->toArray();
})
->toArray();
}
}

View File

@@ -12,17 +12,19 @@ class ControlConnection
/** @var ConnectionInterface */
public $socket;
public $host;
public $authToken;
public $subdomain;
public $client_id;
public $proxies = [];
protected $shared_at;
public function __construct(ConnectionInterface $socket, string $host, string $subdomain, string $clientId)
public function __construct(ConnectionInterface $socket, string $host, string $subdomain, string $clientId, string $authToken = '')
{
$this->socket = $socket;
$this->host = $host;
$this->subdomain = $subdomain;
$this->client_id = $clientId;
$this->authToken = $authToken;
$this->shared_at = now()->toDateTimeString();
}
@@ -58,6 +60,7 @@ class ControlConnection
'type' => 'http',
'host' => $this->host,
'client_id' => $this->client_id,
'auth_token' => $this->authToken,
'subdomain' => $this->subdomain,
'shared_at' => $this->shared_at,
];

View File

@@ -14,7 +14,7 @@ class TcpControlConnection extends ControlConnection
public $shared_port;
public $shared_server;
public function __construct(ConnectionInterface $socket, int $port, Server $sharedServer, string $clientId)
public function __construct(ConnectionInterface $socket, int $port, Server $sharedServer, string $clientId, string $authToken = '')
{
$this->socket = $socket;
$this->client_id = $clientId;
@@ -22,6 +22,7 @@ class TcpControlConnection extends ControlConnection
$this->port = $port;
$this->shared_at = now()->toDateTimeString();
$this->shared_port = parse_url($sharedServer->getAddress(), PHP_URL_PORT);
$this->authToken = $authToken;
$this->configureServer($sharedServer);
}
@@ -85,7 +86,6 @@ class TcpControlConnection extends ControlConnection
$this->once('tcp_proxy_ready_'.$requestId, function (ConnectionInterface $proxy) use ($connection) {
$this->proxy = $proxy;
dump("Proxy ready");
$connection->on('data', function($data) use ($proxy) {
$proxy->send($data);

View File

@@ -12,6 +12,7 @@ use App\Server\Http\Controllers\Admin\DeleteUsersController;
use App\Server\Http\Controllers\Admin\DisconnectSiteController;
use App\Server\Http\Controllers\Admin\GetSettingsController;
use App\Server\Http\Controllers\Admin\GetSitesController;
use App\Server\Http\Controllers\Admin\GetUserDetailsController;
use App\Server\Http\Controllers\Admin\GetUsersController;
use App\Server\Http\Controllers\Admin\ListSitesController;
use App\Server\Http\Controllers\Admin\ListUsersController;
@@ -124,6 +125,7 @@ class Factory
$this->router->post('/api/settings', StoreSettingsController::class, $adminCondition);
$this->router->get('/api/users', GetUsersController::class, $adminCondition);
$this->router->post('/api/users', StoreUsersController::class, $adminCondition);
$this->router->get('/api/users/{id}', GetUserDetailsController::class, $adminCondition);
$this->router->delete('/api/users/{id}', DeleteUsersController::class, $adminCondition);
$this->router->get('/api/sites', GetSitesController::class, $adminCondition);
$this->router->delete('/api/sites/{id}', DisconnectSiteController::class, $adminCondition);

View File

@@ -0,0 +1,33 @@
<?php
namespace App\Server\Http\Controllers\Admin;
use App\Contracts\UserRepository;
use Illuminate\Http\Request;
use Ratchet\ConnectionInterface;
class GetUserDetailsController extends AdminController
{
protected $keepConnectionOpen = true;
/** @var UserRepository */
protected $userRepository;
public function __construct(UserRepository $userRepository)
{
$this->userRepository = $userRepository;
}
public function handle(Request $request, ConnectionInterface $httpConnection)
{
$this->userRepository
->getUserById($request->get('id'))
->then(function ($user) use ($httpConnection) {
$httpConnection->send(
respond_json(['user' => $user])
);
$httpConnection->close();
});
}
}

View File

@@ -39,6 +39,7 @@ class StoreUsersController extends AdminController
$insertData = [
'name' => $request->get('name'),
'auth_token' => (string) Str::uuid(),
'can_specify_subdomains' => (int) $request->get('can_specify_subdomains'),
];
$this->userRepository

View File

@@ -8,7 +8,6 @@ use App\Http\QueryParameters;
use Ratchet\ConnectionInterface;
use Ratchet\WebSocket\MessageComponentInterface;
use React\Promise\Deferred;
use React\Promise\FulfilledPromise;
use React\Promise\PromiseInterface;
use stdClass;
@@ -81,11 +80,11 @@ class ControlMessageController implements MessageComponentInterface
protected function authenticate(ConnectionInterface $connection, $data)
{
$this->verifyAuthToken($connection)
->then(function () use ($connection, $data) {
->then(function ($user) use ($connection, $data) {
if ($data->type === 'http') {
$this->handleHttpConnection($connection, $data);
$this->handleHttpConnection($connection, $data, $user);
} elseif($data->type === 'tcp') {
$this->handleTcpConnection($connection, $data);
$this->handleTcpConnection($connection, $data, $user);
}
}, function () use ($connection) {
$connection->send(json_encode([
@@ -98,9 +97,9 @@ class ControlMessageController implements MessageComponentInterface
});
}
protected function handleHttpConnection(ConnectionInterface $connection, $data)
protected function handleHttpConnection(ConnectionInterface $connection, $data, $user = null)
{
if (! $this->hasValidSubdomain($connection, $data->subdomain)) {
if (! $this->hasValidSubdomain($connection, $data->subdomain, $user)) {
return;
}
@@ -118,7 +117,7 @@ class ControlMessageController implements MessageComponentInterface
]));
}
protected function handleTcpConnection(ConnectionInterface $connection, $data)
protected function handleTcpConnection(ConnectionInterface $connection, $data, $user = null)
{
$connectionInfo = $this->connectionManager->storeTcpConnection($data->port, $connection);
@@ -167,7 +166,7 @@ class ControlMessageController implements MessageComponentInterface
protected function verifyAuthToken(ConnectionInterface $connection): PromiseInterface
{
if (config('expose.admin.validate_auth_tokens') !== true) {
return new FulfilledPromise();
return \React\Promise\resolve(null);
}
$deferred = new Deferred();
@@ -187,8 +186,20 @@ class ControlMessageController implements MessageComponentInterface
return $deferred->promise();
}
protected function hasValidSubdomain(ConnectionInterface $connection, ?string $subdomain): bool
protected function hasValidSubdomain(ConnectionInterface $connection, ?string $subdomain, ?array $user): bool
{
if (! is_null($user) && $user['can_specify_subdomains'] === 0 && ! is_null($subdomain)) {
$connection->send(json_encode([
'event' => 'subdomainTaken',
'data' => [
'message' => config('expose.admin.messages.custom_subdomain_unauthorized'),
],
]));
$connection->close();
return false;
}
if (! is_null($subdomain)) {
$controlConnection = $this->connectionManager->findControlConnectionForSubdomain($subdomain);
if (! is_null($controlConnection) || $subdomain === config('expose.admin.subdomain')) {

View File

@@ -2,6 +2,7 @@
namespace App\Server\UserRepository;
use App\Contracts\ConnectionManager;
use App\Contracts\UserRepository;
use Clue\React\SQLite\DatabaseInterface;
use Clue\React\SQLite\Result;
@@ -13,9 +14,13 @@ class DatabaseUserRepository implements UserRepository
/** @var DatabaseInterface */
protected $database;
public function __construct(DatabaseInterface $database)
/** @var ConnectionManager */
protected $connectionManager;
public function __construct(DatabaseInterface $database, ConnectionManager $connectionManager)
{
$this->database = $database;
$this->connectionManager = $connectionManager;
}
public function getUsers(): PromiseInterface
@@ -46,8 +51,12 @@ class DatabaseUserRepository implements UserRepository
$nextPage = $currentPage + 1;
}
$users = collect($result->rows)->map(function ($user) {
return $this->getUserDetails($user);
})->toArray();
$paginated = [
'users' => $result->rows,
'users' => $users,
'current_page' => $currentPage,
'per_page' => $perPage,
'next_page' => $nextPage ?? null,
@@ -60,6 +69,13 @@ class DatabaseUserRepository implements UserRepository
return $deferred->promise();
}
protected function getUserDetails(array $user)
{
$user['sites'] = $user['auth_token'] !== '' ? $this->connectionManager->getConnectionsForAuthToken($user['auth_token']) : [];
return $user;
}
public function getUserById($id): PromiseInterface
{
$deferred = new Deferred();
@@ -67,7 +83,13 @@ class DatabaseUserRepository implements UserRepository
$this->database
->query('SELECT * FROM users WHERE id = :id', ['id' => $id])
->then(function (Result $result) use ($deferred) {
$deferred->resolve($result->rows[0] ?? null);
$user = $result->rows[0] ?? null;
if (! is_null($user)) {
$user = $this->getUserDetails($user);
}
$deferred->resolve($user);
});
return $deferred->promise();
@@ -91,8 +113,8 @@ class DatabaseUserRepository implements UserRepository
$deferred = new Deferred();
$this->database->query("
INSERT INTO users (name, auth_token, created_at)
VALUES (:name, :auth_token, DATETIME('now'))
INSERT INTO users (name, auth_token, can_specify_subdomains, created_at)
VALUES (:name, :auth_token, :can_specify_subdomains, DATETIME('now'))
", $data)
->then(function (Result $result) use ($deferred) {
$this->database->query('SELECT * FROM users WHERE id = :id', ['id' => $result->insertId])