Commit a1583955 authored by Benni Mack's avatar Benni Mack
Browse files

[TASK] Use middleware for SOAP endpoint & remove pi-based plugin

This change adds a PSR-15 middleware to intercept all /ter and /ter?wsdl
calls in favor of a pi-based plugin.

The plugin is removed (so the whole page in TYPO3 can be removed as well later-on).

The previous WSDL delivery endpoint extensions.typo3.org/typo3conf/ext/ter/tx_ter_wsdl.php
is now a redirect to /ter?wsdl, so everything is covered by the middleware.
parent 4310c1ee
Pipeline #9436 passed with stages
in 8 minutes and 34 seconds
......@@ -4104,9 +4104,10 @@
"dist": {
"type": "path",
"url": "extensions/ter",
"reference": "67290f0598857f79d6184fdf9be74209e0dab347"
"reference": "6691b4efbb79761f87a4234dd4b9a9a76d50bf15"
},
"require": {
"ext-soap": "*",
"typo3/cms-core": "^9.5 || ^10.4"
},
"type": "typo3-cms-extension",
......@@ -4118,10 +4119,7 @@
"autoload": {
"psr-4": {
"T3o\\Ter\\": "Classes/"
},
"classmap": [
"pi1"
]
}
},
"license": [
"GPL-2.0-or-later"
......@@ -5826,13 +5824,13 @@
"authors": [
{
"name": "The TYPO3 Community",
"role": "Contributor",
"homepage": "https://typo3.org/community/"
"homepage": "https://typo3.org/community/",
"role": "Contributor"
},
{
"name": "TYPO3 CMS Core Team",
"role": "Developer",
"homepage": "https://forge.typo3.org/projects/typo3cms-core"
"homepage": "https://forge.typo3.org/projects/typo3cms-core",
"role": "Developer"
}
],
"description": "Minimal required set of TYPO3 extensions",
......
<?php
declare(strict_types = 1);
/*
* This file is part of the TYPO3 CMS project.
* This file is part of TYPO3 CMS-extension "ter_fe2", created by Benni Mack.
*
* 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!
*/
/**
* SOAP server for the TYPO3 Extension Repository
*
* $Id$
*
* @author Robert Lemke <robert@typo3.org>
*/
/**
* [CLASS/FUNCTION INDEX of SCRIPT]
*
*
*
* 54: class tx_ter_pi1 extends tslib_pibase
* 61: function main ($content, $conf)
*
* TOTAL FUNCTIONS: 1
* (This index is automatically created/updated by the extension "extdeveval")
*/
use TYPO3\CMS\Frontend\Plugin\AbstractPlugin;
namespace T3o\Ter\Middleware;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\UriInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use T3o\Ter\Api\Configuration;
use T3o\Ter\Exception\FailedDependencyException;
use T3o\Ter\Exception\InternalServerErrorException;
use T3o\Ter\Exception\NotFoundException;
use T3o\Ter\Exception\UnauthorizedException;
use T3o\Ter\Soap\TerSoapV1Handler;
use T3o\Ter\Soap\Wsdl;
use TYPO3\CMS\Core\Http\Response;
use TYPO3\CMS\Core\Site\Entity\Site;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* TYPO3 Extension Repository, frontend plugin for SOAP service
*
* @author Robert Lemke <robert@typo3.org>
* This middleware actually handles everything that is needed for TER's SOAP API.
* and reacts on /ter* requests. Until 2020 this was done in a pi-based plugin.
*/
class tx_ter_pi1 extends AbstractPlugin
class SoapEndpoint implements MiddlewareInterface
{
public function main($content, $conf)
protected string $endpoint = '/ter';
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
// Always deliver the proper WSDL file if ?wsdl is given
if (isset($_GET['wsdl'])) {
require_once(\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extPath('ter') . 'tx_ter_wsdl.php');
exit;
$currentSite = $request->getAttribute('site');
if ($this->isSoapTerUrl($currentSite, $request->getUri())) {
// Always deliver the proper WSDL file if ?wsdl is given
if (isset($request->getQueryParams()['wsdl'])) {
$wsdlContents = GeneralUtility::makeInstance(Wsdl::class)->getWsdlContents($request);
$response = new Response('php://temp', 200, [
'Content-type' => 'text/xml',
'Content-length' => strlen($wsdlContents)
]);
$response->getBody()->write($wsdlContents);
return $response;
}
return $this->handleSoapRequest($request);
}
return $handler->handle($request);
}
/**
* Checks if the site is "TER" and the path is "/ter" (exact match).
*
* @param Site|null $site
* @param UriInterface $currentUri
* @return bool
*/
protected function isSoapTerUrl(?Site $site, UriInterface $currentUri): bool
{
if (!$site instanceof Site) {
return false;
}
if ($site->getIdentifier() !== 'extensions') {
return false;
}
if (strtolower($currentUri->getPath()) !== $this->endpoint) {
return false;
}
return true;
}
/**
* Returns a response object directly (handled by ext-soap) on failure or exits directly,
* as SoapServer works this way unfortunately.
*
* @param ServerRequestInterface $request
* @return ResponseInterface
*/
protected function handleSoapRequest(ServerRequestInterface $request): ResponseInterface
{
try {
$server = new \SoapServer(null, ['uri' => \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\T3o\Ter\Api\Configuration::class)->getWsdlNamespace(), 'trace' => true, 'exceptions' => true]);
$server->setClass(\T3o\Ter\Soap\TerSoapV1Handler::class);
$server = new \SoapServer(
null,
[
'uri' => GeneralUtility::makeInstance(Configuration::class)->getWsdlNamespace(),
'trace' => true,
'exceptions' => true
]
);
$server->setClass(TerSoapV1Handler::class);
$server->handle(file_get_contents('php://input'));
// Due to different behaviour on stage and prod server we need this exit to prevent wrong XML response
// @todo: make a PSR-7 response out of it
exit();
} catch (\T3o\Ter\Exception\Exception $e) {
/**
......@@ -65,17 +111,16 @@ class tx_ter_pi1 extends AbstractPlugin
* That's why there is an own set of exceptions defined that basically stand for a status code.
*/
$statusCode = 404;
if ($e instanceof \T3o\Ter\Exception\UnauthorizedException) {
if ($e instanceof UnauthorizedException) {
$statusCode = 401;
} elseif ($e instanceof \T3o\Ter\Exception\NotFoundException) {
} elseif ($e instanceof NotFoundException) {
$statusCode = 404;
} elseif ($e instanceof \T3o\Ter\Exception\FailedDependencyException) {
} elseif ($e instanceof FailedDependencyException) {
$statusCode = 424;
} elseif ($e instanceof \T3o\Ter\Exception\InternalServerErrorException) {
} elseif ($e instanceof InternalServerErrorException) {
$statusCode = 500;
error_log(sprintf('TER Server internal error occurred. Error message is: "%s"', $e->getMessage()));
}
header(' ', true, $statusCode);
/**
* Using $server->fault will cause a http 500 status code to be sent which in turn
* will trigger the web server's error page to be shown, instead of the SOAP XML
......@@ -98,10 +143,11 @@ class tx_ter_pi1 extends AbstractPlugin
</SOAP-ENV:Fault>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>';
echo sprintf($faultStringXmlTemplate, $e->getCode(), $e->getMessage());
exit;
return new Response(
sprintf($faultStringXmlTemplate, $e->getCode(), $e->getMessage()),
$statusCode,
['Content-Type' => 'text/xml; charset=utf-8']
);
}
return '';
}
}
<?php
declare(strict_types = 1);
/*
* This file is part of TYPO3 CMS-extension "ter", created by Benni Mack.
*
* 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.
*/
namespace T3o\Ter\Soap;
use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
/**
* WSDL wrapper for the TYPO3 Extension Repository. Returns the contents of the WSDL definition.
*/
class Wsdl
{
public function getWsdlContents(ServerRequestInterface $request): string
{
$serviceLocation = 'https://' . $request->getServerParams()['HTTP_HOST'] . '/ter';
if (Environment::getContext()->isProduction()) {
$wsdlFileName = 'tx_ter.wsdl';
} else {
$wsdlFileName = 'tx_ter.stage.wsdl';
}
$WSDLSource = file_get_contents(ExtensionManagementUtility::extPath('ter') . $wsdlFileName);
$WSDLSource = trim(str_replace('---SERVICE_LOCATION---', $serviceLocation, $WSDLSource));
return $WSDLSource;
}
}
<?php
return [
'frontend' => [
't3o/ter/soap' => [
'target' => \T3o\Ter\Middleware\SoapEndpoint::class,
'after' => [
'typo3/cms-frontend/prepare-tsfe-rendering',
'typo3/cms-frontend/shortcut-and-mountpoint-redirect'
],
]
]
];
<?php
// Remove the old "CODE", "Layout" and the "recursive" fields
$GLOBALS['TCA']['tt_content']['types']['list']['subtypes_excludelist']['ter_pi1'] = 'layout,select_key,pages,recursive';
// Add plugin and datasets
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addPlugin(['TER SOAP Server', 'ter_pi1'], 'list_type', 'ter');
// Add static configuration files
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addStaticFile('ter', 'resources/static/', 'TER Server');
......@@ -20,15 +20,13 @@
"license": ["GPL-2.0-or-later"],
"version": "2.1.0",
"require": {
"ext-soap": "*",
"typo3/cms-core": "^9.5 || ^10.4"
},
"autoload": {
"psr-4": {
"T3o\\Ter\\": "Classes/"
},
"classmap": [
"pi1"
]
}
},
"extra": {
"typo3/cms": {
......
......@@ -3,8 +3,6 @@ if (!defined('TYPO3_MODE')) {
die('Access denied.');
}
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addPItoST43('ter', '', '_pi1', 'list_type', false);
// Register core version update task
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][\T3o\Ter\Task\UpdateCurrentVersionListTask::class] = [
'extension' => 'ter',
......
......@@ -12,30 +12,10 @@
* The TYPO3 project - inspiring people to share!
*/
/**
* WSDL wrapper for the TYPO3 Extension Repository
*
* Note: We expect that you call this script from a directory "wsdl" in the
* site's main public directory.
*
* $Id$
*
* @author Robert Lemke <robert@typo3.org>
* Previously directly accessible, but now it's a redirect to /ter?wsdl.
*/
error_reporting(E_ALL ^ E_NOTICE);
$serviceLocation = 'https://' . $_SERVER['HTTP_HOST'] . '/ter';
if (getenv('TYPO3_CONTEXT') === 'Production') {
$wsdlFileName = 'tx_ter.wsdl';
} else {
$wsdlFileName = 'tx_ter.stage.wsdl';
}
$WSDLSource = file_get_contents(__DIR__ . '/' . $wsdlFileName);
$WSDLSource = trim(str_replace('---SERVICE_LOCATION---', $serviceLocation, $WSDLSource));
if (!headers_sent()) {
header('Content-type: text/xml');
header('Content-Length: ' . strlen($WSDLSource));
}
echo $WSDLSource;
header('HTTP/1.1 301 Moved Permanently');
header('Location: /ter?wsdl');
exit;
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment