From dfe889692b0dbcc505f5d8f3217b8d523ae658c8 Mon Sep 17 00:00:00 2001 From: Ahmed Ashraf Date: Fri, 3 Jul 2020 12:50:08 +0200 Subject: [PATCH 01/10] auto detect valet links --- .../ShareCurrentWorkingDirectoryCommand.php | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/app/Commands/ShareCurrentWorkingDirectoryCommand.php b/app/Commands/ShareCurrentWorkingDirectoryCommand.php index 6574e88..fbddd90 100644 --- a/app/Commands/ShareCurrentWorkingDirectoryCommand.php +++ b/app/Commands/ShareCurrentWorkingDirectoryCommand.php @@ -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,30 @@ 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(); + + $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'; From 8db13e70afbca6758c2322390ead01559f0cc41e Mon Sep 17 00:00:00 2001 From: Ahmed Ashraf Date: Fri, 3 Jul 2020 12:53:31 +0200 Subject: [PATCH 02/10] set path only if exists --- app/Commands/ShareCurrentWorkingDirectoryCommand.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/Commands/ShareCurrentWorkingDirectoryCommand.php b/app/Commands/ShareCurrentWorkingDirectoryCommand.php index fbddd90..819dbab 100644 --- a/app/Commands/ShareCurrentWorkingDirectoryCommand.php +++ b/app/Commands/ShareCurrentWorkingDirectoryCommand.php @@ -51,7 +51,9 @@ class ShareCurrentWorkingDirectoryCommand extends ShareCommand ->keys() ->first(); - $projectPath = $site; + if ($site) { + $projectPath = $site; + } } return str_replace('.', '-', basename($projectPath)); From 188e1efe5792c7d24d9a2897b74376e24a5ff778 Mon Sep 17 00:00:00 2001 From: Tizian Schmidlin Date: Mon, 3 Aug 2020 09:00:55 +0200 Subject: [PATCH 03/10] [DOCS] Describe apache proxy configuration exampl --- docs/server/ssl.md | 48 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/docs/server/ssl.md b/docs/server/ssl.md index 95571bb..9adfb00 100644 --- a/docs/server/ssl.md +++ b/docs/server/ssl.md @@ -7,7 +7,9 @@ order: 2 Once your Expose server is running, you can only access it over the port that you configure when the server gets started. -If you want to enable SSL support, you will need to use a proxy service - like Nginx, HAProxy or Caddy - to handle the SSL configurations and proxy all non-SSL requests to your expose server. +If you want to enable SSL support, you will need to use a proxy service - like Nginx, HAProxy, Apache2 or Caddy - to handle the SSL configurations and proxy all non-SSL requests to your expose server. + +## Nginx configuration A basic Nginx configuration would look like this, but you might want to tweak the SSL parameters to your liking. @@ -40,3 +42,47 @@ server { } } ``` + +## Apache2 configuration + +A basic Apache configuration would look like this, but you might want to tweak the SSL parameters to your liking. + +``` +Listen 80 +Listen 443 + + + + ServerName expose.domain.tld + ServerAlias *.expose.domain.tld + LoadModule proxy_module modules/mod_proxy.so + LoadModule proxy_http_module modules/mod_proxy_http.so + + ServerAdmin admin@domain.tld + + ProxyPass "/" "http://localhost:8080/" + ProxyPassReverse "/" "http://localhost:8080/" + ProxyPreserveHost On + + + # Needed for websocket support + RewriteCond %{HTTP:UPGRADE} ^WebSocket$ [NC,OR] + RewriteCond %{HTTP:CONNECTION} ^Upgrade$ [NC] + RewriteRule .* ws://127.0.0.1:8080%{REQUEST_URI} [P,QSA,L] + + + + Require all granted + + Options none + + + ErrorLog ${APACHE_LOG_DIR}/expose.domain.tld-error.log + CustomLog ${APACHE_LOG_DIR}/expose.domain.tld-access.log combined + + SSLCertificateFile /etc/letsencrypt/live/expose.domain.tld-0001/fullchain.pem + SSLCertificateKeyFile /etc/letsencrypt/live/expose.domain.tld-0001/privkey.pem + Include /etc/letsencrypt/options-ssl-apache.conf + + +``` From 55a456d5e1154e8e7d85b35e1806ee86e342bd29 Mon Sep 17 00:00:00 2001 From: Marcel Pociot Date: Wed, 12 Aug 2020 22:20:06 +0000 Subject: [PATCH 04/10] Apply fixes from StyleCI --- app/Commands/ShareCurrentWorkingDirectoryCommand.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/Commands/ShareCurrentWorkingDirectoryCommand.php b/app/Commands/ShareCurrentWorkingDirectoryCommand.php index 819dbab..e6e117b 100644 --- a/app/Commands/ShareCurrentWorkingDirectoryCommand.php +++ b/app/Commands/ShareCurrentWorkingDirectoryCommand.php @@ -41,11 +41,11 @@ class ShareCurrentWorkingDirectoryCommand extends ShareCommand if (is_dir($valetSitesPath)) { $site = collect(scandir($valetSitesPath)) ->skip(2) - ->map(function($site) use($valetSitesPath) { + ->map(function ($site) use ($valetSitesPath) { return $valetSitesPath.DIRECTORY_SEPARATOR.$site; - })->mapWithKeys(function($site){ + })->mapWithKeys(function ($site) { return [$site => readlink($site)]; - })->filter(function($sourcePath) use($projectPath) { + })->filter(function ($sourcePath) use ($projectPath) { return $sourcePath === $projectPath; }) ->keys() From e52659bf59b30e59b65cf4e3138f3d4a74b4e743 Mon Sep 17 00:00:00 2001 From: Nathanael McDaniel Date: Wed, 26 Aug 2020 14:47:01 -0500 Subject: [PATCH 05/10] Removed padraic/phar-updater --- composer.json | 3 +- composer.lock | 345 +++++++++++++++++++++----------------------------- 2 files changed, 147 insertions(+), 201 deletions(-) diff --git a/composer.json b/composer.json index 9e656fe..8ba0004 100644 --- a/composer.json +++ b/composer.json @@ -17,8 +17,7 @@ ], "require": { "php": "^7.3.0", - "ext-json": "*", - "padraic/phar-updater": "^1.0.6" + "ext-json": "*" }, "require-dev": { "cboden/ratchet": "^0.4.2", diff --git a/composer.lock b/composer.lock index 7f6a2dc..65ce9f1 100644 --- a/composer.lock +++ b/composer.lock @@ -4,196 +4,8 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "29e5e7d9a7d406f7d34ef09a3b2836e9", - "packages": [ - { - "name": "composer/ca-bundle", - "version": "1.2.7", - "source": { - "type": "git", - "url": "https://github.com/composer/ca-bundle.git", - "reference": "95c63ab2117a72f48f5a55da9740a3273d45b7fd" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/95c63ab2117a72f48f5a55da9740a3273d45b7fd", - "reference": "95c63ab2117a72f48f5a55da9740a3273d45b7fd", - "shasum": "" - }, - "require": { - "ext-openssl": "*", - "ext-pcre": "*", - "php": "^5.3.2 || ^7.0 || ^8.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 8", - "psr/log": "^1.0", - "symfony/process": "^2.5 || ^3.0 || ^4.0 || ^5.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Composer\\CaBundle\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - } - ], - "description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.", - "keywords": [ - "cabundle", - "cacert", - "certificate", - "ssl", - "tls" - ], - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" - } - ], - "time": "2020-04-08T08:27:21+00:00" - }, - { - "name": "padraic/humbug_get_contents", - "version": "1.1.2", - "source": { - "type": "git", - "url": "https://github.com/humbug/file_get_contents.git", - "reference": "dcb086060c9dd6b2f51d8f7a895500307110b7a7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/humbug/file_get_contents/zipball/dcb086060c9dd6b2f51d8f7a895500307110b7a7", - "reference": "dcb086060c9dd6b2f51d8f7a895500307110b7a7", - "shasum": "" - }, - "require": { - "composer/ca-bundle": "^1.0", - "ext-openssl": "*", - "php": "^5.3 || ^7.0 || ^7.1 || ^7.2" - }, - "require-dev": { - "bamarni/composer-bin-plugin": "^1.1", - "mikey179/vfsstream": "^1.6", - "phpunit/phpunit": "^4.8 || ^5.7 || ^6.5" - }, - "type": "library", - "extra": { - "bamarni-bin": { - "bin-links": false - }, - "branch-alias": { - "dev-master": "2.0-dev" - } - }, - "autoload": { - "psr-4": { - "Humbug\\": "src/" - }, - "files": [ - "src/function.php", - "src/functions.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Pádraic Brady", - "email": "padraic.brady@gmail.com", - "homepage": "http://blog.astrumfutura.com" - }, - { - "name": "Théo Fidry", - "email": "theo.fidry@gmail.com" - } - ], - "description": "Secure wrapper for accessing HTTPS resources with file_get_contents for PHP 5.3+", - "homepage": "https://github.com/padraic/file_get_contents", - "keywords": [ - "download", - "file_get_contents", - "http", - "https", - "ssl", - "tls" - ], - "time": "2018-02-12T18:47:17+00:00" - }, - { - "name": "padraic/phar-updater", - "version": "v1.0.6", - "source": { - "type": "git", - "url": "https://github.com/humbug/phar-updater.git", - "reference": "d01d3b8f26e541ac9b9eeba1e18d005d852f7ff1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/humbug/phar-updater/zipball/d01d3b8f26e541ac9b9eeba1e18d005d852f7ff1", - "reference": "d01d3b8f26e541ac9b9eeba1e18d005d852f7ff1", - "shasum": "" - }, - "require": { - "padraic/humbug_get_contents": "^1.0", - "php": ">=5.3.3" - }, - "require-dev": { - "phpunit/phpunit": "~4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "psr-4": { - "Humbug\\SelfUpdate\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Pádraic Brady", - "email": "padraic.brady@gmail.com", - "homepage": "http://blog.astrumfutura.com" - } - ], - "description": "A thing to make PHAR self-updating easy and secure.", - "keywords": [ - "humbug", - "phar", - "self-update", - "update" - ], - "time": "2018-03-30T12:52:15+00:00" - } - ], + "content-hash": "dd987a6f4f036893204c0d006d66001b", + "packages": [], "packages-dev": [ { "name": "cboden/ratchet", @@ -478,6 +290,72 @@ }, "time": "2020-05-10T03:16:55+00:00" }, + { + "name": "composer/ca-bundle", + "version": "1.2.7", + "source": { + "type": "git", + "url": "https://github.com/composer/ca-bundle.git", + "reference": "95c63ab2117a72f48f5a55da9740a3273d45b7fd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/95c63ab2117a72f48f5a55da9740a3273d45b7fd", + "reference": "95c63ab2117a72f48f5a55da9740a3273d45b7fd", + "shasum": "" + }, + "require": { + "ext-openssl": "*", + "ext-pcre": "*", + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 8", + "psr/log": "^1.0", + "symfony/process": "^2.5 || ^3.0 || ^4.0 || ^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\CaBundle\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.", + "keywords": [ + "cabundle", + "cacert", + "certificate", + "ssl", + "tls" + ], + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2020-04-08T08:27:21+00:00" + }, { "name": "container-interop/container-interop", "version": "1.2.0", @@ -3258,6 +3136,75 @@ ], "time": "2020-05-23T11:29:07+00:00" }, + { + "name": "padraic/humbug_get_contents", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/humbug/file_get_contents.git", + "reference": "dcb086060c9dd6b2f51d8f7a895500307110b7a7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/humbug/file_get_contents/zipball/dcb086060c9dd6b2f51d8f7a895500307110b7a7", + "reference": "dcb086060c9dd6b2f51d8f7a895500307110b7a7", + "shasum": "" + }, + "require": { + "composer/ca-bundle": "^1.0", + "ext-openssl": "*", + "php": "^5.3 || ^7.0 || ^7.1 || ^7.2" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.1", + "mikey179/vfsstream": "^1.6", + "phpunit/phpunit": "^4.8 || ^5.7 || ^6.5" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": false + }, + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "psr-4": { + "Humbug\\": "src/" + }, + "files": [ + "src/function.php", + "src/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Pádraic Brady", + "email": "padraic.brady@gmail.com", + "homepage": "http://blog.astrumfutura.com" + }, + { + "name": "Théo Fidry", + "email": "theo.fidry@gmail.com" + } + ], + "description": "Secure wrapper for accessing HTTPS resources with file_get_contents for PHP 5.3+", + "homepage": "https://github.com/padraic/file_get_contents", + "keywords": [ + "download", + "file_get_contents", + "http", + "https", + "ssl", + "tls" + ], + "time": "2018-02-12T18:47:17+00:00" + }, { "name": "paragonie/random_compat", "version": "v9.99.99", @@ -8431,16 +8378,16 @@ ], "aliases": [ { - "alias": "1.6.1", - "alias_normalized": "1.6.1.0", - "version": "9999999-dev", - "package": "guzzlehttp/psr7" + "package": "react/socket", + "version": "dev-master", + "alias": "1.1", + "alias_normalized": "1.1.0.0" }, { - "alias": "1.1", - "alias_normalized": "1.1.0.0", - "version": "9999999-dev", - "package": "react/socket" + "package": "guzzlehttp/psr7", + "version": "dev-master", + "alias": "1.6.1", + "alias_normalized": "1.6.1.0" } ], "minimum-stability": "dev", @@ -8456,5 +8403,5 @@ "ext-json": "*" }, "platform-dev": [], - "plugin-api-version": "1.1.0" + "plugin-api-version": "2.0.0" } From 74236b68634088c18bcd3e7e255a46f4c0eb5272 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 4 Sep 2020 14:30:06 +0000 Subject: [PATCH 06/10] Bump symfony/http-kernel from 5.1.2 to 5.1.5 Bumps [symfony/http-kernel](https://github.com/symfony/http-kernel) from 5.1.2 to 5.1.5. - [Release notes](https://github.com/symfony/http-kernel/releases) - [Changelog](https://github.com/symfony/http-kernel/blob/master/CHANGELOG.md) - [Commits](https://github.com/symfony/http-kernel/compare/v5.1.2...v5.1.5) Signed-off-by: dependabot[bot] --- composer.lock | 187 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 114 insertions(+), 73 deletions(-) diff --git a/composer.lock b/composer.lock index 65ce9f1..df609b9 100644 --- a/composer.lock +++ b/composer.lock @@ -3928,6 +3928,7 @@ "keywords": [ "tokenizer" ], + "abandoned": true, "time": "2019-09-17T06:23:10+00:00" }, { @@ -5007,12 +5008,12 @@ "source": { "type": "git", "url": "https://github.com/reactphp/socket.git", - "reference": "842dcd71df86671ee9491734035b3d2cf4a80ece" + "reference": "e2b96b23a13ca9b41ab343268dbce3f8ef4d524a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/socket/zipball/842dcd71df86671ee9491734035b3d2cf4a80ece", - "reference": "842dcd71df86671ee9491734035b3d2cf4a80ece", + "url": "https://api.github.com/repos/reactphp/socket/zipball/e2b96b23a13ca9b41ab343268dbce3f8ef4d524a", + "reference": "e2b96b23a13ca9b41ab343268dbce3f8ef4d524a", "shasum": "" }, "require": { @@ -5026,7 +5027,7 @@ }, "require-dev": { "clue/block-react": "^1.2", - "phpunit/phpunit": "^9.0 || ^5.7 || ^4.8.35", + "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35", "react/promise-stream": "^1.2" }, "type": "library", @@ -5039,6 +5040,28 @@ "license": [ "MIT" ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], "description": "Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for ReactPHP", "keywords": [ "Connection", @@ -5047,7 +5070,17 @@ "reactphp", "stream" ], - "time": "2020-07-01T12:50:00+00:00" + "funding": [ + { + "url": "https://github.com/WyriHaximus", + "type": "github" + }, + { + "url": "https://github.com/clue", + "type": "github" + } + ], + "time": "2020-08-28T12:49:05+00:00" }, { "name": "react/stream", @@ -6081,16 +6114,16 @@ }, { "name": "symfony/deprecation-contracts", - "version": "v2.1.2", + "version": "v2.1.3", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "dd99cb3a0aff6cadd2a8d7d7ed72c2161e218337" + "reference": "5e20b83385a77593259c9f8beb2c43cd03b2ac14" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/dd99cb3a0aff6cadd2a8d7d7ed72c2161e218337", - "reference": "dd99cb3a0aff6cadd2a8d7d7ed72c2161e218337", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/5e20b83385a77593259c9f8beb2c43cd03b2ac14", + "reference": "5e20b83385a77593259c9f8beb2c43cd03b2ac14", "shasum": "" }, "require": { @@ -6100,6 +6133,10 @@ "extra": { "branch-alias": { "dev-master": "2.1-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" } }, "autoload": { @@ -6137,20 +6174,20 @@ "type": "tidelift" } ], - "time": "2020-05-27T08:34:37+00:00" + "time": "2020-06-06T08:49:21+00:00" }, { "name": "symfony/error-handler", - "version": "v5.1.2", + "version": "v5.1.5", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "7d0b927b9d3dc41d7d46cda38cbfcd20cdcbb896" + "reference": "525636d4b84e06c6ca72d96b6856b5b169416e6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/7d0b927b9d3dc41d7d46cda38cbfcd20cdcbb896", - "reference": "7d0b927b9d3dc41d7d46cda38cbfcd20cdcbb896", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/525636d4b84e06c6ca72d96b6856b5b169416e6a", + "reference": "525636d4b84e06c6ca72d96b6856b5b169416e6a", "shasum": "" }, "require": { @@ -6208,20 +6245,20 @@ "type": "tidelift" } ], - "time": "2020-05-30T20:35:19+00:00" + "time": "2020-08-17T10:01:29+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v5.1.2", + "version": "v5.1.5", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "cc0d059e2e997e79ca34125a52f3e33de4424ac7" + "reference": "94871fc0a69c3c5da57764187724cdce0755899c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/cc0d059e2e997e79ca34125a52f3e33de4424ac7", - "reference": "cc0d059e2e997e79ca34125a52f3e33de4424ac7", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/94871fc0a69c3c5da57764187724cdce0755899c", + "reference": "94871fc0a69c3c5da57764187724cdce0755899c", "shasum": "" }, "require": { @@ -6294,20 +6331,20 @@ "type": "tidelift" } ], - "time": "2020-05-20T17:43:50+00:00" + "time": "2020-08-13T14:19:42+00:00" }, { "name": "symfony/event-dispatcher-contracts", - "version": "v2.1.2", + "version": "v2.1.3", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "405952c4e90941a17e52ef7489a2bd94870bb290" + "reference": "f6f613d74cfc5a623fc36294d3451eb7fa5a042b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/405952c4e90941a17e52ef7489a2bd94870bb290", - "reference": "405952c4e90941a17e52ef7489a2bd94870bb290", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/f6f613d74cfc5a623fc36294d3451eb7fa5a042b", + "reference": "f6f613d74cfc5a623fc36294d3451eb7fa5a042b", "shasum": "" }, "require": { @@ -6321,6 +6358,10 @@ "extra": { "branch-alias": { "dev-master": "2.1-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" } }, "autoload": { @@ -6366,7 +6407,7 @@ "type": "tidelift" } ], - "time": "2020-05-20T17:43:50+00:00" + "time": "2020-07-06T13:23:11+00:00" }, { "name": "symfony/expression-language", @@ -6499,16 +6540,16 @@ }, { "name": "symfony/http-foundation", - "version": "v5.1.2", + "version": "v5.1.5", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "f93055171b847915225bd5b0a5792888419d8d75" + "reference": "41a4647f12870e9d41d9a7d72ff0614a27208558" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/f93055171b847915225bd5b0a5792888419d8d75", - "reference": "f93055171b847915225bd5b0a5792888419d8d75", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/41a4647f12870e9d41d9a7d72ff0614a27208558", + "reference": "41a4647f12870e9d41d9a7d72ff0614a27208558", "shasum": "" }, "require": { @@ -6570,20 +6611,20 @@ "type": "tidelift" } ], - "time": "2020-06-15T06:52:54+00:00" + "time": "2020-08-17T07:48:54+00:00" }, { "name": "symfony/http-kernel", - "version": "v5.1.2", + "version": "v5.1.5", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "a18c27ace1ef344ffcb129a5b089bad7643b387a" + "reference": "3e32676e6cb5d2081c91a56783471ff8a7f7110b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/a18c27ace1ef344ffcb129a5b089bad7643b387a", - "reference": "a18c27ace1ef344ffcb129a5b089bad7643b387a", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/3e32676e6cb5d2081c91a56783471ff8a7f7110b", + "reference": "3e32676e6cb5d2081c91a56783471ff8a7f7110b", "shasum": "" }, "require": { @@ -6683,7 +6724,7 @@ "type": "tidelift" } ], - "time": "2020-06-15T13:51:38+00:00" + "time": "2020-09-02T08:15:18+00:00" }, { "name": "symfony/mime", @@ -6764,16 +6805,16 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.17.1", + "version": "v1.18.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "2edd75b8b35d62fd3eeabba73b26b8f1f60ce13d" + "reference": "1c302646f6efc070cd46856e600e5e0684d6b454" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/2edd75b8b35d62fd3eeabba73b26b8f1f60ce13d", - "reference": "2edd75b8b35d62fd3eeabba73b26b8f1f60ce13d", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/1c302646f6efc070cd46856e600e5e0684d6b454", + "reference": "1c302646f6efc070cd46856e600e5e0684d6b454", "shasum": "" }, "require": { @@ -6785,7 +6826,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.17-dev" + "dev-master": "1.18-dev" }, "thanks": { "name": "symfony/polyfill", @@ -6836,7 +6877,7 @@ "type": "tidelift" } ], - "time": "2020-06-06T08:46:27+00:00" + "time": "2020-07-14T12:35:20+00:00" }, { "name": "symfony/polyfill-intl-grapheme", @@ -7079,16 +7120,16 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.17.1", + "version": "v1.18.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "7110338d81ce1cbc3e273136e4574663627037a7" + "reference": "a6977d63bf9a0ad4c65cd352709e230876f9904a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/7110338d81ce1cbc3e273136e4574663627037a7", - "reference": "7110338d81ce1cbc3e273136e4574663627037a7", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/a6977d63bf9a0ad4c65cd352709e230876f9904a", + "reference": "a6977d63bf9a0ad4c65cd352709e230876f9904a", "shasum": "" }, "require": { @@ -7100,7 +7141,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.17-dev" + "dev-master": "1.18-dev" }, "thanks": { "name": "symfony/polyfill", @@ -7152,7 +7193,7 @@ "type": "tidelift" } ], - "time": "2020-06-06T08:46:27+00:00" + "time": "2020-07-14T12:35:20+00:00" }, { "name": "symfony/polyfill-php72", @@ -7225,16 +7266,16 @@ }, { "name": "symfony/polyfill-php73", - "version": "v1.17.1", + "version": "v1.18.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "fa0837fe02d617d31fbb25f990655861bb27bd1a" + "reference": "fffa1a52a023e782cdcc221d781fe1ec8f87fcca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/fa0837fe02d617d31fbb25f990655861bb27bd1a", - "reference": "fa0837fe02d617d31fbb25f990655861bb27bd1a", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/fffa1a52a023e782cdcc221d781fe1ec8f87fcca", + "reference": "fffa1a52a023e782cdcc221d781fe1ec8f87fcca", "shasum": "" }, "require": { @@ -7243,7 +7284,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.17-dev" + "dev-master": "1.18-dev" }, "thanks": { "name": "symfony/polyfill", @@ -7297,20 +7338,20 @@ "type": "tidelift" } ], - "time": "2020-06-06T08:46:27+00:00" + "time": "2020-07-14T12:35:20+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.17.1", + "version": "v1.18.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "4a5b6bba3259902e386eb80dd1956181ee90b5b2" + "reference": "d87d5766cbf48d72388a9f6b85f280c8ad51f981" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/4a5b6bba3259902e386eb80dd1956181ee90b5b2", - "reference": "4a5b6bba3259902e386eb80dd1956181ee90b5b2", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/d87d5766cbf48d72388a9f6b85f280c8ad51f981", + "reference": "d87d5766cbf48d72388a9f6b85f280c8ad51f981", "shasum": "" }, "require": { @@ -7319,7 +7360,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.17-dev" + "dev-master": "1.18-dev" }, "thanks": { "name": "symfony/polyfill", @@ -7377,7 +7418,7 @@ "type": "tidelift" } ], - "time": "2020-06-06T08:46:27+00:00" + "time": "2020-07-14T12:35:20+00:00" }, { "name": "symfony/process", @@ -7921,16 +7962,16 @@ }, { "name": "symfony/var-dumper", - "version": "v5.1.2", + "version": "v5.1.5", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "46a942903059b0b05e601f00eb64179e05578c0f" + "reference": "b43a3905262bcf97b2510f0621f859ca4f5287be" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/46a942903059b0b05e601f00eb64179e05578c0f", - "reference": "46a942903059b0b05e601f00eb64179e05578c0f", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/b43a3905262bcf97b2510f0621f859ca4f5287be", + "reference": "b43a3905262bcf97b2510f0621f859ca4f5287be", "shasum": "" }, "require": { @@ -8007,7 +8048,7 @@ "type": "tidelift" } ], - "time": "2020-05-30T20:35:19+00:00" + "time": "2020-08-17T07:42:30+00:00" }, { "name": "symfony/var-exporter", @@ -8378,16 +8419,16 @@ ], "aliases": [ { - "package": "react/socket", - "version": "dev-master", - "alias": "1.1", - "alias_normalized": "1.1.0.0" + "alias": "1.6.1", + "alias_normalized": "1.6.1.0", + "version": "9999999-dev", + "package": "guzzlehttp/psr7" }, { - "package": "guzzlehttp/psr7", - "version": "dev-master", - "alias": "1.6.1", - "alias_normalized": "1.6.1.0" + "alias": "1.1", + "alias_normalized": "1.1.0.0", + "version": "9999999-dev", + "package": "react/socket" } ], "minimum-stability": "dev", @@ -8403,5 +8444,5 @@ "ext-json": "*" }, "platform-dev": [], - "plugin-api-version": "2.0.0" + "plugin-api-version": "1.1.0" } From 47b2350631eb3db25eeebce7b9507e307117b213 Mon Sep 17 00:00:00 2001 From: Marcel Pociot Date: Mon, 7 Sep 2020 13:33:40 +0200 Subject: [PATCH 07/10] Associate shared sites with auth tokens --- app/Contracts/ConnectionManager.php | 2 + app/Server/Connections/ConnectionManager.php | 26 ++- app/Server/Connections/ControlConnection.php | 5 +- app/Server/Factory.php | 2 + .../Admin/GetUserDetailsController.php | 33 +++ .../Controllers/ControlMessageController.php | 2 +- .../UserRepository/DatabaseUserRepository.php | 28 ++- tests/Feature/Server/AdminTest.php | 3 + tests/Feature/Server/ApiTest.php | 202 ++++++++++++++++++ 9 files changed, 297 insertions(+), 6 deletions(-) create mode 100644 app/Server/Http/Controllers/Admin/GetUserDetailsController.php create mode 100644 tests/Feature/Server/ApiTest.php diff --git a/app/Contracts/ConnectionManager.php b/app/Contracts/ConnectionManager.php index a208ef6..813bdd8 100644 --- a/app/Contracts/ConnectionManager.php +++ b/app/Contracts/ConnectionManager.php @@ -23,4 +23,6 @@ interface ConnectionManager public function findControlConnectionForClientId(string $clientId): ?ControlConnection; public function getConnections(): array; + + public function getConnectionsForAuthToken(string $authToken): array; } diff --git a/app/Server/Connections/ConnectionManager.php b/app/Server/Connections/ConnectionManager.php index 00f2034..cb0186d 100644 --- a/app/Server/Connections/ConnectionManager.php +++ b/app/Server/Connections/ConnectionManager.php @@ -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; @@ -46,7 +47,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; @@ -99,4 +106,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(); + } } diff --git a/app/Server/Connections/ControlConnection.php b/app/Server/Connections/ControlConnection.php index bc821b2..ef3198f 100644 --- a/app/Server/Connections/ControlConnection.php +++ b/app/Server/Connections/ControlConnection.php @@ -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(); } @@ -57,6 +59,7 @@ class ControlConnection return [ 'host' => $this->host, 'client_id' => $this->client_id, + 'auth_token' => $this->authToken, 'subdomain' => $this->subdomain, 'shared_at' => $this->shared_at, ]; diff --git a/app/Server/Factory.php b/app/Server/Factory.php index 88d4088..29dbf90 100644 --- a/app/Server/Factory.php +++ b/app/Server/Factory.php @@ -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); diff --git a/app/Server/Http/Controllers/Admin/GetUserDetailsController.php b/app/Server/Http/Controllers/Admin/GetUserDetailsController.php new file mode 100644 index 0000000..2cb3a52 --- /dev/null +++ b/app/Server/Http/Controllers/Admin/GetUserDetailsController.php @@ -0,0 +1,33 @@ +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(); + }); + } +} diff --git a/app/Server/Http/Controllers/ControlMessageController.php b/app/Server/Http/Controllers/ControlMessageController.php index d19fb9e..77c6550 100644 --- a/app/Server/Http/Controllers/ControlMessageController.php +++ b/app/Server/Http/Controllers/ControlMessageController.php @@ -127,7 +127,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(); diff --git a/app/Server/UserRepository/DatabaseUserRepository.php b/app/Server/UserRepository/DatabaseUserRepository.php index 973e798..5f61cb0 100644 --- a/app/Server/UserRepository/DatabaseUserRepository.php +++ b/app/Server/UserRepository/DatabaseUserRepository.php @@ -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(); diff --git a/tests/Feature/Server/AdminTest.php b/tests/Feature/Server/AdminTest.php index 1b8519a..e973891 100644 --- a/tests/Feature/Server/AdminTest.php +++ b/tests/Feature/Server/AdminTest.php @@ -8,6 +8,7 @@ use Clue\React\Buzz\Browser; use Clue\React\Buzz\Message\ResponseException; use GuzzleHttp\Psr7\Response; use Illuminate\Support\Str; +use Nyholm\Psr7\Request; use Psr\Http\Message\ResponseInterface; use Ratchet\Server\IoConnection; use Tests\Feature\TestCase; @@ -149,6 +150,8 @@ class AdminTest extends TestCase $connectionManager = app(ConnectionManager::class); $connection = \Mockery::mock(IoConnection::class); + $connection->httpRequest = new Request('GET', '/?authToken=some-token'); + $connectionManager->storeConnection('some-host.text', 'fixed-subdomain', $connection); /** @var Response $response */ diff --git a/tests/Feature/Server/ApiTest.php b/tests/Feature/Server/ApiTest.php new file mode 100644 index 0000000..8755723 --- /dev/null +++ b/tests/Feature/Server/ApiTest.php @@ -0,0 +1,202 @@ +browser = new Browser($this->loop); + $this->browser = $this->browser->withOptions([ + 'followRedirects' => false, + ]); + + $this->startServer(); + } + + public function tearDown(): void + { + $this->serverFactory->getSocket()->close(); + + parent::tearDown(); + } + + /** @test */ + public function it_can_list_all_registered_users() + { + /** @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', + ]))); + + /** @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([], $users[0]->sites); + } + + /** @test */ + public function it_can_get_user_details() + { + /** @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', + ]))); + + /** @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()); + $user = $body->user; + + $this->assertSame('Marcel', $user->name); + $this->assertSame([], $user->sites); + } + + /** @test */ + public function it_can_list_all_currently_connected_sites_from_all_users() + { + /** @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', + ]))); + + $createdUser = json_decode($response->getBody()->getContents())->user; + + /** @var ConnectionManager $connectionManager */ + $connectionManager = app(ConnectionManager::class); + + $connection = \Mockery::mock(IoConnection::class); + $connection->httpRequest = new Request('GET', '/?authToken='.$createdUser->auth_token); + $connectionManager->storeConnection('some-host.test', 'fixed-subdomain', $connection); + + $connection = \Mockery::mock(IoConnection::class); + $connection->httpRequest = new Request('GET', '/?authToken=some-other-token'); + $connectionManager->storeConnection('some-different-host.test', 'different-subdomain', $connection); + + /** @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[0]->sites); + $this->assertSame('some-host.test', $users[0]->sites[0]->host); + $this->assertSame('fixed-subdomain', $users[0]->sites[0]->subdomain); + } + + /** @test */ + public function it_can_list_all_currently_connected_sites() + { + /** @var ConnectionManager $connectionManager */ + $connectionManager = app(ConnectionManager::class); + + $connection = \Mockery::mock(IoConnection::class); + $connection->httpRequest = new Request('GET', '/?authToken=some-token'); + + $connectionManager->storeConnection('some-host.test', 'fixed-subdomain', $connection); + + /** @var Response $response */ + $response = $this->await($this->browser->get('http://127.0.0.1:8080/api/sites', [ + 'Host' => 'expose.localhost', + 'Authorization' => base64_encode('username:secret'), + 'Content-Type' => 'application/json', + ])); + + $body = json_decode($response->getBody()->getContents()); + $sites = $body->sites; + + $this->assertCount(1, $sites); + $this->assertSame('some-host.test', $sites[0]->host); + $this->assertSame('some-token', $sites[0]->auth_token); + $this->assertSame('fixed-subdomain', $sites[0]->subdomain); + } + + /** @test */ + public function it_can_list_all_currently_connected_sites_without_auth_tokens() + { + /** @var ConnectionManager $connectionManager */ + $connectionManager = app(ConnectionManager::class); + + $connection = \Mockery::mock(IoConnection::class); + $connection->httpRequest = new Request('GET', '/'); + + $connectionManager->storeConnection('some-host.test', 'fixed-subdomain', $connection); + + /** @var Response $response */ + $response = $this->await($this->browser->get('http://127.0.0.1:8080/api/sites', [ + 'Host' => 'expose.localhost', + 'Authorization' => base64_encode('username:secret'), + 'Content-Type' => 'application/json', + ])); + + $body = json_decode($response->getBody()->getContents()); + $sites = $body->sites; + + $this->assertCount(1, $sites); + $this->assertSame('some-host.test', $sites[0]->host); + $this->assertSame('', $sites[0]->auth_token); + $this->assertSame('fixed-subdomain', $sites[0]->subdomain); + } + + protected function startServer() + { + $this->app['config']['expose.admin.subdomain'] = 'expose'; + $this->app['config']['expose.admin.database'] = ':memory:'; + + $this->app['config']['expose.admin.users'] = [ + 'username' => 'secret', + ]; + + $this->serverFactory = new Factory(); + + $this->serverFactory->setLoop($this->loop) + ->createServer(); + } +} From 9363e97d8127a57424460c0a621e9b226b46c7c4 Mon Sep 17 00:00:00 2001 From: Marcel Pociot Date: Mon, 7 Sep 2020 11:34:05 +0000 Subject: [PATCH 08/10] Apply fixes from StyleCI --- app/Server/Http/Controllers/ControlMessageController.php | 1 - tests/Feature/Server/ApiTest.php | 1 - 2 files changed, 2 deletions(-) diff --git a/app/Server/Http/Controllers/ControlMessageController.php b/app/Server/Http/Controllers/ControlMessageController.php index 77c6550..9ebe053 100644 --- a/app/Server/Http/Controllers/ControlMessageController.php +++ b/app/Server/Http/Controllers/ControlMessageController.php @@ -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; diff --git a/tests/Feature/Server/ApiTest.php b/tests/Feature/Server/ApiTest.php index 8755723..ad53dc8 100644 --- a/tests/Feature/Server/ApiTest.php +++ b/tests/Feature/Server/ApiTest.php @@ -6,7 +6,6 @@ use App\Contracts\ConnectionManager; use App\Server\Factory; use Clue\React\Buzz\Browser; use GuzzleHttp\Psr7\Response; -use Illuminate\Support\Str; use Nyholm\Psr7\Request; use Ratchet\Server\IoConnection; use Tests\Feature\TestCase; From 0ebe6a4ce4256a23a30e0c200a311a76120a3c82 Mon Sep 17 00:00:00 2001 From: Marcel Pociot Date: Mon, 7 Sep 2020 20:19:56 +0200 Subject: [PATCH 09/10] Add a new flag to users to allow the specification of custom subdomains --- .../Admin/StoreUsersController.php | 1 + .../Controllers/ControlMessageController.php | 19 ++++- .../UserRepository/DatabaseUserRepository.php | 4 +- config/expose.php | 2 + .../02_add_feature_flags_to_users_table.sql | 1 + resources/views/server/users/index.twig | 34 +++++++- tests/Feature/Server/TunnelTest.php | 85 ++++++++++++++++++- 7 files changed, 137 insertions(+), 9 deletions(-) create mode 100644 database/migrations/02_add_feature_flags_to_users_table.sql diff --git a/app/Server/Http/Controllers/Admin/StoreUsersController.php b/app/Server/Http/Controllers/Admin/StoreUsersController.php index 3b78fd9..8a0cb61 100644 --- a/app/Server/Http/Controllers/Admin/StoreUsersController.php +++ b/app/Server/Http/Controllers/Admin/StoreUsersController.php @@ -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 diff --git a/app/Server/Http/Controllers/ControlMessageController.php b/app/Server/Http/Controllers/ControlMessageController.php index 77c6550..d2d4f40 100644 --- a/app/Server/Http/Controllers/ControlMessageController.php +++ b/app/Server/Http/Controllers/ControlMessageController.php @@ -5,6 +5,7 @@ namespace App\Server\Http\Controllers; use App\Contracts\ConnectionManager; use App\Contracts\UserRepository; use App\Http\QueryParameters; +use Illuminate\Support\Arr; use Ratchet\ConnectionInterface; use Ratchet\WebSocket\MessageComponentInterface; use React\Promise\Deferred; @@ -77,8 +78,8 @@ class ControlMessageController implements MessageComponentInterface protected function authenticate(ConnectionInterface $connection, $data) { $this->verifyAuthToken($connection) - ->then(function () use ($connection, $data) { - if (! $this->hasValidSubdomain($connection, $data->subdomain)) { + ->then(function ($user) use ($connection, $data) { + if (! $this->hasValidSubdomain($connection, $data->subdomain, $user)) { return; } @@ -147,8 +148,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')) { diff --git a/app/Server/UserRepository/DatabaseUserRepository.php b/app/Server/UserRepository/DatabaseUserRepository.php index 5f61cb0..43b15b1 100644 --- a/app/Server/UserRepository/DatabaseUserRepository.php +++ b/app/Server/UserRepository/DatabaseUserRepository.php @@ -113,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]) diff --git a/config/expose.php b/config/expose.php index e08b519..7835ceb 100644 --- a/config/expose.php +++ b/config/expose.php @@ -230,6 +230,8 @@ return [ 'invalid_auth_token' => 'Authentication failed. Please check your authentication token and try again.', 'subdomain_taken' => 'The chosen subdomain :subdomain is already taken. Please choose a different subdomain.', + + 'custom_subdomain_unauthorized' => 'You are not allowed to specify custom subdomains. Please upgrade to Expose Pro.', ], ], ]; diff --git a/database/migrations/02_add_feature_flags_to_users_table.sql b/database/migrations/02_add_feature_flags_to_users_table.sql new file mode 100644 index 0000000..520d8c3 --- /dev/null +++ b/database/migrations/02_add_feature_flags_to_users_table.sql @@ -0,0 +1 @@ +ALTER TABLE users ADD can_specify_subdomains BOOLEAN DEFAULT 1; diff --git a/resources/views/server/users/index.twig b/resources/views/server/users/index.twig index 6d2ec62..fbdd6e7 100644 --- a/resources/views/server/users/index.twig +++ b/resources/views/server/users/index.twig @@ -24,6 +24,25 @@ +
+ +
+
+
+ + +
+
+
+
@@ -51,6 +70,9 @@ Auth-Token + + Custom Subdomains + Created At @@ -65,6 +87,14 @@ @{ user.auth_token } + + + No + + + Yes + + @{ user.created_at } @@ -113,6 +143,7 @@ data: { userForm: { name: '', + can_specify_subdomains: true, errors: {}, }, paginated: {{ paginated|json_encode|raw }} @@ -140,7 +171,7 @@ }).then((response) => { return response.json(); }).then((data) => { - this.users = this.users.filter(u => u.id !== user.id); + this.getUsers(1) }); }, saveUser() { @@ -155,6 +186,7 @@ }).then((data) => { if (data.user) { this.userForm.name = ''; + this.userForm.can_specify_subdomains = 0; this.userForm.errors = {}; this.users.unshift(data.user); } diff --git a/tests/Feature/Server/TunnelTest.php b/tests/Feature/Server/TunnelTest.php index ed60749..cc612f6 100644 --- a/tests/Feature/Server/TunnelTest.php +++ b/tests/Feature/Server/TunnelTest.php @@ -27,6 +27,9 @@ class TunnelTest extends TestCase parent::setUp(); $this->browser = new Browser($this->loop); + $this->browser = $this->browser->withOptions([ + 'followRedirects' => false, + ]); $this->startServer(); } @@ -58,6 +61,8 @@ class TunnelTest extends TestCase { $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. @@ -98,22 +103,96 @@ class TunnelTest extends TestCase { $this->app['config']['expose.admin.validate_auth_tokens'] = true; - $this->createTestHttpServer(); + $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, + ]))); - $this->expectException(\UnexpectedValueException::class); + $user = json_decode($response->getBody()->getContents())->user; + + $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')); + $response = $this->await($client->connectToServer('127.0.0.1:8085', 'tunnel', $user->auth_token)); + + $this->assertSame('tunnel', $response->subdomain); + } + + /** @test */ + public function it_rejects_clients_to_specify_custom_subdomains() + { + $this->app['config']['expose.admin.validate_auth_tokens'] = true; + + $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' => 0, + ]))); + + $this->expectException(\UnexpectedValueException::class); + + $user = json_decode($response->getBody()->getContents())->user; + + $this->createTestHttpServer(); + + /** + * We create an expose client that connects to our server and shares + * the created test HTTP server. + */ + $client = $this->createClient(); + $response = $this->await($client->connectToServer('127.0.0.1:8085', 'tunnel', $user->auth_token)); + + $this->assertSame('tunnel', $response->subdomain); + } + + /** @test */ + public function it_allows_clients_to_use_random_subdomains_if_custom_subdomains_are_forbidden() + { + $this->app['config']['expose.admin.validate_auth_tokens'] = true; + + $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' => 0, + ]))); + + $user = json_decode($response->getBody()->getContents())->user; + + $this->createTestHttpServer(); + + /** + * We create an expose client that connects to our server and shares + * the created test HTTP server. + */ + $client = $this->createClient(); + $response = $this->await($client->connectToServer('127.0.0.1:8085', '', $user->auth_token)); + + $this->assertInstanceOf(\stdClass::class, $response); } protected function startServer() { + $this->app['config']['expose.admin.subdomain'] = 'expose'; $this->app['config']['expose.admin.database'] = ':memory:'; + $this->app['config']['expose.admin.users'] = [ + 'username' => 'secret', + ]; + $this->serverFactory = new Factory(); $this->serverFactory->setLoop($this->loop) From d9ab55f308fbb20454a6de4d920e5ef4420b050c Mon Sep 17 00:00:00 2001 From: Marcel Pociot Date: Mon, 7 Sep 2020 18:20:13 +0000 Subject: [PATCH 10/10] Apply fixes from StyleCI --- app/Server/Http/Controllers/Admin/StoreUsersController.php | 2 +- app/Server/Http/Controllers/ControlMessageController.php | 2 -- tests/Feature/Server/ApiTest.php | 1 - 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/app/Server/Http/Controllers/Admin/StoreUsersController.php b/app/Server/Http/Controllers/Admin/StoreUsersController.php index 8a0cb61..14e3ff8 100644 --- a/app/Server/Http/Controllers/Admin/StoreUsersController.php +++ b/app/Server/Http/Controllers/Admin/StoreUsersController.php @@ -39,7 +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') + 'can_specify_subdomains' => (int) $request->get('can_specify_subdomains'), ]; $this->userRepository diff --git a/app/Server/Http/Controllers/ControlMessageController.php b/app/Server/Http/Controllers/ControlMessageController.php index d2d4f40..729a73f 100644 --- a/app/Server/Http/Controllers/ControlMessageController.php +++ b/app/Server/Http/Controllers/ControlMessageController.php @@ -5,11 +5,9 @@ namespace App\Server\Http\Controllers; use App\Contracts\ConnectionManager; use App\Contracts\UserRepository; use App\Http\QueryParameters; -use Illuminate\Support\Arr; use Ratchet\ConnectionInterface; use Ratchet\WebSocket\MessageComponentInterface; use React\Promise\Deferred; -use React\Promise\FulfilledPromise; use React\Promise\PromiseInterface; use stdClass; diff --git a/tests/Feature/Server/ApiTest.php b/tests/Feature/Server/ApiTest.php index 8755723..ad53dc8 100644 --- a/tests/Feature/Server/ApiTest.php +++ b/tests/Feature/Server/ApiTest.php @@ -6,7 +6,6 @@ use App\Contracts\ConnectionManager; use App\Server\Factory; use Clue\React\Buzz\Browser; use GuzzleHttp\Psr7\Response; -use Illuminate\Support\Str; use Nyholm\Psr7\Request; use Ratchet\Server\IoConnection; use Tests\Feature\TestCase;