From 351253cc195482344a506eebf3d9fc3c7fd734f8 Mon Sep 17 00:00:00 2001 From: Marcel Pociot Date: Fri, 11 Jun 2021 12:54:39 +0200 Subject: [PATCH 1/3] wip --- .../DatabaseSubdomainRepository.php | 2 +- .../UserRepository/DatabaseUserRepository.php | 45 ++++-- tests/Feature/Server/ApiTest.php | 136 ++++++++++++++++++ 3 files changed, 171 insertions(+), 12 deletions(-) diff --git a/app/Server/SubdomainRepository/DatabaseSubdomainRepository.php b/app/Server/SubdomainRepository/DatabaseSubdomainRepository.php index d540eeb..eaf3d00 100644 --- a/app/Server/SubdomainRepository/DatabaseSubdomainRepository.php +++ b/app/Server/SubdomainRepository/DatabaseSubdomainRepository.php @@ -135,7 +135,7 @@ class DatabaseSubdomainRepository implements SubdomainRepository { $deferred = new Deferred(); - $this->database->query('DELETE FROM subdomains WHERE id = :id AND user_id = :user_id', [ + $this->database->query('DELETE FROM subdomains WHERE (id = :id OR subdomain = :id) AND user_id = :user_id', [ 'id' => $subdomainId, 'user_id' => $userId, ]) diff --git a/app/Server/UserRepository/DatabaseUserRepository.php b/app/Server/UserRepository/DatabaseUserRepository.php index 62ce315..02bf64a 100644 --- a/app/Server/UserRepository/DatabaseUserRepository.php +++ b/app/Server/UserRepository/DatabaseUserRepository.php @@ -53,7 +53,7 @@ class DatabaseUserRepository implements UserRepository ]; if ($searchQuery !== '') { - $query .= "WHERE name LIKE '%".$searchQuery."%' "; + $query .= "WHERE name LIKE '%" . $searchQuery . "%' "; $bindings['search'] = $searchQuery; } @@ -104,7 +104,7 @@ class DatabaseUserRepository implements UserRepository ->then(function (Result $result) use ($deferred) { $user = $result->rows[0] ?? null; - if (! is_null($user)) { + if (!is_null($user)) { $user = $this->getUserDetails($user); } @@ -136,7 +136,7 @@ class DatabaseUserRepository implements UserRepository ->then(function (Result $result) use ($deferred) { $user = $result->rows[0] ?? null; - if (! is_null($user)) { + if (!is_null($user)) { $user = $this->getUserDetails($user); } @@ -150,15 +150,38 @@ class DatabaseUserRepository implements UserRepository { $deferred = new Deferred(); - $this->database->query(" + $this->getUserByToken($data['auth_token']) + ->then(function ($existingUser) use ($data, $deferred) { + if (is_null($existingUser)) { + $this->database->query(" INSERT INTO users (name, auth_token, can_specify_subdomains, can_specify_domains, can_share_tcp_ports, max_connections, created_at) VALUES (:name, :auth_token, :can_specify_subdomains, :can_specify_domains, :can_share_tcp_ports, :max_connections, DATETIME('now')) ", $data) - ->then(function (Result $result) use ($deferred) { - $this->database->query('SELECT * FROM users WHERE id = :id', ['id' => $result->insertId]) - ->then(function (Result $result) use ($deferred) { - $deferred->resolve($result->rows[0]); - }); + ->then(function (Result $result) use ($deferred) { + $this->database->query('SELECT * FROM users WHERE id = :id', ['id' => $result->insertId]) + ->then(function (Result $result) use ($deferred) { + $deferred->resolve($result->rows[0]); + }); + }); + } else { + $this->database->query(" + UPDATE users + SET + name = :name, + can_specify_subdomains = :can_specify_subdomains, + can_specify_domains = :can_specify_domains, + can_share_tcp_ports = :can_share_tcp_ports, + max_connections = :max_connections + WHERE + auth_token = :auth_token + ", $data) + ->then(function (Result $result) use ($existingUser, $deferred) { + $this->database->query('SELECT * FROM users WHERE id = :id', ['id' => $existingUser['id']]) + ->then(function (Result $result) use ($deferred) { + $deferred->resolve($result->rows[0]); + }); + }); + } }); return $deferred->promise(); @@ -181,10 +204,10 @@ class DatabaseUserRepository implements UserRepository $deferred = new Deferred(); $authTokenString = collect($authTokens)->map(function ($token) { - return '"'.$token.'"'; + return '"' . $token . '"'; })->join(','); - $this->database->query('SELECT * FROM users WHERE auth_token IN ('.$authTokenString.')') + $this->database->query('SELECT * FROM users WHERE auth_token IN (' . $authTokenString . ')') ->then(function (Result $result) use ($deferred) { $users = collect($result->rows)->map(function ($user) { return $this->getUserDetails($user); diff --git a/tests/Feature/Server/ApiTest.php b/tests/Feature/Server/ApiTest.php index 879963e..8422026 100644 --- a/tests/Feature/Server/ApiTest.php +++ b/tests/Feature/Server/ApiTest.php @@ -65,6 +65,97 @@ class ApiTest extends TestCase $this->assertSame([], $users[0]->sites); } + /** @test */ + public function it_can_specify_a_token_when_creating_a_user() + { + /** @var Response $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([ + 'name' => 'Marcel', + 'token' => 'my-token' + ]))); + + /** @var Response $response */ + $response = $this->await($this->browser->get('http://127.0.0.1:8080/api/users', [ + 'Host' => 'expose.localhost', + 'Authorization' => base64_encode('username:secret'), + 'Content-Type' => 'application/json', + ])); + + $body = json_decode($response->getBody()->getContents()); + $users = $body->paginated->users; + + $this->assertCount(1, $users); + $this->assertSame('Marcel', $users[0]->name); + $this->assertSame('my-token', $users[0]->auth_token); + $this->assertSame([], $users[0]->sites); + } + + /** @test */ + public function it_updates_users_instead_of_creating_new_ones() + { + /** @var Response $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([ + 'name' => 'Marcel', + 'token' => 'my-token' + ]))); + + /** @var Response $response */ + $response = $this->await($this->browser->get('http://127.0.0.1:8080/api/users', [ + 'Host' => 'expose.localhost', + 'Authorization' => base64_encode('username:secret'), + 'Content-Type' => 'application/json', + ])); + + $body = json_decode($response->getBody()->getContents()); + $users = $body->paginated->users; + + $this->assertCount(1, $users); + $this->assertSame('Marcel', $users[0]->name); + $this->assertSame('my-token', $users[0]->auth_token); + $this->assertSame(0, $users[0]->can_specify_subdomains); + $this->assertSame(0, $users[0]->can_specify_domains); + $this->assertSame(0, $users[0]->can_share_tcp_ports); + $this->assertSame([], $users[0]->sites); + + $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([ + 'name' => 'Marcel Changed', + 'token' => 'my-token', + 'can_specify_subdomains' => 1, + 'can_specify_domains' => 1, + 'can_share_tcp_ports' => 1, + ]))); + + /** @var Response $response */ + $response = $this->await($this->browser->get('http://127.0.0.1:8080/api/users', [ + 'Host' => 'expose.localhost', + 'Authorization' => base64_encode('username:secret'), + 'Content-Type' => 'application/json', + ])); + + $body = json_decode($response->getBody()->getContents()); + $users = $body->paginated->users; + + $this->assertCount(1, $users); + $this->assertSame('Marcel Changed', $users[0]->name); + $this->assertSame('my-token', $users[0]->auth_token); + $this->assertSame(1, $users[0]->can_specify_subdomains); + $this->assertSame(1, $users[0]->can_specify_domains); + $this->assertSame(1, $users[0]->can_share_tcp_ports); + $this->assertSame([], $users[0]->sites); + } + /** @test */ public function it_can_specify_tokens_when_creating_a_user() { @@ -288,6 +379,51 @@ class ApiTest extends TestCase $this->assertCount(0, $subdomains); } + /** @test */ + public function it_can_delete_subdomains_by_name() + { + /** @var Response $response */ + $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([ + 'name' => 'Marcel', + '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', [ + 'Host' => 'expose.localhost', + 'Authorization' => base64_encode('username:secret'), + 'Content-Type' => 'application/json', + ], json_encode([ + 'subdomain' => 'reserved', + 'auth_token' => $user->auth_token, + ]))); + + $this->await($this->browser->delete('http://127.0.0.1:8080/api/subdomains/reserved', [ + 'Host' => 'expose.localhost', + 'Authorization' => base64_encode('username:secret'), + 'Content-Type' => 'application/json', + ], json_encode([ + 'auth_token' => $user->auth_token, + ]))); + + /** @var Response $response */ + $response = $this->await($this->browser->get('http://127.0.0.1:8080/api/users/1', [ + 'Host' => 'expose.localhost', + 'Authorization' => base64_encode('username:secret'), + 'Content-Type' => 'application/json', + ])); + + $body = json_decode($response->getBody()->getContents()); + $subdomains = $body->subdomains; + + $this->assertCount(0, $subdomains); + } + /** @test */ public function it_can_not_reserve_an_already_reserved_subdomain() { From 8be8aff802b970c3f9cde5618becc71ed56d2f95 Mon Sep 17 00:00:00 2001 From: Marcel Pociot Date: Fri, 11 Jun 2021 15:16:52 +0200 Subject: [PATCH 2/3] Improve subdomain detection --- .../Controllers/TunnelMessageController.php | 6 +-- tests/Feature/Server/TunnelTest.php | 38 +++++++++++++++++++ 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/app/Server/Http/Controllers/TunnelMessageController.php b/app/Server/Http/Controllers/TunnelMessageController.php index a2635e5..c4b4fda 100644 --- a/app/Server/Http/Controllers/TunnelMessageController.php +++ b/app/Server/Http/Controllers/TunnelMessageController.php @@ -70,11 +70,9 @@ class TunnelMessageController extends Controller protected function detectSubdomain(Request $request): ?string { - if (substr_count($request->header('Host'), '.') === 1) { - return null; - } + $serverHost = $this->detectServerHost($request); - $subdomain = Str::before($request->header('Host'), '.'); + $subdomain = Str::before($request->header('Host'), '.' . $serverHost); return $subdomain === $request->header('Host') ? null : $subdomain; } diff --git a/tests/Feature/Server/TunnelTest.php b/tests/Feature/Server/TunnelTest.php index 0caefa3..20b2fdc 100644 --- a/tests/Feature/Server/TunnelTest.php +++ b/tests/Feature/Server/TunnelTest.php @@ -64,6 +64,17 @@ class TunnelTest extends TestCase ])); } + /** @test */ + public function it_returns_404_for_non_existing_clients_on_custom_hosts() + { + $this->expectException(ResponseException::class); + $this->expectExceptionMessage(404); + + $this->await($this->browser->get('http://127.0.0.1:8080/', [ + 'Host' => 'tunnel.share.beyondco.de', + ])); + } + /** @test */ public function it_sends_incoming_requests_to_the_connected_client() { @@ -91,6 +102,33 @@ class TunnelTest extends TestCase $this->assertSame('Hello World!', $response->getBody()->getContents()); } + /** @test */ + public function it_sends_incoming_requests_to_the_connected_client_on_custom_hosts() + { + $this->app['config']['expose.admin.validate_auth_tokens'] = false; + + $this->createTestHttpServer(); + + $this->app['config']['expose.admin.validate_auth_tokens'] = false; + + /** + * 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', 'share.beyondco.de')); + + /** + * Once the client is connected, we perform a GET request on the + * created tunnel. + */ + $response = $this->await($this->browser->get('http://127.0.0.1:8080/', [ + 'Host' => 'tunnel.share.beyondco.de', + ])); + + $this->assertSame('Hello World!', $response->getBody()->getContents()); + } + /** @test */ public function it_sends_incoming_requests_to_the_connected_client_via_tcp() { From de9b85df494590b0b69e9dad508f2e4c0d9233fb Mon Sep 17 00:00:00 2001 From: Marcel Pociot Date: Fri, 11 Jun 2021 13:17:14 +0000 Subject: [PATCH 3/3] Apply fixes from StyleCI --- .../Http/Controllers/TunnelMessageController.php | 2 +- .../UserRepository/DatabaseUserRepository.php | 14 +++++++------- tests/Feature/Server/ApiTest.php | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/app/Server/Http/Controllers/TunnelMessageController.php b/app/Server/Http/Controllers/TunnelMessageController.php index c4b4fda..f3e107f 100644 --- a/app/Server/Http/Controllers/TunnelMessageController.php +++ b/app/Server/Http/Controllers/TunnelMessageController.php @@ -72,7 +72,7 @@ class TunnelMessageController extends Controller { $serverHost = $this->detectServerHost($request); - $subdomain = Str::before($request->header('Host'), '.' . $serverHost); + $subdomain = Str::before($request->header('Host'), '.'.$serverHost); return $subdomain === $request->header('Host') ? null : $subdomain; } diff --git a/app/Server/UserRepository/DatabaseUserRepository.php b/app/Server/UserRepository/DatabaseUserRepository.php index 02bf64a..8f03031 100644 --- a/app/Server/UserRepository/DatabaseUserRepository.php +++ b/app/Server/UserRepository/DatabaseUserRepository.php @@ -53,7 +53,7 @@ class DatabaseUserRepository implements UserRepository ]; if ($searchQuery !== '') { - $query .= "WHERE name LIKE '%" . $searchQuery . "%' "; + $query .= "WHERE name LIKE '%".$searchQuery."%' "; $bindings['search'] = $searchQuery; } @@ -104,7 +104,7 @@ class DatabaseUserRepository implements UserRepository ->then(function (Result $result) use ($deferred) { $user = $result->rows[0] ?? null; - if (!is_null($user)) { + if (! is_null($user)) { $user = $this->getUserDetails($user); } @@ -136,7 +136,7 @@ class DatabaseUserRepository implements UserRepository ->then(function (Result $result) use ($deferred) { $user = $result->rows[0] ?? null; - if (!is_null($user)) { + if (! is_null($user)) { $user = $this->getUserDetails($user); } @@ -164,7 +164,7 @@ class DatabaseUserRepository implements UserRepository }); }); } else { - $this->database->query(" + $this->database->query(' UPDATE users SET name = :name, @@ -174,7 +174,7 @@ class DatabaseUserRepository implements UserRepository max_connections = :max_connections WHERE auth_token = :auth_token - ", $data) + ', $data) ->then(function (Result $result) use ($existingUser, $deferred) { $this->database->query('SELECT * FROM users WHERE id = :id', ['id' => $existingUser['id']]) ->then(function (Result $result) use ($deferred) { @@ -204,10 +204,10 @@ class DatabaseUserRepository implements UserRepository $deferred = new Deferred(); $authTokenString = collect($authTokens)->map(function ($token) { - return '"' . $token . '"'; + return '"'.$token.'"'; })->join(','); - $this->database->query('SELECT * FROM users WHERE auth_token IN (' . $authTokenString . ')') + $this->database->query('SELECT * FROM users WHERE auth_token IN ('.$authTokenString.')') ->then(function (Result $result) use ($deferred) { $users = collect($result->rows)->map(function ($user) { return $this->getUserDetails($user); diff --git a/tests/Feature/Server/ApiTest.php b/tests/Feature/Server/ApiTest.php index 8422026..9026bc2 100644 --- a/tests/Feature/Server/ApiTest.php +++ b/tests/Feature/Server/ApiTest.php @@ -75,7 +75,7 @@ class ApiTest extends TestCase 'Content-Type' => 'application/json', ], json_encode([ 'name' => 'Marcel', - 'token' => 'my-token' + 'token' => 'my-token', ]))); /** @var Response $response */ @@ -104,7 +104,7 @@ class ApiTest extends TestCase 'Content-Type' => 'application/json', ], json_encode([ 'name' => 'Marcel', - 'token' => 'my-token' + 'token' => 'my-token', ]))); /** @var Response $response */