From b9b07c966416ee9facbf330f8ca8313fac1f42f1 Mon Sep 17 00:00:00 2001 From: Marcel Pociot Date: Wed, 10 Nov 2021 16:43:23 +0100 Subject: [PATCH] Add the ability to log users and their shared subdomains --- app/Contracts/ConnectionManager.php | 2 +- app/Contracts/LoggerRepository.php | 14 ++++ app/Server/Connections/ConnectionManager.php | 9 +- app/Server/Factory.php | 16 ++++ .../Controllers/Admin/GetLogsController.php | 38 +++++++++ .../Admin/GetLogsForSubdomainController.php | 38 +++++++++ .../LoggerRepository/DatabaseLogger.php | 83 +++++++++++++++++++ app/Server/LoggerRepository/NullLogger.php | 25 ++++++ config/expose.php | 2 + database/migrations/09_create_logs_table.sql | 6 ++ 10 files changed, 231 insertions(+), 2 deletions(-) create mode 100644 app/Contracts/LoggerRepository.php create mode 100644 app/Server/Http/Controllers/Admin/GetLogsController.php create mode 100644 app/Server/Http/Controllers/Admin/GetLogsForSubdomainController.php create mode 100644 app/Server/LoggerRepository/DatabaseLogger.php create mode 100644 app/Server/LoggerRepository/NullLogger.php create mode 100644 database/migrations/09_create_logs_table.sql diff --git a/app/Contracts/ConnectionManager.php b/app/Contracts/ConnectionManager.php index 2898ded..b4b5397 100644 --- a/app/Contracts/ConnectionManager.php +++ b/app/Contracts/ConnectionManager.php @@ -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; diff --git a/app/Contracts/LoggerRepository.php b/app/Contracts/LoggerRepository.php new file mode 100644 index 0000000..d7d94ad --- /dev/null +++ b/app/Contracts/LoggerRepository.php @@ -0,0 +1,14 @@ +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; } diff --git a/app/Server/Factory.php b/app/Server/Factory.php index 4748a2c..e8f4237 100644 --- a/app/Server/Factory.php +++ b/app/Server/Factory.php @@ -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 () { diff --git a/app/Server/Http/Controllers/Admin/GetLogsController.php b/app/Server/Http/Controllers/Admin/GetLogsController.php new file mode 100644 index 0000000..fa5de95 --- /dev/null +++ b/app/Server/Http/Controllers/Admin/GetLogsController.php @@ -0,0 +1,38 @@ +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(); + }); + } +} diff --git a/app/Server/Http/Controllers/Admin/GetLogsForSubdomainController.php b/app/Server/Http/Controllers/Admin/GetLogsForSubdomainController.php new file mode 100644 index 0000000..c1bb2aa --- /dev/null +++ b/app/Server/Http/Controllers/Admin/GetLogsForSubdomainController.php @@ -0,0 +1,38 @@ +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(); + }); + } +} diff --git a/app/Server/LoggerRepository/DatabaseLogger.php b/app/Server/LoggerRepository/DatabaseLogger.php new file mode 100644 index 0000000..1a4ba47 --- /dev/null +++ b/app/Server/LoggerRepository/DatabaseLogger.php @@ -0,0 +1,83 @@ +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(); + } +} diff --git a/app/Server/LoggerRepository/NullLogger.php b/app/Server/LoggerRepository/NullLogger.php new file mode 100644 index 0000000..d64fb4b --- /dev/null +++ b/app/Server/LoggerRepository/NullLogger.php @@ -0,0 +1,25 @@ + \App\Server\SubdomainRepository\DatabaseSubdomainRepository::class, + 'logger_repository' => \App\Server\LoggerRepository\NullLogger::class, + /* |-------------------------------------------------------------------------- | Messages diff --git a/database/migrations/09_create_logs_table.sql b/database/migrations/09_create_logs_table.sql new file mode 100644 index 0000000..9256cba --- /dev/null +++ b/database/migrations/09_create_logs_table.sql @@ -0,0 +1,6 @@ +CREATE TABLE IF NOT EXISTS logs ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + subdomain STRING NOT NULL, + created_at DATETIME +)