Add the ability to log users and their shared subdomains

This commit is contained in:
Marcel Pociot
2021-11-10 16:43:23 +01:00
parent 97993318e7
commit b9b07c9664
10 changed files with 231 additions and 2 deletions

View File

@@ -6,7 +6,7 @@ use App\Server\Connections\ControlConnection;
use App\Server\Connections\HttpConnection;
use Ratchet\ConnectionInterface;
interface ConnectionManager
interface connectionmanager
{
public function storeConnection(string $host, ?string $subdomain, ?string $serverHost, ConnectionInterface $connection): ControlConnection;

View File

@@ -0,0 +1,14 @@
<?php
namespace App\Contracts;
use React\Promise\PromiseInterface;
interface LoggerRepository
{
public function logSubdomain($authToken, $subdomain);
public function getLogs(): PromiseInterface;
public function getLogsBySubdomain($subdomain): PromiseInterface;
}

View File

@@ -7,6 +7,7 @@ use App\Contracts\StatisticsCollector;
use App\Contracts\SubdomainGenerator;
use App\Http\QueryParameters;
use App\Server\Exceptions\NoFreePortAvailable;
use App\Contracts\LoggerRepository;
use Ratchet\ConnectionInterface;
use React\EventLoop\LoopInterface;
use React\Socket\Server;
@@ -28,11 +29,15 @@ class ConnectionManager implements ConnectionManagerContract
/** @var StatisticsCollector */
protected $statisticsCollector;
public function __construct(SubdomainGenerator $subdomainGenerator, StatisticsCollector $statisticsCollector, LoopInterface $loop)
/** @var LoggerRepository */
protected $logger;
public function __construct(SubdomainGenerator $subdomainGenerator, StatisticsCollector $statisticsCollector, LoggerRepository $logger, LoopInterface $loop)
{
$this->subdomainGenerator = $subdomainGenerator;
$this->loop = $loop;
$this->statisticsCollector = $statisticsCollector;
$this->logger = $logger;
}
public function limitConnectionLength(ControlConnection $connection, int $maximumConnectionLength)
@@ -67,6 +72,8 @@ class ConnectionManager implements ConnectionManagerContract
$this->statisticsCollector->siteShared($this->getAuthTokenFromConnection($connection));
$this->logger->logSubdomain($storedConnection->authToken, $storedConnection->subdomain);
return $storedConnection;
}

View File

