diff --git a/wcfsetup/install/files/acp/templates/systemCheck.tpl b/wcfsetup/install/files/acp/templates/systemCheck.tpl index 4d7e9bae99f..eaab63407c0 100644 --- a/wcfsetup/install/files/acp/templates/systemCheck.tpl +++ b/wcfsetup/install/files/acp/templates/systemCheck.tpl @@ -13,6 +13,17 @@

{lang}wcf.acp.systemCheck.result{/lang}

+ +
{lang}wcf.acp.systemCheck.web{/lang}
+
+ {if $results[status][web]} + {@$statusOk} {lang}wcf.acp.systemCheck.pass{/lang} + {else} + {@$statusInsufficient} {lang}wcf.acp.systemCheck.insufficient{/lang} + {/if} +
+ +
{lang}wcf.acp.systemCheck.php{/lang}
@@ -51,6 +62,22 @@
+
+

{lang}wcf.acp.systemCheck.web{/lang}

+ + +
{lang}wcf.acp.systemCheck.web.https{/lang}
+
+ {if $results[web][https]} + {@$statusOk} {lang}wcf.acp.systemCheck.pass{/lang} + {else} + {@$statusInsufficient} {lang}wcf.acp.systemCheck.notSupported{/lang} + {/if} + {lang}wcf.acp.systemCheck.web.https.description{/lang} +
+ +
+

{lang}wcf.acp.systemCheck.php{/lang}

