Commit 73e1cb51 authored by Thomas Löffler's avatar Thomas Löffler

Merge branch 'develop' into 164-download-counter-also-count-downloads-via-packagist-api

parents 6c58c5ea 8c4bb65c
Pipeline #10069 failed with stages
in 3 minutes and 17 seconds
#!/bin/bash
## Description: Generate ecdsa keys for generating tokens in the REST API
## Usage: create-keys
## Example: "ddev create-keys"
readonly KEYS_PATH=/var/www/html
mkdir -p $KEYS_PATH
openssl ecparam -name secp521r1 -genkey -noout -out $KEYS_PATH/private.test.ec.key
openssl pkcs8 -topk8 -in $KEYS_PATH/private.test.ec.key -out $KEYS_PATH/private.test.pem -passin env:ECDSA_KEY_PASSPHRASE -passout env:ECDSA_KEY_PASSPHRASE
openssl ec -in $KEYS_PATH/private.test.pem -pubout -out $KEYS_PATH/public.test.pem -passin env:ECDSA_KEY_PASSPHRASE
rm $KEYS_PATH/private.test.ec.key
......@@ -8,3 +8,11 @@ services:
- DB_HOST=db
- DB_NAME=db
- DB_PASSWORD=db
- ECDSA_PUBLIC_KEY_FILE=/var/www/html/public.test.pem
- ECDSA_PRIVATE_KEY_FILE=/var/www/html/private.test.pem
- ECDSA_KEY_PASSPHRASE=passphrase
- TER_REST_RANDOM_LENGTH=30
- TER_REST_DEFAULT_LIFETIME=604800
- TER_REST_JWT_SUBJECT=t3o-ter-rest
- TER_REST_JWT_LATENCY=60
- TER_REST_SIGNATURE_IDENTIFIER=ecdsa
......@@ -9,6 +9,7 @@
!/public/.well-known/security.txt
/auth.json
/assets/
/*.pem
sequelpro.spf
.php_cs.cache
.ddev/db_snapshots/
......
......@@ -32,13 +32,14 @@ test:unit:
- apt-get update -yqq
- apt-get install git unzip zlib1g-dev libzip-dev -yqq
- docker-php-ext-install zip
- pecl install xdebug
- pecl install xdebug-2.9.2
- docker-php-ext-enable xdebug
- curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
- export TYPO3_PATH_WEB="$PWD/private"
- composer config cache-dir /cache/composer
- mkdir $TYPO3_PATH_WEB/fileadmin/ && touch $TYPO3_PATH_WEB/fileadmin/currentcoredata.json
script:
- composer selfupdate --1
- composer install --ignore-platform-reqs
- composer test:unit
artifacts:
......@@ -57,13 +58,14 @@ test:mutation:
- apt-get update -yqq
- apt-get install git unzip zlib1g-dev libzip-dev -yqq
- docker-php-ext-install zip
- pecl install xdebug
- pecl install xdebug-2.9.2
- docker-php-ext-enable xdebug
- curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
- export TYPO3_PATH_WEB="$PWD/private"
- composer config cache-dir /cache/composer
- mkdir $TYPO3_PATH_WEB/fileadmin/ && touch $TYPO3_PATH_WEB/fileadmin/currentcoredata.json
script:
- composer selfupdate --1
- composer install --ignore-platform-reqs
- ./vendor/bin/infection --min-msi=10 --min-covered-msi=75 --threads=4
artifacts:
......@@ -97,4 +99,4 @@ mutation:badge:
paths:
- badges/
when: always
expire_in: 4 weeks
\ No newline at end of file
expire_in: 4 weeks
......@@ -17,10 +17,14 @@
<testsuite name="ter_fe2 tests">
<directory>../../extensions/ter_fe2/Tests/Unit</directory>
</testsuite>
<testsuite name="ter_rest tests">
<directory>../../extensions/ter_rest/Tests/Unit</directory>
</testsuite>
</testsuites>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">../../extensions/ter_fe2/Classes/</directory>
<directory suffix=".php">../../extensions/ter_rest/Classes/</directory>
</whitelist>
</filter>
<logging>
......
......@@ -14,6 +14,7 @@ set('shared_dirs', $sharedDirectories);
$sharedFiles = [
'private/typo3conf/AdditionalConfiguration.php',
'private/typo3conf/ext/t3olayout/Configuration/TypoScript/Deployment/production.typoscript',
'.cachetool.yml'
];
set('shared_files', $sharedFiles);
......
......@@ -40,10 +40,12 @@
"apache-solr-for-typo3/solr": "^11.0",
"cweagans/composer-patches": "^1.7",
"gordalina/cachetool": "^4.0",
"lcobucci/jwt": "^3.3",
"nimut/testing-framework": "^5.0",
"t3o/t3olayout": "^5.0",
"t3o/ter-frontend": "^0.5.0",
"t3o/ter-layout": "^0.2.0",
"t3o/ter-rest": "^0.1.0",
"t3o/ter-soap": "^2.1",
"typo3/cms-adminpanel": "^10.4",
"typo3/cms-seo": "^10.4"
......@@ -52,6 +54,7 @@
"codeception/codeception": "^4.1",
"codeception/module-asserts": "^1.2",
"codeception/module-phpbrowser": "^1.0",
"friendsofphp/php-cs-fixer": "^2.16",
"infection/infection": "^0.20.0",
"neos/utility-arrays": "^6.0",
"neos/utility-files": "^6.0",
......@@ -84,6 +87,9 @@
],
"test:api": [
"./vendor/bin/codecept run api --steps"
],
"test:php": [
"./vendor/bin/php-cs-fixer fix --dry-run --diff-format=udiff --path-mode=intersection --config=.gitlab-ci/build/.php_cs --diff extensions"
]
}
}
This diff is collapsed.
......@@ -143,11 +143,14 @@ class SoapEndpoint implements MiddlewareInterface
</SOAP-ENV:Fault>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>';
return new Response(
sprintf($faultStringXmlTemplate, $e->getCode(), $e->getMessage()),
$response = new Response(
'php://temp',
$statusCode,
['Content-Type' => 'text/xml; charset=utf-8']
);
$response->getBody()->rewind();
$response->getBody()->write(sprintf($faultStringXmlTemplate, $e->getCode(), $e->getMessage()));
return $response;
}
}
}
......@@ -52,6 +52,7 @@ class AddCompatibleTypo3Versions extends Command
$queryBuilder = $connectionPool->getQueryBuilderForTable('tx_terfe2_domain_model_version');
$io->section('Updating field \'compatible_typo3_versions\' of all \'tx_terfe2_domain_model_version\' rows now');
$io->warning('Please make sure, the new column \'compatible_typo3_versions\' exists.');
$queryBuilder->getRestrictions()->removeAll();
$queryBuilder
......@@ -69,11 +70,19 @@ class AddCompatibleTypo3Versions extends Command
$itemsProcessed = 0;
while ($row = $result->fetchAssociative()) {
// @todo Once https://gitlab.typo3.org/t3o/ter/-/merge_requests/600 is merged,
// @todo we need to json_encode the dependencies instead of unserialize.
// First check if the data is still in old serialized format
$dependencies = (array)(unserialize((string)($row['dependencies'] ?? ''), ['allowed_classes' => false]) ?: []);
if ($dependencies === []) {
continue;
// Fallback for the new JSON format
try {
$dependencies = (array)(json_decode((string)($row['dependencies'] ?? ''), true, 512, JSON_THROW_ON_ERROR) ?: []);
if ($dependencies === []) {
// If still empty move on with next row
continue;
}
} catch (\JsonException $e) {
continue;
}
}
$compatibleTypo3Versions = VersionUtility::getCompatibleTypo3Versions($dependencies);
......
......@@ -169,7 +169,12 @@ class ExtensionController extends \T3o\TerFe2\Controller\AbstractController
public function showAction(\T3o\TerFe2\Domain\Model\Extension $extension = null)
{
if ($extension === null || !$extension instanceof \T3o\TerFe2\Domain\Model\Extension) {
$GLOBALS['TSFE']->pageNotFoundAndExit('Extension not found!');
GeneralUtility::makeInstance(\TYPO3\CMS\Frontend\Controller\ErrorController::class)
->pageNotFoundAction(
$GLOBALS['TYPO3_REQUEST'],
'Extension not found!',
['code' => \TYPO3\CMS\Frontend\Page\PageAccessFailureReasons::INVALID_PAGE_ARGUMENTS]
);
}
$owner = [];
if ($extension instanceof \T3o\TerFe2\Domain\Model\Extension and $extension->getFrontendUser()) {
......
<?php
declare(strict_types = 1);
namespace T3o\TerFe2\Controller;
/*
* This file is part of TYPO3 CMS-extension "ter_fe2", 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 GuzzleHttp\Exception\RequestException;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use T3o\TerFe2\Domain\DTO\TokenFormData;
use T3o\TerFe2\Domain\Repository\ExtensionRepository;
use T3o\TerFe2\Service\TokenRequestService;
use TYPO3\CMS\Core\Context\Context;
use TYPO3\CMS\Core\Log\LogManager;
use TYPO3\CMS\Core\Messaging\AbstractMessage;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
use TYPO3\CMS\Extbase\Property\TypeConverter\DateTimeConverter;
/**
* Controller for managing access tokens
*/
class TokenController extends ActionController implements LoggerAwareInterface
{
use LoggerAwareTrait;
private const API_ACTIONS = ['create', 'refresh', 'revoke'];
protected ExtensionRepository $extensionRepository;
protected TokenRequestService $tokenRequestService;
protected Context $context;
public function __construct(
ExtensionRepository $extensionRepository,
TokenRequestService $tokenRequestService,
Context $context,
LogManager $logManager
) {
$this->extensionRepository = $extensionRepository;
$this->tokenRequestService = $tokenRequestService;
$this->context = $context;
$this->setLogger($logManager->getLogger('TER.API.REST.GUI'));
}
public function indexAction(): void
{
$this->view->assignMultiple([
'actions' => self::API_ACTIONS,
'activeTab' => $this->getActiveTab(),
'extensions' => $this->extensionRepository->findByFrontendUser(
(string)$this->context->getPropertyFromAspect('frontend.user', 'username')
)
]);
}
/**
* @param TokenFormData $tokenFormData
* @TYPO3\CMS\Extbase\Annotation\Validate("T3o\TerFe2\Validation\Validator\TokenCreationValidator", param="tokenFormData")
*/
public function createAction(TokenFormData $tokenFormData): void
{
try {
$responseContent = (string)$this->tokenRequestService->request(
'/token',
$tokenFormData->getPassword(),
['query' => $this->tokenRequestService->createQueryArguments($tokenFormData)]
)->getBody()->getContents();
} catch (RequestException $e) {
$this->addFlashMessageForException($e);
$this->redirectToIndexAction();
}
$this->view->assign('tokenData', json_decode($responseContent, true, 512, JSON_THROW_ON_ERROR));
}
/**
* @param TokenFormData $tokenFormData
* @TYPO3\CMS\Extbase\Annotation\Validate("T3o\TerFe2\Validation\Validator\TokenUpdateValidator", param="tokenFormData")
*/
public function refreshAction(TokenFormData $tokenFormData): void
{
try {
$responseContent = (string)$this->tokenRequestService->request(
'/token/refresh',
$tokenFormData->getPassword(),
['form_params' => $this->tokenRequestService->createFormParams($tokenFormData)]
)->getBody()->getContents();
} catch (RequestException $e) {
$this->addFlashMessageForException($e);
$this->redirectToIndexAction();
}
$this->view->assign('tokenData', json_decode($responseContent, true, 512, JSON_THROW_ON_ERROR));
}
/**
* @param TokenFormData $tokenFormData
* @TYPO3\CMS\Extbase\Annotation\Validate("T3o\TerFe2\Validation\Validator\TokenUpdateValidator", param="tokenFormData")
*/
public function revokeAction(TokenFormData $tokenFormData): void
{
try {
$this->tokenRequestService->request(
'/token/revoke',
$tokenFormData->getPassword(),
['form_params' => $this->tokenRequestService->createFormParams($tokenFormData)]
);
$this->addFlashMessage('', 'Token sucessfully revoked');
} catch (RequestException $e) {
$this->addFlashMessageForException($e);
}
$this->redirectToIndexAction();
}
protected function initializeCreateAction(): void
{
if ($this->arguments->hasArgument('tokenFormData')) {
$this->arguments
->getArgument('tokenFormData')
->getPropertyMappingConfiguration()
->forProperty('expires')
->setTypeConverterOption(DateTimeConverter::class, DateTimeConverter::CONFIGURATION_DATE_FORMAT, 'Y-m-d');
}
}
protected function getActiveTab(): string
{
$activeTab = (string)($this->request->getArguments()['activeTab'] ?? '');
$apiActions = self::API_ACTIONS;
if ($activeTab === '' || !in_array($activeTab, $apiActions, true)) {
$this->redirectToIndexAction(array_shift($apiActions));
}
return $activeTab;
}
protected function redirectToIndexAction(string $activeTab = ''): void
{
if ($activeTab === '') {
$activeTab = str_replace('Action', '', $this->actionMethodName);
}
$this->redirect('index', 'Token', null, ['activeTab' => $activeTab]);
}
protected function getErrorFlashMessage(): bool
{
return false;
}
protected function addFlashMessageForException(RequestException $exception): void
{
$action = str_replace('Action', '', $this->actionMethodName);
switch ((int)$exception->getCode()) {
case 400:
$this->addFlashMessage(
'Could not ' . $action . ' token because the token is invalid or not yet ready.',
'Error on validation',
AbstractMessage::ERROR
);
break;
case 401:
case 403:
$this->addFlashMessage(
'Could not ' . $action . ' token due to invalid authorization.',
'Error on authentication',
AbstractMessage::ERROR
);
break;
default:
$this->logger->error($exception->getMessage());
$this->addFlashMessage(
'Could not ' . $action . ' token due to a server error. Please try again.',
'General server error',
AbstractMessage::ERROR
);
break;
}
}
}
<?php
declare(strict_types = 1);
namespace T3o\TerFe2\Domain\DTO;
/*
* This file is part of TYPO3 CMS-extension "ter_fe2", 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.
*/
/**
* DTO for token request data
*/
class TokenFormData
{
protected string $password;
protected string $name;
protected ?\DateTime $expires;
protected array $scope;
protected array $extensions;
protected string $jwt;
public function __construct(
string $password,
string $name = '',
\DateTime $expires = null,
array $scope = [],
array $extensions = [],
string $jwt = ''
) {
$this->password = $password;
$this->name = $name;
$this->expires = $expires;
$this->scope = $scope;
$this->extensions = $extensions;
$this->jwt = $jwt;
}
public function getPassword(): string
{
return $this->password;
}
public function getName(): string
{
return $this->name;
}
public function getExpires(): ?\DateTime
{
return $this->expires;
}
public function getScope(): array
{
return $this->scope;
}
public function getExtensions(): array
{
return $this->extensions;
}
public function getJwt(): string
{
return $this->jwt;
}
}
......@@ -17,6 +17,7 @@ namespace T3o\TerFe2\Domain\Model;
use T3o\TerFe2\Domain\Repository\DownloadRepository;
use T3o\TerFe2\Utility\VersionUtility;
use T3o\TerRest\DTO\Extension as ExtensionDto;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\VersionNumberUtility;
use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
......@@ -24,7 +25,7 @@ use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
/**
* Extension container
*/
class Extension extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity
class Extension extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity implements \JsonSerializable
{
/**
* Extension key
......@@ -270,6 +271,27 @@ class Extension extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity
return $this->versions;
}
/**
* Getter for a single version
*
* @param mixed $identifier Either the version number (e.g. 1000001) or the version string (e.g. 1.0.1)
* @return Version|null
*/
public function getVersion($identifier): ?Version
{
foreach ($this->versions as $version) {
if ($version instanceof Version
&& ((is_int($identifier) && $version->getVersionNumber() === $identifier)
|| (is_string($identifier) && $version->getVersionString() === $identifier))
) {
return $version;
}
}
return null;
}
/**
* Get versions reverse sorted by version number
*
......@@ -770,28 +792,53 @@ class Extension extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity
public function getAdditionalSupportVersions(): array
{
$supportDevVersion = [];