Commit 90c0c71e authored by Oliver Bartsch's avatar Oliver Bartsch
Browse files

Move REST API into dedicated extension and add basic authentication handling

parent c3bd6601
......@@ -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": "1419bb371d9b93c04904ac0aaf436fc8",
"content-hash": "4f9a302da8f614ffd9e28c4493761a67",
"packages": [
{
"name": "adoy/fastcgi-client",
......@@ -6319,6 +6319,52 @@
"relative": true
}
},
{
"name": "t3o/ter-rest",
"version": "0.1.0",
"dist": {
"type": "path",
"url": "extensions/ter_rest",
"reference": "a6ad9151832789fb672cde79ddcd57443eae9ad4"
},
"require": {
"php": "^7.4",
"typo3/cms-core": "^10.4"
},
"type": "typo3-cms-extension",
"extra": {
"typo3/cms": {
"extension-key": "ter_rest"
}
},
"autoload": {
"psr-4": {
"T3o\\TerRest\\": "Classes/"
}
},
"license": [
"GPL-2.0-or-later"
],
"authors": [
{
"name": "t3o team",
"email": "maintenance@typo3.org"
}
],
"description": "REST API for the TYPO3 Extension Repository (TER). (extensions.typo3.org)",
"keywords": [
"Extensions",
"TER",
"TYPO3 CMS"
],
"support": {
"email": "maintenance@typo3.org",
"issues": "https://git-t3o.typo3.org/t3o/ter/issues"
},
"transport-options": {
"relative": true
}
},
{
"name": "t3o/ter-soap",
"version": "2.2.0",
......
<?php
return [
'frontend' => [
't3o/ter/rest' => [
'target' => \T3o\Ter\Middleware\RestRouteDispatcher::class,
'after' => [
'typo3/cms-frontend/site'
],
'before' => [
'typo3/cms-frontend/base-redirect-resolver'
]
],
't3o/ter/soap' => [
'target' => \T3o\Ter\Middleware\SoapEndpoint::class,
'after' => [
......
services:
_defaults:
autowire: true
autoconfigure: true
public: false
T3o\Ter\:
resource: '../Classes/*'
<?php
declare(strict_types = 1);
namespace T3o\TerRest\Authentication;
/*
* This file is part of TYPO3 CMS-extension "ter_rest", created by Oliver Bartsch.
*
* 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.
*/
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ServerRequestInterface;
use T3o\TerRest\Authentication\Provider\AuthenticationProviderInterface;
use T3o\TerRest\Authentication\User\ApiUserInterface;
/**
* Dispatch authentication providers to fetch an API user. Therefore
* all providers which are allowed for the requested route are called
* successively till a user is found.
*
* @see ApiUserInterface
*/
final class AuthenticationDispatcher implements AuthenticationHandlerInterface
{
protected AuthenticationHandlerInterface $tip;
protected ContainerInterface $container;
public function __construct(
AuthenticationHandlerInterface $base,
ContainerInterface $container,
iterable $authenticationProviders = []
) {
$this->container = $container;
$this->seedAuthenticationStack($base);
foreach ($authenticationProviders as $authenticationProvider) {
$this->add($authenticationProvider);
}
}
public function handle(ServerRequestInterface $request): ApiUserInterface
{
return $this->tip->handle($request);
}
protected function seedAuthenticationStack(AuthenticationHandlerInterface $base): void
{
$this->tip = $base;
}
public function add(string $authenticationProvider): void
{
$next = $this->tip;
$this->tip = new class($authenticationProvider, $next, $this->container) implements AuthenticationHandlerInterface {
private string $authenticationProvider;
private AuthenticationHandlerInterface $next;
private ContainerInterface $container;
public function __construct(
string $authenticationProvider,
AuthenticationHandlerInterface $next,
ContainerInterface $container
) {
$this->authenticationProvider = $authenticationProvider;
$this->next = $next;
$this->container = $container;
}
public function handle(ServerRequestInterface $request): ApiUserInterface
{
$authenticationProvider = $this->container->has($this->authenticationProvider)
? $this->container->get($this->authenticationProvider)
: null;
if ($authenticationProvider === null) {
throw new \InvalidArgumentException('Authentication provider could not be fetched', 1601473937);
}
if (!$authenticationProvider instanceof AuthenticationProviderInterface) {
throw new \InvalidArgumentException(get_class($authenticationProvider) . ' must implement ' . AuthenticationProviderInterface::class, 1601473614);
}
return $authenticationProvider->authenticate($request, $this->next);
}
};
}
}
<?php
declare(strict_types = 1);
namespace T3o\TerRest\Authentication;
/*
* This file is part of TYPO3 CMS-extension "ter_rest", created by Oliver Bartsch.
*
* 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.
*/
use Psr\Http\Message\ServerRequestInterface;
use T3o\TerRest\Authentication\User\ApiUserInterface;
use T3o\TerRest\Authentication\User\UnauthorizedUser;
/**
* Main handler for fetching a API user. If non is found, an unauthorized user
* is returned which can be used for routes which do not require authentication.
*
* @see UnauthorizedUser
*/
final class AuthenticationHandler implements AuthenticationHandlerInterface
{
public function handle(ServerRequestInterface $request): ApiUserInterface
{
return new UnauthorizedUser();
}
}
<?php
declare(strict_types = 1);
namespace T3o\TerRest\Authentication;
/*
* This file is part of TYPO3 CMS-extension "ter_rest", created by Oliver Bartsch.
*
* 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.
*/
use Psr\Http\Message\ServerRequestInterface;
use T3o\TerRest\Authentication\User\ApiUserInterface;
/**
* To be implemented by authentication handlers. Ensures a API user is returned in any case.
*
* @see AuthenticationHandler
*/
interface AuthenticationHandlerInterface
{
public function handle(ServerRequestInterface $request): ApiUserInterface;
}
<?php
declare(strict_types = 1);
namespace T3o\TerRest\Authentication\Provider;
/*
* This file is part of TYPO3 CMS-extension "ter_rest", created by Oliver Bartsch.
*
* 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.
*/
use Psr\Http\Message\ServerRequestInterface;
use T3o\TerRest\Authentication\AuthenticationHandlerInterface;
use T3o\TerRest\Authentication\User\ApiUserInterface;
/**
* Must be implemented by all authentication providers. This is mandatory to get
* them tagged by the compiler pass and dispatched by the authentication dispatcher.
*
* @see AuthenticationDispatcher
* @see AuthenticationProviderPass
*/
interface AuthenticationProviderInterface
{
public function authenticate(
ServerRequestInterface $request,
AuthenticationHandlerInterface $handler
): ApiUserInterface;
}
\ No newline at end of file
<?php
declare(strict_types = 1);
namespace T3o\TerRest\Authentication\Provider;
/*
* This file is part of TYPO3 CMS-extension "ter_rest", created by Oliver Bartsch.
*
* 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.
*/
use Psr\Http\Message\ServerRequestInterface;
use T3o\TerRest\Authentication\AuthenticationHandlerInterface;
use T3o\TerRest\Authentication\User\ApiUserInterface;
/**
* Authentication provider for basic auth
*/
final class BasicAuth implements AuthenticationProviderInterface
{
public function authenticate(
ServerRequestInterface $request,
AuthenticationHandlerInterface $handler
): ApiUserInterface {
return $handler->handle($request);
}
}
<?php
declare(strict_types = 1);
namespace T3o\TerRest\Authentication\Provider;
/*
* This file is part of TYPO3 CMS-extension "ter_rest", created by Oliver Bartsch.
*
* 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.
*/
use Psr\Http\Message\ServerRequestInterface;
use T3o\TerRest\Authentication\AuthenticationHandlerInterface;
use T3o\TerRest\Authentication\User\ApiUser;
use T3o\TerRest\Authentication\User\ApiUserInterface;
use TYPO3\CMS\Core\Context\Context;
use TYPO3\CMS\Frontend\Authentication\FrontendUserAuthentication;
/**
* Authentication provider for login as frontend user
*/
final class FrontendUser implements AuthenticationProviderInterface
{
protected Context $context;
public function __construct(Context $context)
{
$this->context = $context;
}
public function authenticate(
ServerRequestInterface $request,
AuthenticationHandlerInterface $handler
): ApiUserInterface {
if ((bool)$this->context->getPropertyFromAspect('frontend.user', 'id')) {
$frontendUserAuthentication = $request->getAttribute('frontend.user');
if ($frontendUserAuthentication instanceof FrontendUserAuthentication) {
$frontendUserAuthentication->fetchGroupData();
}
}
if ($this->context->getPropertyFromAspect('frontend.user', 'isLoggedIn')) {
return new ApiUser('FeUser', 3);
}
return $handler->handle($request);
}
}
<?php
declare(strict_types = 1);
namespace T3o\TerRest\Authentication\Provider;
/*
* This file is part of TYPO3 CMS-extension "ter_rest", created by Oliver Bartsch.
*
* 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.
*/
use Psr\Http\Message\ServerRequestInterface;
use T3o\TerRest\Authentication\AuthenticationHandlerInterface;
use T3o\TerRest\Authentication\User\ApiUserInterface;
/**
* Authentication provider for OAuth2 client credentials
*/
final class OAuth2ClientCredentials implements AuthenticationProviderInterface
{
public function authenticate(
ServerRequestInterface $request,
AuthenticationHandlerInterface $handler
): ApiUserInterface {
return $handler->handle($request);
}
}
<?php
declare(strict_types = 1);
namespace T3o\TerRest\Authentication;
/*
* This file is part of TYPO3 CMS-extension "ter_rest", created by Oliver Bartsch.
*
* 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.
*/
use TYPO3\CMS\Core\Type\BitSet;
/**
* Define the user scopes available for the REST API
*/
class Scope extends BitSet
{
public const EXTENSION_NONE = 0;
public const EXTENSION_READ = 1;
public const EXTENSION_WRITE = 2;
public const EXTENSION_ADMIN = 4;
public const ALL = 7;
public static function getMap(): array
{
return [
'extension:read' => static::EXTENSION_READ,
'extension:write' => static::EXTENSION_WRITE,
'extension:admin' => static::EXTENSION_ADMIN
];
}
public function isGranted(int $permission): bool
{
return $this->get($permission);
}
}
\ No newline at end of file
<?php
declare(strict_types = 1);
namespace T3o\TerRest\Authentication\User;
/*
* This file is part of TYPO3 CMS-extension "ter_rest", created by Oliver Bartsch.
*
* 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.
*/
/**
* Standard authorized API user
*/
class ApiUser implements ApiUserInterface
{
protected string $name;
protected int $scope;
public function __construct(string $name, int $scope)
{
$this->name = $name;
$this->scope = $scope;
}
public function getScope(): int
{
return $this->scope;
}
}
\ No newline at end of file
<?php
declare(strict_types = 1);
namespace T3o\TerRest\Authentication\User;
/*
* This file is part of TYPO3 CMS-extension "ter_rest", created by Oliver Bartsch.
*
* 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.
*/
/**
* Must be implemented by all API user implementations
*/
interface ApiUserInterface
{
public function getScope(): int;
}
<?php
declare(strict_types = 1);
namespace T3o\TerRest\Authentication\User;
/*
* This file is part of TYPO3 CMS-extension "ter_rest", created by Oliver Bartsch.
*
* 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.
*/
use T3o\TerRest\Authentication\AuthenticationHandler;
use T3o\TerRest\Authentication\Scope;
use T3o\TerRest\Controller\PingController;
/**
* Unauthorized user, used for endpoints which do not require authentication.
* This is the default user returned by the authentication handler if no provider
* has returned an authorized user beforehand.
*
* @see PingController
* @see AuthenticationHandler
*/
class UnauthorizedUser implements ApiUserInterface
{
public function getScope(): int
{
return Scope::EXTENSION_NONE;
}
}
\ No newline at end of file
......@@ -2,10 +2,10 @@
declare(strict_types = 1);
namespace T3o\Ter\Controller\Api;
namespace T3o\TerRest\Controller;
/*
* This file is part of TYPO3 CMS-extension "ter", created by Oliver Bartsch.
* This file is part of TYPO3 CMS-extension "ter_rest", created by Oliver Bartsch.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
......@@ -15,61 +15,71 @@ namespace T3o\Ter\Controller\Api;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use T3o\Ter\Rest\Response\ApiResponseFactory;
use T3o\Ter\Rest\RouteArgument\DeepObjectRouteArgumentInterface;
use T3o\Ter\Rest\RouteConfiguration;
use T3o\Ter\Rest\RouteResultArguments;
use T3o\TerRest\Authentication\Scope;
use T3o\TerRest\Authentication\User\ApiUserInterface;
use T3o\TerRest\Http\ResponseFactory;
use T3o\TerRest\Routing\RouteArgument\DeepObjectRouteArgumentInterface;
use T3o\TerRest\Routing\RouteConfiguration;
use T3o\TerRest\Routing\RouteResultArguments;
use TYPO3\CMS\Core\Routing\RouteResultInterface;
/**
* Receive the request, initialize the response using the ApiResponseFactory,
* validate the route arguments and finally call the operationId of the
* validate the route arguments and finally call the operationId for the
* specified request handler.
*
* @see ApiResponseFactory
* @see ResponseFactory
* @see RouteResultArguments
*/
abstract class AbstractApiController implements RequestHandlerInterface
abstract class AbstractController implements RequestHandlerInterface
{
protected ApiResponseFactory $apiResponseFactory;
protected ResponseFactory $responseFactory;
protected RouteConfiguration $routeConfiguration;
protected ServerRequestInterface $request;
protected RouteResultArguments $routeResultArguments;
protected RouteResultInterface $routeResult;
protected ApiUserInterface $apiUser;
public function __construct(ApiResponseFactory $apiResponseFactory, RouteConfiguration $routeConfiguration)
public function __construct(ResponseFactory $responseFactory, RouteConfiguration $routeConfiguration)
{
$this->apiResponseFactory = $apiResponseFactory;
$this->responseFactory = $responseFactory;
$this->routeConfiguration = $routeConfiguration;
}
public function handle(ServerRequestInterface $request): ResponseInterface
{
$this->request = $request;
$this->routeResult = $request->getAttribute('routing');
if