Files
2026-10-06 19:40:28 +02:00

363 lines
13 KiB
PHP

<?php
declare(strict_types=1);
namespace In2code\Femanager\Utility;
use In2code\Femanager\Domain\Model\User;
use In2code\Femanager\Domain\Model\UserGroup;
use In2code\Femanager\Domain\Repository\UserRepository;
use TYPO3\CMS\Core\Crypto\PasswordHashing\Argon2iPasswordHash;
use TYPO3\CMS\Core\Crypto\PasswordHashing\BcryptPasswordHash;
use TYPO3\CMS\Core\Crypto\PasswordHashing\BlowfishPasswordHash;
use TYPO3\CMS\Core\Crypto\PasswordHashing\InvalidPasswordHashException;
use TYPO3\CMS\Core\Crypto\PasswordHashing\Md5PasswordHash;
use TYPO3\CMS\Core\Crypto\PasswordHashing\PasswordHashFactory;
use TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash;
use TYPO3\CMS\Core\Crypto\PasswordHashing\PhpassPasswordHash;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
use TYPO3\CMS\Extbase\Reflection\ObjectAccess;
/**
* Class UserUtility
* @codeCoverageIgnore
*/
class UserUtility extends AbstractUtility
{
/**
* Return current logged in fe_user
*
* @return User|null
*/
public static function getCurrentUser()
{
if (self::getPropertyFromUser() !== null) {
$userRepository = GeneralUtility::makeInstance(UserRepository::class);
return $userRepository->findByUid((int)self::getPropertyFromUser());
}
return null;
}
/**
* Get property from current logged in Frontend User
*
* @param string $propertyName
* @return string|null
*/
public static function getPropertyFromUser($propertyName = 'uid')
{
if (!empty(self::getTypoScriptFrontendController()->fe_user->user[$propertyName])) {
return self::getTypoScriptFrontendController()->fe_user->user[$propertyName];
}
return null;
}
/**
* Get Usergroups from current logged in user
*
* array(
* 1,
* 5,
* 7
* )
*
* @return array
*/
public static function getCurrentUsergroupUids()
{
$currentLoggedInUser = self::getCurrentUser();
$usergroupUids = [];
if ($currentLoggedInUser !== null) {
foreach ($currentLoggedInUser->getUsergroup() as $usergroup) {
$usergroupUids[] = $usergroup->getUid();
}
}
return $usergroupUids;
}
/**
* Autogenerate username and password if it's empty
*
* @param User $user
* @return User $user
*/
public static function fallbackUsernameAndPassword(User $user)
{
$settings = self::getConfigurationManager()->getConfiguration(
ConfigurationManagerInterface::CONFIGURATION_TYPE_SETTINGS,
'Femanager',
'Pi1'
);
if (isset($settings['new']['misc']['autogenerate'])) {
$autogenerateSettings = $settings['new']['misc']['autogenerate'];
if (!$user->getUsername()) {
$user->setUsername(
StringUtility::getRandomString(
$autogenerateSettings['username']['length'],
$autogenerateSettings['username']['addUpperCase'],
$autogenerateSettings['username']['addSpecialCharacters']
)
);
if ($user->getEmail()) {
$user->setUsername($user->getEmail());
}
}
if (!$user->getPassword()) {
$password = StringUtility::getRandomString(
$autogenerateSettings['password']['length'],
$autogenerateSettings['password']['addUpperCase'],
$autogenerateSettings['password']['addSpecialCharacters']
);
$user->setPassword($password);
$user->setPasswordAutoGenerated($password);
}
}
return $user;
}
/**
* @param User $user
* @param array $settings
* @return User
*/
public static function takeEmailAsUsername(User $user, array $settings)
{
if (ConfigurationUtility::getValue('new/fillEmailWithUsername', $settings) === '1') {
$user->setEmail($user->getUsername());
}
return $user;
}
/**
* Overwrite usergroups from user by flexform settings
*
* @param User $user
* @param array $settings
* @param string $controllerName
* @return User $object
*/
public static function overrideUserGroup(User $user, $settings, $controllerName = 'new')
{
if (!empty($settings[$controllerName]['overrideUserGroup'])) {
$user->removeAllUsergroups();
$usergroupUids = GeneralUtility::trimExplode(',', $settings[$controllerName]['overrideUserGroup'], true);
foreach ($usergroupUids as $usergroupUid) {
/** @var UserGroup $usergroup */
$usergroup = self::getUserGroupRepository()->findByUid($usergroupUid);
$user->addUsergroup($usergroup);
}
}
return $user;
}
/**
* Convert password to Argon2i, Bcrypt, Pbkdf2, Phpass, Blowfish or Md5 hash
*
* @param User $user
* @param string $method
* @throws InvalidPasswordHashException
*/
public static function convertPassword(User $user, $method)
{
if (array_key_exists('password', UserUtility::getDirtyPropertiesFromUser($user))) {
self::hashPassword($user, $method);
}
}
/**
* Hash a password from $user->getPassword()
*
* @param User $user
* @param string $method "Argon2i", "Bcrypt", "Pbkdf2", "Phpass", "Blowfish", "md5" or "none" ("sha1" for TYPO3 V8)
* @throws InvalidPasswordHashException
*/
public static function hashPassword(User &$user, $method)
{
$hashInstance = false;
$saltedHashPassword = $user->getPassword();
/** @var PasswordHashFactory $passwordHashFactory */
$passwordHashFactory = GeneralUtility::makeInstance(PasswordHashFactory::class);
switch ($method) {
case 'Argon2i':
$hashInstance = GeneralUtility::makeInstance(Argon2iPasswordHash::class);
break;
case 'Bcrypt':
$hashInstance = GeneralUtility::makeInstance(BcryptPasswordHash::class);
break;
case 'Pbkdf2':
$hashInstance = GeneralUtility::makeInstance(Pbkdf2PasswordHash::class);
break;
case 'Phpass':
$hashInstance = GeneralUtility::makeInstance(PhpassPasswordHash::class);
break;
case 'Blowfish':
$hashInstance = GeneralUtility::makeInstance(BlowfishPasswordHash::class);
break;
case 'md5':
$hashInstance = GeneralUtility::makeInstance(Md5PasswordHash::class);
break;
case 'none':
break;
default:
$hashInstance = $passwordHashFactory->getDefaultHashInstance(TYPO3_MODE);
}
if ($hashInstance === false) {
$user->setPassword($saltedHashPassword);
} else {
$user->setPassword($hashInstance->getHashedPassword($saltedHashPassword));
}
}
/**
* Get changed properties (compare two objects with same getter methods)
*
* @param User $changedObject
* @return array
* [firstName][old] = Alex
* [firstName][new] = Alexander
*/
public static function getDirtyPropertiesFromUser(User $changedObject)
{
$dirtyProperties = [];
$ignoreProperties = [
'txFemanagerChangerequest',
'ignoreDirty',
'isOnline',
'lastlogin'
];
foreach ($changedObject->_getCleanProperties() as $propertyName => $oldPropertyValue) {
if (method_exists($changedObject, 'get' . ucfirst($propertyName))
&& !in_array($propertyName, $ignoreProperties)
) {
$newPropertyValue = $changedObject->{'get' . ucfirst($propertyName)}();
if (!is_object($oldPropertyValue) || !is_object($newPropertyValue)) {
if ($oldPropertyValue !== $newPropertyValue) {
$dirtyProperties[$propertyName]['old'] = $oldPropertyValue;
$dirtyProperties[$propertyName]['new'] = $newPropertyValue;
}
} else {
if (get_class($oldPropertyValue) === 'DateTime') {
/** @var $oldPropertyValue \DateTime */
/** @var $newPropertyValue \DateTime */
if ($oldPropertyValue->getTimestamp() !== $newPropertyValue->getTimestamp()) {
$dirtyProperties[$propertyName]['old'] = $oldPropertyValue->getTimestamp();
$dirtyProperties[$propertyName]['new'] = $newPropertyValue->getTimestamp();
}
} elseif (get_class($oldPropertyValue) === ObjectStorage::class) {
$titlesOld = ObjectUtility::implodeObjectStorageOnProperty($oldPropertyValue);
$titlesNew = ObjectUtility::implodeObjectStorageOnProperty($newPropertyValue);
if ($titlesOld !== $titlesNew) {
$dirtyProperties[$propertyName]['old'] = $titlesOld;
$dirtyProperties[$propertyName]['new'] = $titlesNew;
}
} else {
$uidOld = ObjectAccess::getProperty($oldPropertyValue, 'uid');
$uidNew = ObjectAccess::getProperty($newPropertyValue, 'uid');
if ($uidOld !== $uidNew) {
$dirtyProperties[$propertyName]['old'] = $uidOld;
$dirtyProperties[$propertyName]['new'] = $uidNew;
}
}
}
}
}
return $dirtyProperties;
}
/**
* overwrite user with old values and xml with new values
*
* @param User $user
* @param array $dirtyProperties
* @return User $user
*/
public static function rollbackUserWithChangeRequest($user, $dirtyProperties)
{
$existingProperties = $user->_getCleanProperties();
// reset old values
$user->setUserGroup($existingProperties['usergroup']);
foreach ($dirtyProperties as $propertyName => $propertyValue) {
$propertyValue = null;
$user->{'set' . ucfirst($propertyName)}($existingProperties[$propertyName]);
}
// store changes as xml in field fe_users.tx_femanager_changerequest
$user->setTxFemanagerChangerequest(GeneralUtility::array2xml($dirtyProperties, '', 0, 'changes'));
return $user;
}
/**
* Remove FE Session to a given user
*
* @param User $user
*/
public static function removeFrontendSessionToUser(User $user)
{
self::getConnectionPool()->getConnectionForTable('fe_sessions')->delete(
'fe_sessions',
['ses_userid' => (int)$user->getUid()]
);
}
/**
* Check if FE Session exists
*
* @param User $user
* @return bool
*/
public static function checkFrontendSessionToUser(User $user)
{
$queryBuilder = self::getConnectionPool()->getQueryBuilderForTable('fe_sessions');
$row = $queryBuilder->select('ses_id')
->from('fe_sessions')
->where(
$queryBuilder->expr()->eq('ses_userid', (int)$user->getUid())
)
->execute()
->fetch();
return !empty($row['ses_id']);
}
/**
* Login FE-User
*
* @param User $user
* @param string|null $storagePids
*/
public static function login(User $user, $storagePids = null)
{
// ensure a session cookie is set (in case there is no session yet)
$GLOBALS['TSFE']->fe_user->setAndSaveSessionData('dummy', true);
// create the session (destroys all existing session data in the session backend!)
$GLOBALS['TSFE']->fe_user->createUserSession(['uid' => (int)$user->getUid()]);
// re-fetch the session from the database so that the internal 'userSession' property of TSFE gets updated
$GLOBALS['TSFE']->fe_user->fetchUserSession();
// write the session data again to the session backend; preserves what was there before!!
$GLOBALS['TSFE']->fe_user->setAndSaveSessionData('dummy', true);
}
}