diff --git a/wcfsetup/install/files/acp/update_com.woltlab.wcf_6.1_checkSystemRequirements.php b/wcfsetup/install/files/acp/update_com.woltlab.wcf_6.1_checkSystemRequirements.php new file mode 100644 index 00000000000..d6521c53f43 --- /dev/null +++ b/wcfsetup/install/files/acp/update_com.woltlab.wcf_6.1_checkSystemRequirements.php @@ -0,0 +1,37 @@ + + * @since 6.1 + */ + +use wcf\system\request\RouteHandler; +use wcf\system\WCF; + +$checkForTls = function () { + if (RouteHandler::secureConnection()) { + return true; + } + + // @see RouteHandler::secureContext() + $host = $_SERVER['HTTP_HOST']; + if ($host === '127.0.0.1' || $host === 'localhost' || \str_ends_with($host, '.localhost')) { + return true; + } + + return false; +}; + +if (!$checkForTls()) { + if (WCF::getLanguage()->getFixedLanguageCode() === 'de') { + $message = "Die Seite wird nicht über HTTPS aufgerufen. Wichtige Funktionen stehen dadurch nicht zur Verfügung, die für die korrekte Funktionsweise der Software erforderlich sind."; + } else { + $message = "The page is not accessed via HTTPS. Important features that are required for the proper operation of the software are therefore not available."; + } + + throw new \RuntimeException($message); +} diff --git a/wcfsetup/install/files/lib/acp/page/SystemCheckPage.class.php b/wcfsetup/install/files/lib/acp/page/SystemCheckPage.class.php index 19278c61bd7..450f2b8e0e2 100644 --- a/wcfsetup/install/files/lib/acp/page/SystemCheckPage.class.php +++ b/wcfsetup/install/files/lib/acp/page/SystemCheckPage.class.php @@ -8,6 +8,7 @@ use wcf\system\Environment; use wcf\system\exception\SystemException; use wcf\system\registry\RegistryHandler; +use wcf\system\request\RouteHandler; use wcf\system\WCF; use wcf\util\FileUtil; @@ -150,10 +151,14 @@ class SystemCheckPage extends AbstractPage ], 'x64' => false, ], + 'web' => [ + 'https' => false, + ], 'status' => [ 'directories' => false, 'mysql' => false, 'php' => false, + 'web' => false, ], ]; @@ -184,6 +189,7 @@ public function readData() $this->validatePhpVersion(); $this->validatePhpGdSupport(); $this->validateWritableDirectories(); + $this->validateWebHttps(); if ( $this->results['status']['mysql'] @@ -449,4 +455,14 @@ protected function makeDirectoryWritable($path) return true; } + + /** + * @since 6.1 + */ + protected function validateWebHttps(): void + { + $this->results['web']['https'] = RouteHandler::secureContext(); + + $this->results['status']['web'] = $this->results['web']['https']; + } } diff --git a/wcfsetup/install/files/lib/bootstrap/com.woltlab.wcf.php b/wcfsetup/install/files/lib/bootstrap/com.woltlab.wcf.php index 6b06c3296d6..f3554ed8ecd 100644 --- a/wcfsetup/install/files/lib/bootstrap/com.woltlab.wcf.php +++ b/wcfsetup/install/files/lib/bootstrap/com.woltlab.wcf.php @@ -117,9 +117,9 @@ static function (\wcf\event\acp\dashboard\box\BoxCollecting $event) { \wcf\event\endpoint\ControllerCollecting::class, static function (\wcf\event\endpoint\ControllerCollecting $event) { $event->register(new \wcf\system\endpoint\controller\core\files\DeleteFile); - $event->register(new \wcf\system\endpoint\controller\core\files\PostGenerateThumbnails); - $event->register(new \wcf\system\endpoint\controller\core\files\PostUpload); - $event->register(new \wcf\system\endpoint\controller\core\files\upload\PostChunk); + $event->register(new \wcf\system\endpoint\controller\core\files\GenerateThumbnails); + $event->register(new \wcf\system\endpoint\controller\core\files\PrepareUpload); + $event->register(new \wcf\system\endpoint\controller\core\files\upload\SaveChunk); $event->register(new \wcf\system\endpoint\controller\core\comments\CreateComment); $event->register(new \wcf\system\endpoint\controller\core\comments\DeleteComment); $event->register(new \wcf\system\endpoint\controller\core\comments\EditComment); diff --git a/wcfsetup/install/files/lib/http/middleware/CheckForTls.class.php b/wcfsetup/install/files/lib/http/middleware/CheckForTls.class.php new file mode 100644 index 00000000000..8fc785dfa43 --- /dev/null +++ b/wcfsetup/install/files/lib/http/middleware/CheckForTls.class.php @@ -0,0 +1,46 @@ + + * @since 6.1 + */ +final class CheckForTls implements MiddlewareInterface +{ + #[\Override] + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + if (RequestHandler::getInstance()->isACPRequest()) { + return $handler->handle($request); + } + + if (RouteHandler::secureContext()) { + return $handler->handle($request); + } + + return $this->redirectToHttps($request); + } + + private function redirectToHttps(ServerRequestInterface $request): ResponseInterface + { + $uri = $request->getUri()->withScheme('https'); + + return HeaderUtil::withNoCacheHeaders( + new RedirectResponse($uri) + ); + } +} diff --git a/wcfsetup/install/files/lib/system/WCFSetup.class.php b/wcfsetup/install/files/lib/system/WCFSetup.class.php index 8c1b0b17954..73c23af7ba7 100644 --- a/wcfsetup/install/files/lib/system/WCFSetup.class.php +++ b/wcfsetup/install/files/lib/system/WCFSetup.class.php @@ -421,7 +421,7 @@ protected function showSystemRequirements(): ResponseInterface $system['cookie']['result'] = !empty($_COOKIE['wcfsetup_cookietest']) && $_COOKIE['wcfsetup_cookietest'] == TMP_FILE_PREFIX; - $system['tls']['result'] = RouteHandler::secureConnection() || $system['hostname']['value'] == 'localhost'; + $system['tls']['result'] = RouteHandler::secureContext(); foreach ($system as $result) { if (!$result['result']) { diff --git a/wcfsetup/install/files/lib/system/acp/dashboard/box/StatusMessageAcpDashboardBox.class.php b/wcfsetup/install/files/lib/system/acp/dashboard/box/StatusMessageAcpDashboardBox.class.php index 8df91e3b299..75e3f0657ba 100644 --- a/wcfsetup/install/files/lib/system/acp/dashboard/box/StatusMessageAcpDashboardBox.class.php +++ b/wcfsetup/install/files/lib/system/acp/dashboard/box/StatusMessageAcpDashboardBox.class.php @@ -11,6 +11,7 @@ use wcf\system\Environment; use wcf\system\event\EventHandler; use wcf\system\registry\RegistryHandler; +use wcf\system\request\RouteHandler; use wcf\system\WCF; /** @@ -147,6 +148,13 @@ private function getBasicMessages(): array ); } + if (!RouteHandler::secureContext()) { + $messages[] = new StatusMessage( + StatusMessageType::Error, + WCF::getLanguage()->getDynamicVariable('wcf.acp.index.insecureContext') + ); + } + return $messages; } diff --git a/wcfsetup/install/files/lib/system/endpoint/controller/core/files/PostGenerateThumbnails.class.php b/wcfsetup/install/files/lib/system/endpoint/controller/core/files/GenerateThumbnails.class.php similarity index 93% rename from wcfsetup/install/files/lib/system/endpoint/controller/core/files/PostGenerateThumbnails.class.php rename to wcfsetup/install/files/lib/system/endpoint/controller/core/files/GenerateThumbnails.class.php index 01887f8faaf..e923fa442c2 100644 --- a/wcfsetup/install/files/lib/system/endpoint/controller/core/files/PostGenerateThumbnails.class.php +++ b/wcfsetup/install/files/lib/system/endpoint/controller/core/files/GenerateThumbnails.class.php @@ -10,11 +10,10 @@ use wcf\http\Helper; use wcf\system\endpoint\IController; use wcf\system\endpoint\PostRequest; -use wcf\system\exception\UserInputException; use wcf\system\file\processor\FileProcessor; #[PostRequest('/core/files/{id:\d+}/generatethumbnails')] -final class PostGenerateThumbnails implements IController +final class GenerateThumbnails implements IController { public function __invoke(ServerRequestInterface $request, array $variables): ResponseInterface { diff --git a/wcfsetup/install/files/lib/system/endpoint/controller/core/files/PostUpload.class.php b/wcfsetup/install/files/lib/system/endpoint/controller/core/files/PrepareUpload.class.php similarity index 98% rename from wcfsetup/install/files/lib/system/endpoint/controller/core/files/PostUpload.class.php rename to wcfsetup/install/files/lib/system/endpoint/controller/core/files/PrepareUpload.class.php index 899bd24f6e0..845e587e881 100644 --- a/wcfsetup/install/files/lib/system/endpoint/controller/core/files/PostUpload.class.php +++ b/wcfsetup/install/files/lib/system/endpoint/controller/core/files/PrepareUpload.class.php @@ -18,7 +18,7 @@ use wcf\util\JSON; #[PostRequest('/core/files/upload')] -final class PostUpload implements IController +final class PrepareUpload implements IController { public function __invoke(ServerRequestInterface $request, array $variables): ResponseInterface { diff --git a/wcfsetup/install/files/lib/system/endpoint/controller/core/files/upload/PostChunk.class.php b/wcfsetup/install/files/lib/system/endpoint/controller/core/files/upload/SaveChunk.class.php similarity index 99% rename from wcfsetup/install/files/lib/system/endpoint/controller/core/files/upload/PostChunk.class.php rename to wcfsetup/install/files/lib/system/endpoint/controller/core/files/upload/SaveChunk.class.php index 00e33455774..02f65da5a87 100644 --- a/wcfsetup/install/files/lib/system/endpoint/controller/core/files/upload/PostChunk.class.php +++ b/wcfsetup/install/files/lib/system/endpoint/controller/core/files/upload/SaveChunk.class.php @@ -15,7 +15,7 @@ use wcf\system\io\File; #[PostRequest('/core/files/upload/{identifier}/chunk/{sequenceNo:\d+}')] -final class PostChunk implements IController +final class SaveChunk implements IController { /** * Read data in chunks to avoid hitting the memory limit. diff --git a/wcfsetup/install/files/lib/system/request/RequestHandler.class.php b/wcfsetup/install/files/lib/system/request/RequestHandler.class.php index 6ce5f4261f2..b4c06bf17c6 100644 --- a/wcfsetup/install/files/lib/system/request/RequestHandler.class.php +++ b/wcfsetup/install/files/lib/system/request/RequestHandler.class.php @@ -20,6 +20,7 @@ use wcf\http\middleware\CheckForForceLogin; use wcf\http\middleware\CheckForMultifactorRequirement; use wcf\http\middleware\CheckForOfflineMode; +use wcf\http\middleware\CheckForTls; use wcf\http\middleware\CheckHttpMethod; use wcf\http\middleware\CheckSystemEnvironment; use wcf\http\middleware\CheckUserBan; @@ -144,6 +145,7 @@ public function handle(string $application = 'wcf', bool $isACPRequest = false): new EnforceAcpAuthentication(), new CheckForEnterpriseNonOwnerAccess(), new CheckForExpiredAppEvaluation(), + new CheckForTls(), new CheckForOfflineMode(), new CheckForForceLogin(), new CheckForMultifactorRequirement(), diff --git a/wcfsetup/install/files/lib/system/request/RouteHandler.class.php b/wcfsetup/install/files/lib/system/request/RouteHandler.class.php index f4ba8ce1977..adb6c970f35 100644 --- a/wcfsetup/install/files/lib/system/request/RouteHandler.class.php +++ b/wcfsetup/install/files/lib/system/request/RouteHandler.class.php @@ -263,6 +263,34 @@ public static function secureConnection(): bool return self::$secure; } + /** + * Returns true if the current environment is treated as a secure context by + * browsers. + * + * @see https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts#when_is_a_context_considered_secure + * @since 6.1 + */ + public static function secureContext(): bool + { + static $secureContext = null; + if ($secureContext === null) { + $secureContext = self::secureConnection(); + + // The connection is considered as secure if it is encrypted with + // TLS, or if the target host is a local address. + if (!$secureContext) { + $host = $_SERVER['HTTP_HOST']; + + // @see https://datatracker.ietf.org/doc/html/draft-ietf-dnsop-let-localhost-be-localhost-02 + if ($host === '127.0.0.1' || $host === 'localhost' || \str_ends_with($host, '.localhost')) { + $secureContext = true; + } + } + } + + return $secureContext; + } + /** * Returns HTTP protocol, either 'http://' or 'https://'. */ diff --git a/wcfsetup/install/lang/de.xml b/wcfsetup/install/lang/de.xml index 14e58e9993d..590357b715b 100644 --- a/wcfsetup/install/lang/de.xml +++ b/wcfsetup/install/lang/de.xml @@ -984,6 +984,7 @@ Sie erreichen das Fehlerprotokoll unter: {link controller='ExceptionLogView' isE Liste der fehlenden Texte für weitere Informationen.]]> Systemüberprüfung durchzuführen.]]> + @@ -2815,6 +2816,9 @@ Abschnitte dürfen nicht leer sein und nur folgende Zeichen enthalten: [a-z opcache_reset() und opcache_invalidate() zur Verfügung stehen, damit der Cache nach einer Aktualisierung des Programmcodes zuverlässig neu aufgebaut werden kann.]]> + + + diff --git a/wcfsetup/install/lang/en.xml b/wcfsetup/install/lang/en.xml index ca8d4a27617..e1cdd80d35e 100644 --- a/wcfsetup/install/lang/en.xml +++ b/wcfsetup/install/lang/en.xml @@ -962,6 +962,7 @@ You can access the error log at: {link controller='ExceptionLogView' isEmail=tru list of missing phrases for more information.]]> System Check.]]> + @@ -2744,6 +2745,9 @@ If you have already bought the licenses for the listed apps, th opcache_reset() and opcache_invalidate() need to be available to be able to reliably reset the cache when the application code is updated.]]> + + + diff --git a/wcfsetup/setup/template/stepShowSystemRequirements.tpl b/wcfsetup/setup/template/stepShowSystemRequirements.tpl index b148fc9bc4d..951922c3741 100644 --- a/wcfsetup/setup/template/stepShowSystemRequirements.tpl +++ b/wcfsetup/setup/template/stepShowSystemRequirements.tpl @@ -171,17 +171,13 @@
- - -
-

{lang}wcf.global.systemRequirements.recommended{/lang}

- +

{lang}wcf.global.systemRequirements.tls{/lang}

-
{lang}wcf.global.systemRequirements.element.recommended{/lang}
+
{lang}wcf.global.systemRequirements.element.required{/lang}
{lang}wcf.global.systemRequirements.active{/lang}
@@ -197,6 +193,10 @@
+
+ +
+

{lang}wcf.global.systemRequirements.recommended{/lang}

{lang}wcf.global.systemRequirements.uploadMaxFilesize{/lang}

@@ -263,7 +263,7 @@
- + diff --git a/wcfsetup/test.php b/wcfsetup/test.php index a9277de7d8f..f6c41db8949 100644 --- a/wcfsetup/test.php +++ b/wcfsetup/test.php @@ -11,7 +11,7 @@ $language = $_GET['language']; } -const WSC_SRT_VERSION = '6.0.0'; +const WSC_SRT_VERSION = '6.1.0'; $requiredExtensions = [ 'ctype', 'dom', @@ -82,6 +82,10 @@ 'de' => 'Bitte stellen Sie sicher, dass MySQL 8.0.30+ oder MariaDB 10.5.15+ mit InnoDB-Unterstützung vorhanden ist.', 'en' => 'Please make sure that MySQL 8.0.30+ or MariaDB 10.5.15+, with InnoDB support is available.', ], + 'tls_failure' => [ + 'de' => 'Die Seite wird nicht über HTTPS aufgerufen. Wichtige Funktionen stehen dadurch nicht zur Verfügung, die für die korrekte Funktionsweise der Software erforderlich sind.', + 'en' => 'The page is not accessed via HTTPS. Important features that are required for the proper operation of the software are therefore not available.', + ], 'result' => [ 'de' => 'Ergebnis', 'en' => 'Summary', @@ -185,7 +189,7 @@ function checkResult() { global $requiredExtensions; - if (!checkPHPVersion() || !checkX64() || !checkMemoryLimit() || !checkOpcache()) { + if (!checkPHPVersion() || !checkX64() || !checkMemoryLimit() || !checkOpcache() || !checkTls()) { return false; } @@ -224,9 +228,30 @@ function checkOpcache() return true; } +function checkTls(): bool +{ + // @see \wcf\system\request\RouteHandler::secureConnection() + if ( + (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off') + || $_SERVER['SERVER_PORT'] == 443 + || (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') + ) { + return true; + } + + // @see \wcf\system\request\RouteHandler::secureContext() + $host = $_SERVER['HTTP_HOST']; + if ($host === '127.0.0.1' || $host === 'localhost' || \str_ends_with($host, '.localhost')) { + return true; + } + + return false; +} -?> +?> + + @@ -238,7 +263,7 @@ function checkOpcache() background-color: #2D2D2D; box-sizing: border-box; color: #c0c0c0; - font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol"; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 14px; line-height: 1.5; } @@ -272,7 +297,7 @@ function checkOpcache() margin-bottom: 15px; } - header > img { + header>img { flex: 0 auto; } @@ -283,11 +308,11 @@ function checkOpcache() padding: 0; } - .language-switcher > li { + .language-switcher>li { display: inline; } - .language-switcher > li + li { + .language-switcher>li+li { border-left: 1px solid #444444; margin-left: 10px; padding-left: 10px; @@ -313,12 +338,13 @@ function checkOpcache() text-align: right; } - footer > a { + footer>a { color: inherit; text-decoration: none; } - h2, h3 { + h2, + h3 { margin: 0 0 10px 0; font-weight: 300; padding: 0; @@ -346,6 +372,7 @@ function checkOpcache() margin-bottom: 10px; padding: 0 20px; } + ul.system-requirements li::before { font-family: Georgia, "Times New Roman", serif; margin-right: 10px; @@ -354,18 +381,23 @@ function checkOpcache() li.success { color: #00c291; } + li.success::before { content: '✔'; } + li.failure { color: #f08f84; } + li.failure::before { content: '✘'; } + li.info { color: #63b0e3; } + li.info::before { content: '✔'; } @@ -376,6 +408,7 @@ function checkOpcache() color: #fff; padding: 10px 20px; } + p.success::before, p.failure::before { font-family: Georgia, "Times New Roman", serif; @@ -385,6 +418,7 @@ function checkOpcache() p.success { background-color: #008563; } + p.success::before { content: '✔'; } @@ -392,6 +426,7 @@ function checkOpcache() p.failure { background-color: #de2f1b; } + p.failure::before { content: '✘'; } @@ -415,86 +450,92 @@ function checkOpcache() } + -
-
- WoltLab Suite - -
- -
-

WoltLab Suite System Requirements Test

- -

- -
    - -
  • - - -
  • - -
  • - +
    +
    + WoltLab Suite + +
    + +
    +

    WoltLab Suite System Requirements Test

    + +

    + +
      + +
    • + + +
    • + +
    • + + + + +
    • + +
    • + + - - -
    • + +
    • + + +
    • + +
    • + -
    • +
    • - - -
    • - - -
    • + +
    • -
    • +
    • - -
    • - - -
    • + +
    • + + + +
    • + -
    • +
    • +
    - -
  • - +

    + +
      +
    • +
    + +

    + + +

    -
  • +

    + + + +

    -
- -

- -
    -
  • -
- -

- - -

- -

- - - -

- -
- -
+ + +
- + + \ No newline at end of file