Skip to content

Commit

Permalink
Merge branch 'release/6.2.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
sbusemann committed Jun 6, 2021
2 parents 23a23bf + 4a3d6cc commit 1f19f14
Show file tree
Hide file tree
Showing 44 changed files with 669 additions and 186 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ var
dynamicReturnTypeMeta.json
/Documentation-GENERATED-temp/
docker-compose.yml
.php_cs.cache
4 changes: 2 additions & 2 deletions .project/data/db.sql.gz
Git LFS file not shown
10 changes: 8 additions & 2 deletions Classes/Controller/AbstractController.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ abstract class AbstractController extends ActionController
*/
protected $sendMailService;

/**
* @var \In2code\Femanager\Domain\Service\RatelimiterService
* @TYPO3\CMS\Extbase\Annotation\Inject
*/
protected $ratelimiterService;

/**
* @var \In2code\Femanager\Finisher\FinisherRunner
* @TYPO3\CMS\Extbase\Annotation\Inject
Expand Down Expand Up @@ -232,7 +238,7 @@ public function finalCreate(
$this->settings['new']['email']['createUserNotify']['sender']['email']['value'],
$this->settings['new']['email']['createUserNotify']['sender']['name']['value']
),
'Profile creation',
$this->settings['new']['email']['createUserNotify']['subject']['data'],
$variables,
$this->config['new.']['email.']['createUserNotify.']
);
Expand All @@ -247,7 +253,7 @@ public function finalCreate(
$this->settings['new']['email']['createAdminNotify']['receiver']['name']['value']
),
StringUtility::makeEmailArray($user->getEmail(), $user->getUsername()),
'Profile creation',
$this->settings['new']['email']['createAdminNotify']['subject']['data'],
$variables,
$this->config['new.']['email.']['createAdminNotify.']
);
Expand Down
10 changes: 10 additions & 0 deletions Classes/Controller/InvitationController.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,15 @@ public function newAction()
*/
public function createAction(User $user)
{
if ($this->ratelimiterService->isLimited()) {
$this->addFlashMessage(
LocalizationUtility::translate('ratelimiter_too_many_attempts'),
'',
FlashMessage::ERROR
);
$this->redirect('status');
}

$this->allowedUserForInvitationNewAndCreate();
$user->setDisable(true);
$user = FrontendUtility::forceValues(
Expand All @@ -58,6 +67,7 @@ public function createAction(User $user)
}
UserUtility::hashPassword($user, $this->settings['invitation']['misc']['passwordSave']);
$this->eventDispatcher->dispatch(new InviteUserCreateEvent($user));
$this->ratelimiterService->consumeSlot();
$this->createAllConfirmed($user);
}

Expand Down
9 changes: 9 additions & 0 deletions Classes/Controller/NewController.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,22 @@ public function newAction(User $user = null)
*/
public function createAction(User $user)
{
if ($this->ratelimiterService->isLimited()) {
$this->addFlashMessage(
LocalizationUtility::translate('ratelimiter_too_many_attempts'),
'',
FlashMessage::ERROR
);
$this->redirect('createStatus');
}
$user = UserUtility::overrideUserGroup($user, $this->settings);
$user = FrontendUtility::forceValues($user, $this->config['new.']['forceValues.']['beforeAnyConfirmation.']);
$user = UserUtility::fallbackUsernameAndPassword($user);
$user = UserUtility::takeEmailAsUsername($user, $this->settings);
UserUtility::hashPassword($user, $this->settings['new']['misc']['passwordSave']);

$this->eventDispatcher->dispatch(new BeforeUserCreateEvent($user));
$this->ratelimiterService->consumeSlot();

if ($this->isAllConfirmed()) {
$this->createAllConfirmed($user);
Expand Down
12 changes: 11 additions & 1 deletion Classes/Domain/Repository/UserRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ public function findByUsergroups($userGroupList, $settings, $filter)
public function checkUniqueDb($field, $value, User $user = null)
{
$query = $this->createQuery();
$this->ignoreEnableFieldsAndStoragePage($query);
$this->ignoreEnableFieldsAndStoragePageAndStarttime($query);

$and = [$query->equals($field, $value)];
if (method_exists($user, 'getUid')) {
Expand Down Expand Up @@ -291,6 +291,16 @@ protected function ignoreEnableFieldsAndStoragePage(QueryInterface $query)
$query->getQuerySettings()->setEnableFieldsToBeIgnored(['disabled']);
}

/**
* @param QueryInterface $query
* @return void
*/
protected function ignoreEnableFieldsAndStoragePageAndStarttime(QueryInterface $query)
{
$this->ignoreEnableFieldsAndStoragePage($query);
$query->getQuerySettings()->setEnableFieldsToBeIgnored(['disabled','starttime','endtime']);
}

/**
* Find All
*
Expand Down
127 changes: 127 additions & 0 deletions Classes/Domain/Service/RatelimiterService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
<?php

declare(strict_types=1);

namespace In2code\Femanager\Domain\Service;

use TYPO3\CMS\Core\Cache\CacheManager;
use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
use TYPO3\CMS\Core\Crypto\Random;
use TYPO3\CMS\Core\SingletonInterface;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;

class RatelimiterService implements SingletonInterface
{
const CACHE_IDENTIFIER = 'femanager_ratelimiter';
const DEFAULT_CONFIG = ['timeframe' => 60, 'limit' => 3];
const LIMIT_IP = 'limit_ip_';
const SESSION_KEY = 'tx_femanager_ratelimiter';

/** @var FrontendInterface */
protected $cache;

/** @var int */
protected $limit;

/** @var int */
protected $timeframe;

public function __construct()
{
$this->cache = GeneralUtility::makeInstance(CacheManager::class)->getCache(self::CACHE_IDENTIFIER);
$setup = $this->getTSFE()->tmpl->setup;
$config = $setup['plugin.']['tx_femanager.']['settings.']['ratelimiter.'] ?? self::DEFAULT_CONFIG;
$this->timeframe = (int)$config['timeframe'];
$this->limit = (int)$config['limit'];
}

protected function getTSFE(): TypoScriptFrontendController
{
return $GLOBALS['TSFE'];
}

public function isLimited(): bool
{

if ($this->limit > 0) {
$userIp = GeneralUtility::getIndpEnv('REMOTE_ADDR');
$userIpAccess = $this->getToken(self::LIMIT_IP, $userIp);

return count($userIpAccess) >= $this->limit;
}
return false;
}

public function consumeSlot()
{
if ($this->limit > 0) {
$userIp = GeneralUtility::getIndpEnv('REMOTE_ADDR');
$this->consumeToken(self::LIMIT_IP, $userIp);
}
}

protected function getCookie()
{
$this->touchCookie();

return $this->getTSFE()->fe_user->getSessionData(self::SESSION_KEY);
}

public function touchCookie()
{
$feUser = $this->getTSFE()->fe_user;
$identifier = $feUser->getSessionData(self::SESSION_KEY);

if (null === $identifier) {
$unique = GeneralUtility::makeInstance(Random::class)->generateRandomHexString(16);
$feUser->setAndSaveSessionData(self::SESSION_KEY, $unique);
}
}

protected function consumeToken(string $tokenName, string $value): array
{
$cacheID = $this->getCacheID($tokenName, $value);

$token = $this->retrieveToken($cacheID);
$token[] = $GLOBALS['EXEC_TIME'];
$this->cache->set($cacheID, $token, [], $this->timeframe);

return $token;
}

protected function getCacheID(string $tokenName, string $value): string
{
return $tokenName.hash('sha1', $value);
}

protected function retrieveToken(string $cacheID): array
{
$token = [];
if ($this->cache->has($cacheID)) {
$token = $this->cache->get($cacheID);
$token = $this->filterExpiredToken($token);
}

return $token;
}

protected function filterExpiredToken(array $token): array
{
$slidingWindowStartTime = $GLOBALS['EXEC_TIME'] - $this->timeframe;
foreach ($token as $idx => $accessTime) {
if ($accessTime < $slidingWindowStartTime) {
unset($token[$idx]);
}
}

return $token;
}

protected function getToken(string $tokenName, string $value): array
{
$cacheID = $this->getCacheID($tokenName, $value);

return $this->retrieveToken($cacheID);
}
}
3 changes: 2 additions & 1 deletion Classes/Domain/Service/ValidationSettingsService.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class ValidationSettingsService
'email',
'intOnly',
'lettersOnly',
'unicodeLettersOnly',
'required',
'uniqueInDb',
'uniqueInPage'
Expand All @@ -53,7 +54,7 @@ public function __construct(string $controllerName, string $validationName)
/**
* Get validation string like
* required, email, min(10), max(10), intOnly,
* lettersOnly, uniqueInPage, uniqueInDb, date,
* lettersOnly, unicodeLettersOnly, uniqueInPage, uniqueInDb, date,
* mustInclude(number|letter|special), inList(1|2|3)
*
* @param string $fieldName Fieldname
Expand Down
17 changes: 14 additions & 3 deletions Classes/Domain/Validator/AbstractValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ protected function validateInt($value)
}

/**
* Validation for Letters only
* Validation for Letters (a-zA-Z), hyphen and underscore
*
* @param string $value
* @return \bool
Expand All @@ -142,6 +142,17 @@ protected function validateLetters($value)
return false;
}

/**
* Validation for all Unicode letters, hyphen and underscore
*
* @param string $value
* @return \bool
*/
protected function validateUnicodeLetters($value)
{
return (bool)preg_match('/^[\pL_-]+$/u', $value);
}

/**
* Validation for Unique in sysfolder
*
Expand Down Expand Up @@ -358,15 +369,15 @@ protected function validateDate($value, $validationSetting)
switch ($validationSetting) {
case 'd.m.Y':
if (preg_match('/^([0-9]{2})\.([0-9]{2})\.([0-9]{4})$/', $value, $dateParts)) {
if (checkdate($dateParts[2], $dateParts[1], $dateParts[3])) {
if (checkdate((int)$dateParts[2], (int)$dateParts[1], (int)$dateParts[3])) {
return true;
}
}
break;

case 'm/d/Y':
if (preg_match('/^([0-9]{2})\/([0-9]{2})\/([0-9]{4})$/', $value, $dateParts)) {
if (checkdate($dateParts[1], $dateParts[2], $dateParts[3])) {
if (checkdate((int)$dateParts[1], (int)$dateParts[2], (int)$dateParts[3])) {
return true;
}
}
Expand Down
9 changes: 8 additions & 1 deletion Classes/Domain/Validator/ClientsideValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class ClientsideValidator extends AbstractValidator
/**
* Validation settings string
* possible validations for each field are:
* required, email, min(12), max(13), intOnly, lettersOnly,
* required, email, min(12), max(13), intOnly, lettersOnly,unicodeLettersOnly
* uniqueInPage, uniqueInDb, date, mustInclude(number,letter,special),
* inList(1,2,3)
*
Expand Down Expand Up @@ -136,6 +136,13 @@ public function validateField()
}
break;

case 'unicodeLettersOnly':
if ($this->getValue() && !$this->validateUnicodeLetters($this->getValue())) {
$this->addMessage('validationErrorLetters');
$this->isValid = false;
}
break;

case 'uniqueInPage':
if ($this->getValue() &&
!$this->validateUniquePage($this->getValue(), $this->getFieldName(), $this->getUser())
Expand Down
24 changes: 24 additions & 0 deletions Classes/Domain/Validator/ServersideValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use In2code\Femanager\Domain\Model\User;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Domain\Model\FileReference;
use TYPO3\CMS\Extbase\Reflection\ObjectAccess;

use function array_key_exists;
Expand Down Expand Up @@ -59,6 +60,10 @@ public function isValid($user): bool
$this->checkLetterOnlyValidation($value, $validationSetting, $fieldName);
break;

case 'unicodeLettersOnly':
$this->checkUnicodeLetterOnlyValidation($value, $validationSetting, $fieldName);
break;

case 'uniqueInPage':
$this->checkUniqueInPageValidation($user, $value, $validationSetting, $fieldName);
break;
Expand Down Expand Up @@ -184,6 +189,20 @@ protected function checkLetterOnlyValidation($value, $validationSetting, $fieldN
}
}

/**
* @param $value
* @param $validationSetting
* @param $fieldName
* @return void
*/
protected function checkUnicodeLetterOnlyValidation($value, $validationSetting, $fieldName)
{
if (!empty($value) && $validationSetting === '1' && !$this->validateUnicodeLetters($value)) {
$this->addError('validationErrorLetters', 0, ['code' => $fieldName]);
$this->isValid = false;
}
}

/**
* @param $user
* @param $value
Expand Down Expand Up @@ -316,6 +335,11 @@ protected function getValue($user, $fieldName)
}
if (method_exists($value, 'current')) {
$current = $value->current();

if ($current instanceof FileReference){
return true;
}

if (method_exists($current, 'getUid')) {
$value = $current->getUid();
}
Expand Down
1 change: 0 additions & 1 deletion Classes/Event/InviteUserCreateEvent.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,4 @@

class InviteUserCreateEvent extends UserEvent
{

}
5 changes: 2 additions & 3 deletions Classes/Utility/FrontendUtility.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,8 @@ public static function getCurrentPid(): int
public static function getFrontendLanguageUid(): int
{
$languageUid = 0;
if (!empty(self::getTypoScriptFrontendController()->tmpl->setup['config.']['sys_language_uid'])) {
$languageUid = (int)self::getTypoScriptFrontendController()->tmpl->setup['config.']['sys_language_uid'];
}
$languageAspect = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Core\Context\Context::class)->getAspect('language');
$languageUid = $languageAspect->getId();
return $languageUid;
}

Expand Down
Loading

0 comments on commit 1f19f14

Please sign in to comment.