Skip to content

Commit

Permalink
[FEATURE] Make backend URL configurable
Browse files Browse the repository at this point in the history
The TYPO3 Backend URL is made configurable in order to enable optional
protection against application admin interface infrastructure
enumeration (WSTG-CONF-05). Both, frontend and backend requests are
now handled by the PHP script `/index.php` to enable virtual admin
interface URLs.

The default TYPO3 Backend entrypoint path `/typo3` can be changed by
specifying a custom URL path or domain name in
`$GLOBALS['TYPO3_CONF_VARS']['BE']['entryPoint']`.

This change requires web server adaption. A silent migration and
according documentation for custom web server configurations is added.
A deprecation layer (for non-adapted systems) is in place that rewrites
the server environment variables passed to `/typo3/index.php` as if
`/index.php` was used directly. This layer will be removed in TYPO3 v14.

This change does not take assets into account, only routing is adapted.
That means composer mode will use assets provided via _assets as before
and TYPO3 classic mode will serve backend assets from /typo3/* even if
another backend URL is used and configured.

In composer mode there is an additional opt-out for the installation of
the legacy entrypoint for that can be defined in composer.json:

  "extra": {
    "typo3/cms": {
      "install-deprecated-typo3-index-php": false
    }
  }

The application flow is slightly adapted by moving common middlewares
into a separate core middleware chain. This chain is dispatched by a
distinct core HTTP application (which is invoked by index.php).
These middlewares are suitable for proxy determination or generic
access control – basically everything not needed for subrequests.
The core HTTP request handler then decides whether the request is to be
routed to the frontend or backend application. Frontend and backend
appplications are still designed to work independently with a plain
PSR-7 Server Request in order for sub requests from backend to frontend
(or vice versa) to work.

The following diagram outlines the new application workflow including
flow of possible sub requests (not yet used from backend to frontend,
but it shows how they are intended to be invoked):

                         +-------------------+
                         |                   |
                         |  Core HTTP        |
                         |  Application      |
                         |                   |
                         +---------+---------+
                                   |
                                   |
                                   v
                         +---------+---------+
                         |                   |
                         |  Core HTTP        |
                         |  Middlewares      |
                         |                   |
                         +---------+---------+
                                   |
                                   |
                                   v
                         +---------+---------+
                         |                   |
                         |  Core HTTP        |
           +-------------+  Request Handler  +--------------+
           |             |                   |              |
           |             +-------------------+              |
           |                                                |
           v                                                v
  +--------+----------+                           +---------+---------+
  |                   | (Sub Request)             |                   |
  |  Frontend HTTP    +<-------------+            |  Backend HTTP     |
  |  Application      +<-----------+ |            |  Application      |
  |                   |            | |            |                   |
  +---------+---------+            | |            +---------+---------+
            |                      | |                      |
            |                      | |                      |
            v                      | |                      v
  +---------+---------+            | |            +---------+---------+
  |                   |            | |            |                   |
  |  Frontend HTTP    |            | |            |  Backend HTTP     |
  |  Middlewares      |            | |            |  Middlewares      |
  |                   |            | |            |                   |
  +---------+---------+            | |            +---------+---------+
            |                      | |                      |
            |                      | |                      |
            v                      | |                      v
  +---------+---------+            | |            +---------+---------+
  |                   |            | |            |                   |
  |  Frontend HTTP    |            | |            |  Backend HTTP     |
  |  Request Handler  |            | |            |  Request Handler  |
  |                   |            | |            |                   |
  +---------+---------+            | |            +---------+---------+
            |                      | |                      |
            |                      | |                      |
            v                      | |                      v
  +---------+---------+            | |            +---------+---------+
  |                   |            | |            |                   |
  |  TypoScript       |            | |            |  Backend Route    |
  |  Frontend         +------------+ |            |  Dispatcher       |
  |  Controller       |              |            |                   |
  |                   |              |            +---------+---------+
  +-------------------+              |                      |
                                     |                      |
                                     |                      v
                                     |            +---------+---------+
                                     |            |                   |
                                     |            |  Backend          |
                                     +------------+  Controller       |
                                                  |                   |
                                                  +-------------------+

Commands executed:
  # For changed in TYPO3/testing-framework#533
  composer req --dev "typo3/testing-framework":"dev-main"

Resolves: #87889
Releases: main
Change-Id: I3c96d4d7c58f08ed302ee35eb75d28afbf77686a
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/74366
Reviewed-by: Oliver Hader <oliver.hader@typo3.org>
Reviewed-by: Stefan Bürk <stefan@buerk.tech>
Tested-by: core-ci <typo3@b13.com>
Tested-by: Stefan Bürk <stefan@buerk.tech>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Oliver Hader <oliver.hader@typo3.org>
Reviewed-by: Benni Mack <benni@typo3.org>
Tested-by: Benni Mack <benni@typo3.org>
  • Loading branch information
bnf authored and bmack committed Jan 24, 2024
1 parent 96d8a59 commit a94b7b5
Show file tree
Hide file tree
Showing 61 changed files with 767 additions and 387 deletions.
3 changes: 2 additions & 1 deletion Build/Sources/TypeScript/backend/module/router.ts
Expand Up @@ -50,6 +50,7 @@ export class ModuleRouter extends LitElement {
@property({ type: String, attribute: 'state-tracker' }) stateTrackerUrl: string;
@property({ type: String, attribute: 'sitename' }) sitename: string;
@property({ type: Boolean, attribute: 'sitename-first' }) sitenameFirst: boolean;
@property({ type: String, attribute: 'entry-point' }) entryPoint: string;
@query('slot', true) slotElement: HTMLSlotElement;

constructor() {
Expand Down Expand Up @@ -212,7 +213,7 @@ export class ModuleRouter extends LitElement {
const controller = params.get('install[controller]');
params.delete('install[controller]');
params.delete('install[context]');
url.pathname = url.pathname.replace('/typo3/install.php', '/typo3/module/tools/' + controller);
url.pathname = url.pathname.replace('/typo3/install.php', this.entryPoint + 'module/tools/' + controller);
} else {
// non token-urls cannot be mapped by
// the main backend controller right now
Expand Down
8 changes: 4 additions & 4 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion typo3/sysext/backend/Classes/Composer/InstallerScripts.php
Expand Up @@ -31,9 +31,14 @@ class InstallerScripts implements InstallerScriptsRegistration
{
public static function register(Event $event, ScriptDispatcher $scriptDispatcher)
{
$extra = $event->getComposer()->getPackage()->getExtra();
$installDeprecatedTypo3IndexPhp = $extra['typo3/cms']['install-deprecated-typo3-index-php'] ?? true;
if (!$installDeprecatedTypo3IndexPhp) {
return;
}
$scriptDispatcher->addInstallerScript(
new EntryPoint(
dirname(__DIR__, 2) . '/Resources/Private/Php/backend.php',
dirname(__DIR__, 2) . '/Resources/Private/Php/legacy-backend.php',
'typo3/index.php'
)
);
Expand Down
3 changes: 3 additions & 0 deletions typo3/sysext/backend/Classes/Controller/BackendController.php
Expand Up @@ -44,6 +44,7 @@
use TYPO3\CMS\Core\Messaging\FlashMessageService;
use TYPO3\CMS\Core\Page\JavaScriptModuleInstruction;
use TYPO3\CMS\Core\Page\PageRenderer;
use TYPO3\CMS\Core\Routing\BackendEntryPointResolver;
use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity;
use TYPO3\CMS\Core\Type\File\ImageInfo;
use TYPO3\CMS\Core\Utility\GeneralUtility;
Expand Down Expand Up @@ -75,6 +76,7 @@ public function __construct(
protected readonly BackendViewFactory $viewFactory,
protected readonly EventDispatcherInterface $eventDispatcher,
protected readonly FlashMessageService $flashMessageService,
protected readonly BackendEntryPointResolver $backendEntryPointResolver,
) {
$this->modules = $this->moduleProvider->getModulesForModuleMenu($this->getBackendUser());
}
Expand Down Expand Up @@ -157,6 +159,7 @@ public function mainAction(ServerRequestInterface $request): ResponseInterface
'modulesCollapsed' => $this->getCollapseStateOfMenu(),
'modulesInformation' => GeneralUtility::jsonEncodeForHtmlAttribute($this->getModulesInformation(), false),
'startupModule' => $this->getStartupModule($request),
'entryPoint' => $this->backendEntryPointResolver->getPathFromRequest($request),
'stateTracker' => (string)$this->uriBuilder->buildUriFromRoute('state-tracker'),
'sitename' => $title,
'sitenameFirstInBackendTitle' => ($backendUser->uc['backendTitleFormat'] ?? '') === 'sitenameFirst',
Expand Down
23 changes: 4 additions & 19 deletions typo3/sysext/backend/Classes/Http/Application.php
Expand Up @@ -20,15 +20,11 @@
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use TYPO3\CMS\Core\Configuration\ConfigurationManager;
use TYPO3\CMS\Core\Context\Context;
use TYPO3\CMS\Core\Context\DateTimeAspect;
use TYPO3\CMS\Core\Context\VisibilityAspect;
use TYPO3\CMS\Core\Core\Bootstrap;
use TYPO3\CMS\Core\Core\SystemEnvironmentBuilder;
use TYPO3\CMS\Core\Http\AbstractApplication;
use TYPO3\CMS\Core\Http\RedirectResponse;
use TYPO3\CMS\Core\Routing\BackendEntryPointResolver;

/**
* Entry point for the TYPO3 Backend (HTTP requests)
Expand All @@ -37,35 +33,24 @@ class Application extends AbstractApplication
{
public function __construct(
RequestHandlerInterface $requestHandler,
protected readonly ConfigurationManager $configurationManager,
protected readonly Context $context,
protected readonly BackendEntryPointResolver $backendEntryPointResolver
) {
$this->requestHandler = $requestHandler;
}

public function handle(ServerRequestInterface $request): ResponseInterface
{
if (!Bootstrap::checkIfEssentialConfigurationExists($this->configurationManager)) {
return $this->installToolRedirect($request);
}

// Add applicationType attribute to request: This is backend and maybe backend ajax.
$request = $request->withAttribute('applicationType', SystemEnvironmentBuilder::REQUESTTYPE_BE);

if (isset($_SERVER['TYPO3_DEPRECATED_ENTRYPOINT'])) {
trigger_error('/typo3/index.php entrypoint is deprecated and will be removed in TYPO3 v14. Adapt your webserver config to route all requests via /index.php', E_USER_DEPRECATED);
}

// Set up the initial context
$this->initializeContext();
return parent::handle($request);
}

/**
* Create a PSR-7 Response that redirects to the install tool
*/
protected function installToolRedirect(ServerRequestInterface $request): ResponseInterface
{
return new RedirectResponse($this->backendEntryPointResolver->getPathFromRequest($request) . 'install.php', 302);
}