@@ -17,6 +17,8 @@ use App\Server\Http\Controllers\Admin\DeleteSubdomainController;
use App\Server\Http\Controllers\Admin\DeleteUsersController;
use App\Server\Http\Controllers\Admin\DisconnectSiteController;
use App\Server\Http\Controllers\Admin\DisconnectTcpConnectionController;
use App\Server\Http\Controllers\Admin\GetLogsController;
use App\Server\Http\Controllers\Admin\GetLogsForSubdomainController;
use App\Server\Http\Controllers\Admin\GetSettingsController;
use App\Server\Http\Controllers\Admin\GetSiteDetailsController;
use App\Server\Http\Controllers\Admin\GetSitesController;
@@ -36,6 +38,8 @@ use App\Server\Http\Controllers\Admin\StoreUsersController;
use App\Server\Http\Controllers\ControlMessageController;
use App\Server\Http\Controllers\TunnelMessageController;
use App\Server\Http\Router;
use App\Server\LoggerRepository\NullLogger;
use App\Contracts\LoggerRepository;
use App\Server\StatisticsCollector\DatabaseStatisticsCollector;
use App\Server\StatisticsRepository\DatabaseStatisticsRepository;
use App\Server\SubdomainRepository\DatabaseSubdomainRepository;
@@ -144,6 +148,8 @@ class Factory
$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->get('/api/logs', GetLogsController::class, $adminCondition);
$this->router->get('/api/logs/{subdomain}', GetLogsForSubdomainController::class, $adminCondition);
$this->router->post('/api/domains', StoreDomainController::class, $adminCondition);
$this->router->delete('/api/domains/{domain}', DeleteSubdomainController::class, $adminCondition);
$this->router->post('/api/subdomains', StoreSubdomainController::class, $adminCondition);
@@ -190,6 +196,7 @@ class Factory
$this->bindConfiguration()
->bindSubdomainGenerator()
->bindUserRepository()
->bindLoggerRepository()
->bindSubdomainRepository()
->bindDomainRepository()
->bindDatabase()
@@ -238,6 +245,15 @@ class Factory
return $this;
}
protected function bindLoggerRepository()
{
app()->singleton(LoggerRepository::class, function () {
return app(config('expose.admin.logger_repository', NullLogger::class));
});
return $this;
}
protected function bindDomainRepository()
{
app()->singleton(DomainRepository::class, function () {

View File

@@ -0,0 +1,38 @@
<?php
namespace App\Server\Http\Controllers\Admin;
use App\Contracts\LoggerRepository;
use App\Contracts\UserRepository;
use App\Server\Configuration;
use Illuminate\Http\Request;
use Ratchet\ConnectionInterface;
class GetLogsController extends AdminController
{
protected $keepConnectionOpen = true;
/** @var Configuration */
protected $configuration;
/** @var LoggerRepository */
protected $logger;
public function __construct(LoggerRepository $logger)
{
$this->logger = $logger;
}
public function handle(Request $request, ConnectionInterface $httpConnection)
{
$subdomain = $request->get('subdomain');
$this->logger->getLogs()
->then(function ($logs) use ($httpConnection) {
$httpConnection->send(
respond_json(['logs' => $logs])
);
$httpConnection->close();
});
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace App\Server\Http\Controllers\Admin;
use App\Contracts\LoggerRepository;
use App\Contracts\UserRepository;
use App\Server\Configuration;
use Illuminate\Http\Request;
use Ratchet\ConnectionInterface;
class GetLogsForSubdomainController extends AdminController
{
protected $keepConnectionOpen = true;
/** @var Configuration */
protected $configuration;
/** @var LoggerRepository */
protected $logger;
public function __construct(LoggerRepository $logger)
{
$this->logger = $logger;
}
public function handle(Request $request, ConnectionInterface $httpConnection)
{
$subdomain = $request->get('subdomain');
$this->logger->getLogsBySubdomain($subdomain)
->then(function ($logs) use ($httpConnection) {
$httpConnection->send(
respond_json(['logs' => $logs])
);
$httpConnection->close();
});
}
}

View File

@@ -0,0 +1,83 @@
<?php
namespace App\Server\LoggerRepository;
use App\Contracts\UserRepository;
use Clue\React\SQLite\DatabaseInterface;
use App\Contracts\LoggerRepository;
use Clue\React\SQLite\Result;
use React\Promise\Deferred;
use React\Promise\PromiseInterface;
class DatabaseLogger implements LoggerRepository
{
/** @var DatabaseInterface */
protected $database;
public function __construct(DatabaseInterface $database)
{
$this->database = $database;
}
public function logSubdomain($authToken, $subdomain)
{
app(UserRepository::class)->getUserByToken($authToken)
->then(function ($user) use ($subdomain) {
$this->database->query("
INSERT INTO logs (user_id, subdomain, created_at)
VALUES (:user_id, :subdomain, DATETIME('now'))
", [
'user_id' => $user['id'],
'subdomain' => $subdomain,
])->then(function () {
$this->cleanOldLogs();
});
});
}
public function cleanOldLogs()
{
$this->database->query("DELETE FROM logs WHERE created_at < date('now', '-10 day')");
}
public function getLogsBySubdomain($subdomain): PromiseInterface
{
$deferred = new Deferred();
$this->database
->query("
SELECT
logs.id AS log_id,
logs.subdomain,
users.*
FROM logs
INNER JOIN users
ON users.id = logs.user_id
WHERE logs.subdomain = :subdomain", ['subdomain' => $subdomain])
->then(function (Result $result) use ($deferred) {
$deferred->resolve($result->rows);
});
return $deferred->promise();
}
public function getLogs(): PromiseInterface
{
$deferred = new Deferred();
$this->database
->query("
SELECT
logs.id AS log_id,
logs.subdomain,
users.*
FROM logs
INNER JOIN users
ON users.id = logs.user_id")
->then(function (Result $result) use ($deferred) {
$deferred->resolve($result->rows);
});
return $deferred->promise();
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace App\Server\LoggerRepository;
use App\Contracts\LoggerRepository;
use Clue\React\SQLite\DatabaseInterface;
use React\Promise\PromiseInterface;
class NullLogger implements LoggerRepository
{
public function logSubdomain($authToken, $subdomain)
{
// noop
}
public function getLogsBySubdomain($subdomain): PromiseInterface
{
return \React\Promise\resolve([]);
}
public function getLogs(): PromiseInterface
{
return \React\Promise\resolve([]);
}
}