From cf6d13d1add27bebcb0aa82cc430a93f63f5f76b Mon Sep 17 00:00:00 2001 From: Belle Aerni Date: Fri, 10 Nov 2023 02:50:54 -0800 Subject: [PATCH] Redirect + HSTS support --- composer.json | 1 + composer.lock | 117 +++++++++++++++++++++++++- src/AntCMS/AntCMS.php | 26 +++++- src/AntCMS/AntRouting.php | 23 ----- src/AntCMS/AntUsers.php | 4 +- src/Plugins/Admin/AdminPlugin.php | 22 ++--- src/Plugins/Profile/ProfilePlugin.php | 14 +-- src/index.php | 8 +- 8 files changed, 165 insertions(+), 50 deletions(-) diff --git a/composer.json b/composer.json index a3d83e0..d999993 100644 --- a/composer.json +++ b/composer.json @@ -14,6 +14,7 @@ "elgigi/commonmark-emoji": "^2.0", "embed/embed": "^4.4", "league/commonmark": "^2.3", + "middlewares/https": "^2.0", "nyholm/psr7": "^1.8", "nyholm/psr7-server": "^1.1", "psr/http-message": "2.0 as 1.1", diff --git a/composer.lock b/composer.lock index 3b5cbd5..2262239 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "b2c8ceb8ed14d19e2688f7ec7710a7b9", + "content-hash": "03371ff18f92e62d0da1155ec5876278", "packages": [ { "name": "antcms/antloader", @@ -661,6 +661,121 @@ ], "time": "2022-12-11T20:36:23+00:00" }, + { + "name": "middlewares/https", + "version": "v2.0.1", + "source": { + "type": "git", + "url": "https://github.com/middlewares/https.git", + "reference": "afe80daea218c6be057b76b83de730d7da7b42b2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/middlewares/https/zipball/afe80daea218c6be057b76b83de730d7da7b42b2", + "reference": "afe80daea218c6be057b76b83de730d7da7b42b2", + "shasum": "" + }, + "require": { + "middlewares/utils": "^3.0", + "php": "^7.2 || ^8.0", + "psr/http-server-middleware": "^1.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.0", + "laminas/laminas-diactoros": "^2.3", + "oscarotero/php-cs-fixer-config": "^1.0", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8|^9", + "squizlabs/php_codesniffer": "^3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Middlewares\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Middleware to redirect to https and adds the Strict-Transport-Security header", + "homepage": "https://github.com/middlewares/https", + "keywords": [ + "Strict-Transport-Security", + "http", + "https", + "middleware", + "psr-15", + "psr-7" + ], + "support": { + "issues": "https://github.com/middlewares/https/issues", + "source": "https://github.com/middlewares/https/tree/v2.0.1" + }, + "time": "2020-12-02T00:06:18+00:00" + }, + { + "name": "middlewares/utils", + "version": "v3.3.0", + "source": { + "type": "git", + "url": "https://github.com/middlewares/utils.git", + "reference": "670b135ce0dbd040eadb025a9388f9bd617cc010" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/middlewares/utils/zipball/670b135ce0dbd040eadb025a9388f9bd617cc010", + "reference": "670b135ce0dbd040eadb025a9388f9bd617cc010", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "psr/container": "^1.0 || ^2.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0", + "psr/http-server-middleware": "^1.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^v2.16", + "guzzlehttp/psr7": "^2.0", + "laminas/laminas-diactoros": "^2.4", + "nyholm/psr7": "^1.0", + "oscarotero/php-cs-fixer-config": "^1.0", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8|^9", + "slim/psr7": "^1.4", + "squizlabs/php_codesniffer": "^3.5", + "sunrise/http-message": "^1.0", + "sunrise/http-server-request": "^1.0", + "sunrise/stream": "^1.0.15", + "sunrise/uri": "^1.0.15" + }, + "type": "library", + "autoload": { + "psr-4": { + "Middlewares\\Utils\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Common utils for PSR-15 middleware packages", + "homepage": "https://github.com/middlewares/utils", + "keywords": [ + "PSR-11", + "http", + "middleware", + "psr-15", + "psr-17", + "psr-7" + ], + "support": { + "issues": "https://github.com/middlewares/utils/issues", + "source": "https://github.com/middlewares/utils/tree/v3.3.0" + }, + "time": "2021-07-04T17:56:23+00:00" + }, { "name": "ml/iri", "version": "1.1.4", diff --git a/src/AntCMS/AntCMS.php b/src/AntCMS/AntCMS.php index 9b45fad..41df68c 100644 --- a/src/AntCMS/AntCMS.php +++ b/src/AntCMS/AntCMS.php @@ -7,16 +7,20 @@ use AntCMS\AntPages; use AntCMS\AntConfig; use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; +use Slim\App; +use Psr\Http\Message\UriInterface; class AntCMS { protected $antTwig; protected ?Response $response = null; protected ?Request $request = null; + protected ?App $app = null; - public function __construct() + public function __construct(?App $app) { $this->antTwig = new AntTwig(); + $this->app = $app; } public function SetResponse(?Response $response): void @@ -39,6 +43,11 @@ class AntCMS return $this->request; } + public function getApp() + { + return $this->app; + } + /** * Renders the page based on the request URI */ @@ -257,7 +266,20 @@ class AntCMS } } - public static function redirect(string $url) + public function redirect(string|UriInterface $url, int $code = 307): Response + { + if (!is_string($url)) { + $url = $url->__toString(); + } + $response = $this->response->withStatus($code); + return $response->withHeader('Location', $url); + } + + /** + * Old redirect function. + * TODO: Remove this once the other functions have been updated to no longer rely on this + */ + public static function redirectWithoutRequest(string $url) { $url = '//' . AntTools::repairURL(AntConfig::currentConfig('baseURL') . $url); header("Location: $url"); diff --git a/src/AntCMS/AntRouting.php b/src/AntCMS/AntRouting.php index 2d83f74..4444562 100644 --- a/src/AntCMS/AntRouting.php +++ b/src/AntCMS/AntRouting.php @@ -42,21 +42,6 @@ class AntRouting $this->requestUri = implode('/', $this->uriExploded); } - /** - * Used to detect if the current request is over HTTPS. If the request is over HTTP, it'll redirect to HTTPS. - */ - public function redirectHttps(): void - { - $scheme = $_SERVER['HTTPS'] ?? $_SERVER['REQUEST_SCHEME'] ?? $_SERVER['HTTP_X_FORWARDED_PROTO'] ?? null; - $isHttps = !empty($scheme) && (strcasecmp('on', $scheme) == 0 || strcasecmp('https', $scheme) == 0); - - if (!$isHttps) { - $url = 'https://' . AntTools::repairURL($this->baseUrl . $this->requestUri); - header('Location: ' . $url); - exit; - } - } - /** * Used to check if the current request URI matches a specified route. * Supports using '*' as a wild-card. Ex: '/admin/*' will match '/admin/somthing' and '/admin' @@ -108,14 +93,6 @@ class AntRouting exit; } - /** - * @return bool Returns true if the current request URI is an index request. - */ - public function isIndex(): bool - { - return (in_array($this->requestUri, $this->indexes)); - } - private function setExplodedUri(string $uri): void { $exploaded = explode('/', $uri); diff --git a/src/AntCMS/AntUsers.php b/src/AntCMS/AntUsers.php index 00e7d8b..ec36af9 100644 --- a/src/AntCMS/AntUsers.php +++ b/src/AntCMS/AntUsers.php @@ -30,7 +30,7 @@ class AntUsers if (file_exists(antUsersList)) { return AntYaml::parseFile(antUsersList); } else { - AntCMS::redirect('/profile/firsttime'); + AntCMS::redirectWithoutRequest('/profile/firsttime'); } } @@ -101,7 +101,7 @@ class AntUsers public static function setupFirstUser($data) { if (file_exists(antUsersList)) { - AntCMS::redirect('/'); + AntCMS::redirectWithoutRequest('/'); } $data['username'] = trim($data['username']); diff --git a/src/Plugins/Admin/AdminPlugin.php b/src/Plugins/Admin/AdminPlugin.php index 2687520..e207f5a 100644 --- a/src/Plugins/Admin/AdminPlugin.php +++ b/src/Plugins/Admin/AdminPlugin.php @@ -90,7 +90,7 @@ class AdminPlugin extends AntPlugin case 'save': if (!$_POST['textarea']) { - AntCMS::redirect('/admin/config'); + AntCMS::redirectWithoutRequest('/admin/config'); } $yaml = AntYaml::parseYaml($_POST['textarea']); @@ -98,7 +98,7 @@ class AdminPlugin extends AntPlugin AntYaml::saveFile(antConfigFile, $yaml); } - AntCMS::redirect('/admin/config'); + AntCMS::redirectWithoutRequest('/admin/config'); break; default: @@ -141,7 +141,7 @@ class AdminPlugin extends AntPlugin switch ($route[0] ?? 'none') { case 'regenerate': AntPages::generatePages(); - AntCMS::redirect('/admin/pages'); + AntCMS::redirectWithoutRequest('/admin/pages'); exit; case 'edit': @@ -176,11 +176,11 @@ class AdminPlugin extends AntPlugin $pagePath = AntTools::repairFilePath(antContentPath . '/' . implode('/', $route)); if (!isset($_POST['textarea'])) { - AntCMS::redirect('/admin/pages'); + AntCMS::redirectWithoutRequest('/admin/pages'); } file_put_contents($pagePath, $_POST['textarea']); - AntCMS::redirect('/admin/pages'); + AntCMS::redirectWithoutRequest('/admin/pages'); exit; case 'create': @@ -204,7 +204,7 @@ class AdminPlugin extends AntPlugin AntYaml::saveFile(antPagesList, $pages); } - AntCMS::redirect('/admin/pages'); + AntCMS::redirectWithoutRequest('/admin/pages'); break; case 'togglevisibility': @@ -218,7 +218,7 @@ class AdminPlugin extends AntPlugin } AntYaml::saveFile(antPagesList, $pages); - AntCMS::redirect('/admin/pages'); + AntCMS::redirectWithoutRequest('/admin/pages'); break; default: @@ -263,7 +263,7 @@ class AdminPlugin extends AntPlugin $user = AntUsers::getUserPublicalKeys($route[1]); if (!$user) { - AntCMS::redirect('/admin/users'); + AntCMS::redirectWithoutRequest('/admin/users'); } $user['username'] = $route[1]; @@ -276,7 +276,7 @@ class AdminPlugin extends AntPlugin $user = AntUsers::getUserPublicalKeys($route[1]); if (!$user) { - AntCMS::redirect('/admin/users'); + AntCMS::redirectWithoutRequest('/admin/users'); } $user['username'] = $route[1]; @@ -298,11 +298,11 @@ class AdminPlugin extends AntPlugin } AntUsers::updateUser($_POST['originalusername'], $data); - AntCMS::redirect('/admin/users'); + AntCMS::redirectWithoutRequest('/admin/users'); break; case 'savenew': AntUsers::addUser($_POST); - AntCMS::redirect('/admin/users'); + AntCMS::redirectWithoutRequest('/admin/users'); break; default: diff --git a/src/Plugins/Profile/ProfilePlugin.php b/src/Plugins/Profile/ProfilePlugin.php index 792df49..147ae34 100644 --- a/src/Plugins/Profile/ProfilePlugin.php +++ b/src/Plugins/Profile/ProfilePlugin.php @@ -28,14 +28,14 @@ class ProfilePlugin extends AntPlugin switch ($currentStep) { case 'firsttime': if (file_exists(antUsersList)) { - AntCMS::redirect('/admin'); + AntCMS::redirectWithoutRequest('/admin'); } echo $this->antTwig->renderWithSubLayout('profile_firstTime', $params); break; case 'submitfirst': if (file_exists(antUsersList)) { - AntCMS::redirect('/admin'); + AntCMS::redirectWithoutRequest('/admin'); } if (isset($_POST['username']) && isset($_POST['password']) && isset($_POST['display-name'])) { @@ -45,9 +45,9 @@ class ProfilePlugin extends AntPlugin 'name' => $_POST['display-name'], ]; AntUsers::setupFirstUser($data); - AntCMS::redirect('/admin'); + AntCMS::redirectWithoutRequest('/admin'); } else { - AntCMS::redirect('/profile/firsttime'); + AntCMS::redirectWithoutRequest('/profile/firsttime'); } break; @@ -56,7 +56,7 @@ class ProfilePlugin extends AntPlugin $user = AntUsers::getUserPublicalKeys($this->antAuth->getUsername()); if (!$user) { - AntCMS::redirect('/profile'); + AntCMS::redirectWithoutRequest('/profile'); } $user['username'] = $this->antAuth->getUsername(); @@ -70,7 +70,7 @@ class ProfilePlugin extends AntPlugin $user = AntUsers::getUserPublicalKeys($this->antAuth->getUsername()); if (!$user) { - AntCMS::redirect('/profile'); + AntCMS::redirectWithoutRequest('/profile'); } $user['username'] = $this->antAuth->getUsername(); @@ -92,7 +92,7 @@ class ProfilePlugin extends AntPlugin } AntUsers::updateUser($this->antAuth->getUsername(), $data); - AntCMS::redirect('/profile'); + AntCMS::redirectWithoutRequest('/profile'); break; case 'logout': diff --git a/src/index.php b/src/index.php index 9fa1af5..a94ec8c 100644 --- a/src/index.php +++ b/src/index.php @@ -24,14 +24,14 @@ $requestUri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH); $baseUrl = AntConfig::currentConfig('baseURL'); $antRouting = new \AntCMS\AntRouting($baseUrl, $requestUri); -$antCMS = new AntCMS; - $app = AppFactory::create(); $app->addRoutingMiddleware(); $app->addErrorMiddleware(true, true, true); +$antCMS = new AntCMS($app); + if (AntConfig::currentConfig('forceHTTPS') && !\AntCMS\AntEnviroment::isCli()) { - $antRouting->redirectHttps(); + $app->addMiddleware(new Middlewares\Https()); } $app->get('/themes/{theme}/assets', function (Request $request, Response $response) use ($antCMS) { @@ -72,7 +72,7 @@ if ($antRouting->checkMatch('/plugin/*')) { $app->get('/', function (Request $request, Response $response) use ($antCMS) { if (!file_exists(antUsersList)) { - AntCMS::redirect('/profile/firsttime'); + AntCMS::redirectWithoutRequest('/profile/firsttime'); } $antCMS->setRequest($request);