/**
* Initializes the Context used for accessing data and finding out the current state of the application
*/
Expand Down
12 changes: 7 additions & 5 deletions typo3/sysext/backend/Classes/Routing/Router.php
Expand Up @@ -20,6 +20,7 @@
use Symfony\Component\Routing\Route as SymfonyRoute;
use TYPO3\CMS\Backend\Routing\Exception\MethodNotAllowedException;
use TYPO3\CMS\Backend\Routing\Exception\ResourceNotFoundException;
use TYPO3\CMS\Core\Routing\BackendEntryPointResolver;
use TYPO3\CMS\Core\Routing\RequestContextFactory;
use TYPO3\CMS\Core\Routing\RouteCollection;
use TYPO3\CMS\Core\SingletonInterface;
Expand All @@ -43,7 +44,8 @@ class Router implements SingletonInterface
protected RouteCollection $routeCollection;

public function __construct(
protected readonly RequestContextFactory $requestContextFactory
protected readonly RequestContextFactory $requestContextFactory,
protected readonly BackendEntryPointResolver $backendEntryPointResolver,
) {
$this->routeCollection = new RouteCollection();
}
Expand Down Expand Up @@ -127,11 +129,11 @@ public function match($pathInfo): Route
*/
public function matchResult(ServerRequestInterface $request): RouteResult
{
$path = $request->getUri()->getPath();
if (($normalizedParams = $request->getAttribute('normalizedParams')) !== null) {
// Remove the directory name of the script from the path. This will usually be `/typo3` in this context.
$path = substr($path, strlen(dirname($normalizedParams->getScriptName())));
$path = $this->backendEntryPointResolver->getBackendRoutePath($request);
if ($path === null) {
throw new ResourceNotFoundException('The requested resource "' . $request->getUri()->getPath() . '" does not contain a known backend route prefix.', 1704787661);
}

if ($path === '' || $path === '/' || $path === '/index.php') {
// Allow the login page to be displayed if routing is not used and on index.php
// (consolidate RouteDispatcher::evaluateReferrer() when changing 'login' to something different)
Expand Down
8 changes: 4 additions & 4 deletions typo3/sysext/backend/Classes/ServiceProvider.php
Expand Up @@ -32,7 +32,6 @@
use TYPO3\CMS\Backend\Security\SudoMode\Access\AccessStorage;
use TYPO3\CMS\Core\Cache\Event\CacheWarmupEvent;
use TYPO3\CMS\Core\Cache\Exception\InvalidDataException;
use TYPO3\CMS\Core\Configuration\ConfigurationManager;
use TYPO3\CMS\Core\Context\Context;
use TYPO3\CMS\Core\EventDispatcher\ListenerProvider;
use TYPO3\CMS\Core\Exception as CoreException;
Expand Down Expand Up @@ -95,9 +94,7 @@ public static function getApplication(ContainerInterface $container): Applicatio
);
return new Application(
$requestHandler,
$container->get(ConfigurationManager::class),
$container->get(Context::class),
$container->get(BackendEntryPointResolver::class)
);
}

Expand Down Expand Up @@ -174,7 +171,10 @@ public static function getBackendMiddlewares(ContainerInterface $container): \Ar

public static function configureBackendRouter(ContainerInterface $container, Router $router = null): Router
{
$router = $router ?? self::new($container, Router::class, [$container->get(RequestContextFactory::class)]);
$router = $router ?? self::new($container, Router::class, [
$container->get(RequestContextFactory::class),
$container->get(BackendEntryPointResolver::class),
]);
$cache = $container->get('cache.core');

$cacheIdentifier = $container->get(PackageDependentCacheIdentifier::class)->withPrefix('BackendRoutes')->toString();
Expand Down
7 changes: 0 additions & 7 deletions typo3/sysext/backend/Configuration/RequestMiddlewares.php
Expand Up @@ -13,16 +13,9 @@
*/
return [
'backend' => [
/** internal: do not use or reference this middleware in your own code */
'typo3/cms-core/verify-host-header' => [
'target' => \TYPO3\CMS\Core\Middleware\VerifyHostHeader::class,
],
/** internal: do not use or reference this middleware in your own code */
'typo3/cms-core/normalized-params-attribute' => [
'target' => \TYPO3\CMS\Core\Middleware\NormalizedParamsAttribute::class,
'after' => [
'typo3/cms-core/verify-host-header',
],
],
'typo3/cms-backend/locked-backend' => [
'target' => \TYPO3\CMS\Backend\Middleware\LockedBackendGuard::class,
Expand Down
21 changes: 0 additions & 21 deletions typo3/sysext/backend/Resources/Private/Php/backend.php

This file was deleted.

48 changes: 48 additions & 0 deletions typo3/sysext/backend/Resources/Private/Php/legacy-backend.php
@@ -0,0 +1,48 @@
<?php

/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/

// Legacy wrapper for typo3/index.php
// @deprecated will be removed in TYPO3 v14, /index.php entrypoint should be used directly

// defer deprecation log message
$_SERVER['TYPO3_DEPRECATED_ENTRYPOINT'] = 1;

array_map(
static function (string $var): void {
$setenv = static function (string $name, string $value = null): void {
// If PHP is running as an Apache module and an existing
// Apache environment variable exists, overwrite it
if (function_exists('apache_getenv') && function_exists('apache_setenv') && apache_getenv($name)) {
apache_setenv($name, $value);
}

if (function_exists('putenv')) {
putenv("$name=$value");
}

$_ENV[$name] = $value;
};

if (isset($_SERVER[$var])) {
$_SERVER[$var] = str_replace('/typo3/index.php', '/index.php', $_SERVER[$var]);
}
if (isset($_ENV[$var])) {
$setenv($var, str_replace('/typo3/index.php', '/index.php', $_ENV[$var]));
}
},
['SCRIPT_NAME', 'SCRIPT_FILENAME', 'SCRIPT_URI', 'SCRIPT_URL', 'PHP_SELF'],
);

require '../index.php';
Expand Up @@ -42,7 +42,7 @@
persistence-identifier="navigation.width"
></typo3-backend-navigation-switcher>
<div class="scaffold-content-module t3js-scaffold-content-module">
<typo3-backend-module-router module="{startupModule.0}" endpoint="{startupModule.1}" state-tracker="{stateTracker}" sitename="{sitename}"{f:if(condition: sitenameFirstInBackendTitle, then: ' sitename-first')}></typo3-backend-module-router>
<typo3-backend-module-router module="{startupModule.0}" endpoint="{startupModule.1}" state-tracker="{stateTracker}" sitename="{sitename}"{f:if(condition: sitenameFirstInBackendTitle, then: ' sitename-first')} entry-point="{entryPoint}"></typo3-backend-module-router>
</div>
<div class="scaffold-content-overlay t3js-scaffold-content-overlay"></div>
</div>
Expand Down

0 comments on commit a94b7b5

Please sign in to comment.