1290 lines
51 KiB
PHP
1290 lines
51 KiB
PHP
<?php
|
|
|
|
/*
|
|
* This file is part of the TYPO3 CMS project.
|
|
*
|
|
* 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!
|
|
*/
|
|
|
|
namespace TYPO3\CMS\Core\Authentication;
|
|
|
|
use Psr\EventDispatcher\EventDispatcherInterface;
|
|
use Psr\Http\Message\ResponseInterface;
|
|
use Psr\Http\Message\ServerRequestInterface;
|
|
use Psr\Log\LoggerAwareInterface;
|
|
use Psr\Log\LoggerAwareTrait;
|
|
use TYPO3\CMS\Core\Authentication\Event\AfterUserLoggedOutEvent;
|
|
use TYPO3\CMS\Core\Authentication\Event\BeforeRequestTokenProcessedEvent;
|
|
use TYPO3\CMS\Core\Authentication\Event\BeforeUserLogoutEvent;
|
|
use TYPO3\CMS\Core\Authentication\Event\LoginAttemptFailedEvent;
|
|
use TYPO3\CMS\Core\Authentication\Mfa\MfaProviderRegistry;
|
|
use TYPO3\CMS\Core\Authentication\Mfa\MfaRequiredException;
|
|
use TYPO3\CMS\Core\Context\Context;
|
|
use TYPO3\CMS\Core\Context\SecurityAspect;
|
|
use TYPO3\CMS\Core\Core\Environment;
|
|
use TYPO3\CMS\Core\Crypto\HashService;
|
|
use TYPO3\CMS\Core\Database\Connection;
|
|
use TYPO3\CMS\Core\Database\ConnectionPool;
|
|
use TYPO3\CMS\Core\Database\Query\Restriction\DefaultRestrictionContainer;
|
|
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
|
|
use TYPO3\CMS\Core\Database\Query\Restriction\EndTimeRestriction;
|
|
use TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction;
|
|
use TYPO3\CMS\Core\Database\Query\Restriction\PageIdListRestriction;
|
|
use TYPO3\CMS\Core\Database\Query\Restriction\QueryRestrictionContainerInterface;
|
|
use TYPO3\CMS\Core\Database\Query\Restriction\RootLevelRestriction;
|
|
use TYPO3\CMS\Core\Database\Query\Restriction\StartTimeRestriction;
|
|
use TYPO3\CMS\Core\Exception;
|
|
use TYPO3\CMS\Core\Http\CookieHeaderTrait;
|
|
use TYPO3\CMS\Core\Http\NormalizedParams;
|
|
use TYPO3\CMS\Core\Http\SetCookieBehavior;
|
|
use TYPO3\CMS\Core\Http\SetCookieService;
|
|
use TYPO3\CMS\Core\Security\RequestToken;
|
|
use TYPO3\CMS\Core\Session\UserSession;
|
|
use TYPO3\CMS\Core\Session\UserSessionManager;
|
|
use TYPO3\CMS\Core\SysLog\Action\Login as SystemLogLoginAction;
|
|
use TYPO3\CMS\Core\SysLog\Error as SystemLogErrorClassification;
|
|
use TYPO3\CMS\Core\SysLog\Type as SystemLogType;
|
|
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
|
use TYPO3\CMS\Core\Utility\StringUtility;
|
|
|
|
/**
|
|
* Authentication of users in TYPO3
|
|
*
|
|
* This class is used to authenticate a login user.
|
|
* The class is used by both the frontend and backend.
|
|
* In both cases this class is a parent class to BackendUserAuthentication and FrontendUserAuthentication
|
|
*/
|
|
abstract class AbstractUserAuthentication implements LoggerAwareInterface
|
|
{
|
|
use LoggerAwareTrait;
|
|
use CookieHeaderTrait;
|
|
|
|
/**
|
|
* Session/Cookie name
|
|
* @var string
|
|
*/
|
|
public $name = '';
|
|
|
|
/**
|
|
* Table in database with user data
|
|
* @var string
|
|
*/
|
|
public $user_table = '';
|
|
|
|
/**
|
|
* Table in database with user groups
|
|
* @var string
|
|
*/
|
|
public $usergroup_table = '';
|
|
|
|
/**
|
|
* Column for login-name
|
|
* @var string
|
|
*/
|
|
public $username_column = '';
|
|
|
|
/**
|
|
* Column for password
|
|
* @var string
|
|
*/
|
|
public $userident_column = '';
|
|
|
|
/**
|
|
* Column for user-id
|
|
* @var string
|
|
*/
|
|
public $userid_column = '';
|
|
|
|
/**
|
|
* Column for user group information
|
|
* @var string
|
|
*/
|
|
public $usergroup_column = '';
|
|
|
|
/**
|
|
* Column name for last login timestamp
|
|
* @var string
|
|
* @internal
|
|
*/
|
|
protected $lastLogin_column = '';
|
|
|
|
/**
|
|
* Enable field columns of user table
|
|
* @var array
|
|
*/
|
|
public $enablecolumns = [
|
|
'rootLevel' => '',
|
|
// Boolean: If TRUE, 'AND pid=0' will be a part of the query...
|
|
'disabled' => '',
|
|
'starttime' => '',
|
|
'endtime' => '',
|
|
'deleted' => '',
|
|
];
|
|
|
|
/**
|
|
* Form field with login-name
|
|
* @var string
|
|
* @internal
|
|
*/
|
|
protected $formfield_uname = '';
|
|
|
|
/**
|
|
* Form field with password
|
|
* @var string
|
|
* @internal
|
|
*/
|
|
protected $formfield_uident = '';
|
|
|
|
/**
|
|
* Form field with status: *'login', 'logout'. If empty login is not verified.
|
|
* @var string
|
|
* @internal
|
|
*/
|
|
protected $formfield_status = '';
|
|
|
|
/**
|
|
* Decides if the writelog() function is called at login and logout
|
|
* @var bool
|
|
*/
|
|
public $writeStdLog = false;
|
|
|
|
/**
|
|
* Log failed login attempts
|
|
* @var bool
|
|
*/
|
|
public $writeAttemptLog = false;
|
|
|
|
/**
|
|
* If set, the user-record must be stored at the page defined by $checkPid_value
|
|
* @var bool
|
|
*/
|
|
public $checkPid = true;
|
|
|
|
/**
|
|
* The page id the user record must be stored at, can also hold a comma separated list of pids
|
|
* @var int|string|null
|
|
*/
|
|
public $checkPid_value = 0;
|
|
|
|
/**
|
|
* Will be set to TRUE if the login session is actually written during auth-check.
|
|
* @var bool
|
|
* @internal
|
|
*/
|
|
protected $loginSessionStarted = false;
|
|
|
|
/**
|
|
* @var array|null contains user- AND session-data from database (joined tables)
|
|
* @internal
|
|
*/
|
|
public $user;
|
|
|
|
/**
|
|
* This array will hold the groups that the user is a member of
|
|
*/
|
|
public array $userGroups = [];
|
|
|
|
/**
|
|
* Will prevent the setting of the session cookie
|
|
* @var bool
|
|
* @internal
|
|
*/
|
|
protected $dontSetCookie = false;
|
|
|
|
/**
|
|
* Login type, used for services.
|
|
* @var string
|
|
*/
|
|
public $loginType = '';
|
|
|
|
/**
|
|
* User Settings (= preferences)
|
|
*/
|
|
public array $uc = [];
|
|
|
|
protected ?UserSession $userSession = null;
|
|
|
|
protected UserSessionManager $userSessionManager;
|
|
|
|
/**
|
|
* If set, this cookie will be set to the response.
|
|
*/
|
|
protected SetCookieBehavior $setCookie = SetCookieBehavior::None;
|
|
|
|
/**
|
|
* Initialize some important variables
|
|
*
|
|
* @throws Exception
|
|
*/
|
|
public function __construct()
|
|
{
|
|
// Backend or frontend login - used for auth services
|
|
if (empty($this->loginType)) {
|
|
throw new Exception('No loginType defined, must be set explicitly by subclass', 1476045345);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Currently needed for various unit tests, until start() and checkAuthentication() methods
|
|
* are smaller and extracted from this class.
|
|
*
|
|
* @internal
|
|
*/
|
|
public function initializeUserSessionManager(?UserSessionManager $userSessionManager = null): void
|
|
{
|
|
$this->userSessionManager = $userSessionManager ?? UserSessionManager::create($this->loginType);
|
|
$this->createAnonymousSession();
|
|
}
|
|
|
|
/**
|
|
* Creates an anonymous user session.
|
|
* This method should be avoided, as it is only a workaround due to the ugly setup of this class
|
|
* and the authentication / logout behavior.
|
|
*/
|
|
public function createAnonymousSession(): void
|
|
{
|
|
if (!empty($this->userSessionManager)) {
|
|
$this->userSession = $this->userSessionManager->createAnonymousSession();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Starts a user session
|
|
* Typical configurations will:
|
|
* a) check if session cookie was set and if not, set one,
|
|
* b) check if a password/username was sent and if so, try to authenticate the user
|
|
* c) Lookup a session attached to a user and check timeout etc.
|
|
* d) Garbage collection, setting of no-cache headers.
|
|
* If a user is authenticated the database record of the user (array) will be set in the ->user internal variable.
|
|
*/
|
|
public function start(ServerRequestInterface $request)
|
|
{
|
|
$this->logger->debug('## Beginning of auth logging.');
|
|
|
|
// Make certain that NO user is set initially
|
|
$this->user = null;
|
|
|
|
if (!isset($this->userSessionManager)) {
|
|
$this->initializeUserSessionManager();
|
|
}
|
|
$this->userSession = $this->userSessionManager->createFromRequestOrAnonymous($request, $this->name);
|
|
|
|
// Load user session, check to see if anyone has submitted login-information and if so authenticate
|
|
// the user with the session. $this->user[uid] may be used to write log...
|
|
try {
|
|
$this->checkAuthentication($request);
|
|
} catch (MfaRequiredException $mfaRequiredException) {
|
|
// Ensure the cookie is still set to keep the user session available
|
|
if ($this->shallSetSessionCookie()) {
|
|
$this->setSessionCookie();
|
|
}
|
|
throw $mfaRequiredException;
|
|
}
|
|
if ($this->shallSetSessionCookie()) {
|
|
$this->setSessionCookie();
|
|
}
|
|
// Hook for alternative ways of filling the $this->user array
|
|
foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['postUserLookUp'] ?? [] as $funcName) {
|
|
$_params = [
|
|
'pObj' => $this,
|
|
];
|
|
GeneralUtility::callUserFunction($funcName, $_params, $this);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Used to apply a cookie to a PSR-7 Response.
|
|
*
|
|
* @todo: should go into a middleware?
|
|
* @internal
|
|
*/
|
|
public function appendCookieToResponse(ResponseInterface $response, ?NormalizedParams $normalizedParams = null): ResponseInterface
|
|
{
|
|
if ($this->setCookie === SetCookieBehavior::None) {
|
|
return $response;
|
|
}
|
|
if ($normalizedParams === null) {
|
|
$normalizedParams = NormalizedParams::createFromRequest($GLOBALS['TYPO3_REQUEST']);
|
|
}
|
|
$setCookieService = SetCookieService::create($this->name, $this->loginType);
|
|
if ($this->setCookie === SetCookieBehavior::Send) {
|
|
$cookieObject = $setCookieService->setSessionCookie($this->userSession, $normalizedParams);
|
|
if ($cookieObject) {
|
|
$response = $response->withAddedHeader('Set-Cookie', $cookieObject->__toString());
|
|
}
|
|
}
|
|
if ($this->setCookie === SetCookieBehavior::Remove) {
|
|
$cookieObject = $setCookieService->removeCookie($normalizedParams);
|
|
$response = $response->withAddedHeader('Set-Cookie', $cookieObject->__toString());
|
|
}
|
|
return $response;
|
|
}
|
|
|
|
/**
|
|
* Sets the setCookie directive to "Send", which will then result in appending
|
|
* a new cookie to the PSR-7 response, see appendCookieToResponse().
|
|
* In case this method is called, the cookie needs to be set later.
|
|
*/
|
|
protected function setSessionCookie()
|
|
{
|
|
$this->setCookie = SetCookieBehavior::Send;
|
|
}
|
|
|
|
/**
|
|
* Determines whether setting the session cookie is generally enabled,
|
|
* or the current session is a non-session cookie (FE permalogin).
|
|
*/
|
|
protected function shallSetSessionCookie(): bool
|
|
{
|
|
return !$this->dontSetCookie
|
|
|| SetCookieService::create($this->name, $this->loginType)->isRefreshTimeBasedCookie($this->userSession);
|
|
}
|
|
|
|
/**
|
|
* Determine whether a session cookie needs to be set (lifetime=0)
|
|
*
|
|
* @return bool
|
|
* @internal
|
|
*/
|
|
protected function isSetSessionCookie()
|
|
{
|
|
return SetCookieService::create($this->name, $this->loginType)->isSetSessionCookie($this->userSession);
|
|
}
|
|
|
|
/**
|
|
* Determine whether a non-session cookie needs to be set (lifetime>0)
|
|
*
|
|
* @return bool
|
|
* @internal
|
|
*/
|
|
protected function isRefreshTimeBasedCookie()
|
|
{
|
|
return SetCookieService::create($this->name, $this->loginType)->isRefreshTimeBasedCookie($this->userSession);
|
|
}
|
|
|
|
/**
|
|
* "auth" services configuration array from $GLOBALS['TYPO3_CONF_VARS']['SVCONF']['auth']
|
|
*/
|
|
protected function getAuthServiceConfiguration(): array
|
|
{
|
|
if (is_array($GLOBALS['TYPO3_CONF_VARS']['SVCONF']['auth']['setup'] ?? null)) {
|
|
return $GLOBALS['TYPO3_CONF_VARS']['SVCONF']['auth']['setup'];
|
|
}
|
|
return [];
|
|
}
|
|
|
|
/**
|
|
* Checks if a submission of username and password is present or use other authentication by auth services
|
|
*
|
|
* @throws MfaRequiredException
|
|
* @internal
|
|
*/
|
|
public function checkAuthentication(ServerRequestInterface $request)
|
|
{
|
|
$authConfiguration = $this->getAuthServiceConfiguration();
|
|
if (!empty($authConfiguration)) {
|
|
$this->logger->debug('Authentication Service Configuration found.', ['auth_configuration' => $authConfiguration]);
|
|
}
|
|
$userRecordCandidate = false;
|
|
// User is not authenticated by default
|
|
$authenticated = false;
|
|
// User want to login with passed login data (name/password)
|
|
$activeLogin = false;
|
|
$this->logger->debug('Login type: {type}', ['type' => $this->loginType]);
|
|
// Get Login/Logout data submitted by a form or params
|
|
$loginData = $this->getLoginFormData($request);
|
|
$this->logger->debug('Login data', $this->removeSensitiveLoginDataForLoggingInfo($loginData));
|
|
$type = LoginType::tryFrom($loginData['status'] ?? '');
|
|
// Active logout (eg. with "logout" button)
|
|
if ($type === LoginType::LOGOUT) {
|
|
if ($this->writeStdLog) {
|
|
$this->writelog(SystemLogType::LOGIN, SystemLogLoginAction::LOGOUT, SystemLogErrorClassification::MESSAGE, null, 'User %s logged out', [$this->user['username']], '', 0);
|
|
}
|
|
$this->logger->info('User logged out. Id: {session}', ['session' => sha1($this->userSession->getIdentifier())]);
|
|
$this->logoff();
|
|
}
|
|
// Determine whether we need to skip session update.
|
|
// This is used mainly for checking session timeout in advance without refreshing the current session's timeout.
|
|
$skipSessionUpdate = (bool)($request->getQueryParams()['skipSessionUpdate'] ?? false);
|
|
$isExistingSession = false;
|
|
$anonymousSession = false;
|
|
$authenticatedUserFromSession = null;
|
|
if (!$this->userSession->isNew()) {
|
|
// Read user data if this is bound to a user
|
|
// However, if the user data is not valid, or the session has timed out we'll recreate a new anonymous session
|
|
if ($this->userSession->getUserId() > 0) {
|
|
$authenticatedUserFromSession = $this->fetchValidUserFromSessionOrDestroySession($skipSessionUpdate);
|
|
}
|
|
$isExistingSession = !$this->userSession->isNew();
|
|
$anonymousSession = $isExistingSession && $this->userSession->isAnonymous();
|
|
}
|
|
|
|
// Active login (eg. with login form).
|
|
if ($type === LoginType::LOGIN) {
|
|
if (!$isExistingSession) {
|
|
$activeLogin = true;
|
|
$this->logger->debug('Active login (eg. with login form)');
|
|
// check referrer for submitted login values
|
|
if ($this->formfield_status && $loginData['uident'] && $loginData['uname']) {
|
|
// Delete old user session if any
|
|
$this->logoff();
|
|
}
|
|
// Refuse login for _CLI users, if not processing a CLI request type
|
|
// (although we shouldn't be here in case of a CLI request type)
|
|
if (stripos($loginData['uname'], '_CLI_') === 0 && !Environment::isCli()) {
|
|
throw new \RuntimeException('TYPO3 Fatal Error: You have tried to login using a CLI user. Access prohibited!', 1270853931);
|
|
}
|
|
}
|
|
// Cause elevation of privilege, make sure regenerateSessionId is called later on
|
|
// Note for further research: $anonymousSession actually implies having $isExistingSession = true
|
|
// allowing to further simplify this concern.
|
|
if ($anonymousSession) {
|
|
$activeLogin = true;
|
|
}
|
|
}
|
|
|
|
if ($isExistingSession && $authenticatedUserFromSession !== null) {
|
|
$this->logger->debug('User found in session', [
|
|
$this->userid_column => $authenticatedUserFromSession[$this->userid_column] ?? null,
|
|
$this->username_column => $authenticatedUserFromSession[$this->username_column] ?? null,
|
|
]);
|
|
} else {
|
|
$this->logger->debug('No user session found');
|
|
}
|
|
|
|
if ($activeLogin) {
|
|
$context = GeneralUtility::makeInstance(Context::class);
|
|
$securityAspect = SecurityAspect::provideIn($context);
|
|
$requestToken = $securityAspect->getReceivedRequestToken();
|
|
|
|
$event = new BeforeRequestTokenProcessedEvent($this, $request, $requestToken);
|
|
GeneralUtility::makeInstance(EventDispatcherInterface::class)->dispatch($event);
|
|
$requestToken = $event->getRequestToken();
|
|
|
|
$requestTokenScopeMatches = ($requestToken->scope ?? null) === 'core/user-auth/' . strtolower($this->loginType);
|
|
if (!$requestTokenScopeMatches) {
|
|
$this->logger->debug('Missing or invalid request token during login', ['requestToken' => $requestToken]);
|
|
// important: disable `$activeLogin` state
|
|
$activeLogin = false;
|
|
} elseif ($requestToken instanceof RequestToken && $requestToken->getSigningSecretIdentifier() !== null) {
|
|
$securityAspect->getSigningSecretResolver()->revokeIdentifier(
|
|
$requestToken->getSigningSecretIdentifier()
|
|
);
|
|
}
|
|
}
|
|
|
|
// Fetch users from the database (or somewhere else)
|
|
$possibleUsers = $this->fetchPossibleUsers($loginData, $activeLogin, $isExistingSession, $authenticatedUserFromSession, $request);
|
|
|
|
// If no new user was set we use the already found user session
|
|
if (empty($possibleUsers) && $isExistingSession && !$anonymousSession) {
|
|
// Check if the previous services returned a proper user
|
|
if (is_array($authenticatedUserFromSession)) {
|
|
$possibleUsers[] = $authenticatedUserFromSession;
|
|
$userRecordCandidate = $authenticatedUserFromSession;
|
|
// User is authenticated because we found a user session
|
|
$authenticated = true;
|
|
$this->logger->debug('User session used', [
|
|
$this->userid_column => $authenticatedUserFromSession[$this->userid_column] ?? '',
|
|
$this->username_column => $authenticatedUserFromSession[$this->username_column] ?? '',
|
|
]);
|
|
}
|
|
}
|
|
|
|
// Re-auth user when 'auth'-service option is set
|
|
if (!empty($authConfiguration[$this->loginType . '_alwaysAuthUser'])) {
|
|
$authenticated = false;
|
|
$this->logger->debug('alwaysAuthUser option is enabled');
|
|
}
|
|
// Authenticate the user if needed
|
|
if (!empty($possibleUsers) && !$authenticated) {
|
|
foreach ($possibleUsers as $userRecordCandidate) {
|
|
// Use 'auth' service to authenticate the user
|
|
// If one service returns FALSE then authentication failed
|
|
// a service might return 100 which means there's no reason to stop but the user can't be authenticated by that service
|
|
$this->logger->debug('Auth user', $this->removeSensitiveLoginDataForLoggingInfo($userRecordCandidate, true));
|
|
$subType = 'authUser' . $this->loginType;
|
|
|
|
/** @var AuthenticationService $serviceObj */
|
|
foreach ($this->getAuthServices($subType, $loginData, $authenticatedUserFromSession, $request) as $serviceObj) {
|
|
if (($ret = (int)$serviceObj->authUser($userRecordCandidate)) > 0) {
|
|
// If the service returns >=200 then no more checking is needed - useful for IP checking without password
|
|
if ($ret >= 200) {
|
|
$authenticated = true;
|
|
break;
|
|
}
|
|
if ($ret < 100) {
|
|
$authenticated = true;
|
|
}
|
|
// $ret is between 100 and 199 which means "I'm not responsible, ask others"
|
|
} else {
|
|
// $ret is < 0
|
|
$authenticated = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ($authenticated) {
|
|
// Leave foreach() because a user is authenticated
|
|
break;
|
|
}
|
|
}
|
|
// mimic user authentication to mitigate observable timing discrepancies
|
|
// @link https://cwe.mitre.org/data/definitions/208.html
|
|
} elseif ($activeLogin) {
|
|
$subType = 'authUser' . $this->loginType;
|
|
foreach ($this->getAuthServices($subType, $loginData, $authenticatedUserFromSession, $request) as $serviceObj) {
|
|
if ($serviceObj instanceof MimicServiceInterface && $serviceObj->mimicAuthUser() === false) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If user is authenticated, then a valid user is found in $userRecordCandidate
|
|
if ($authenticated) {
|
|
// Insert session record if needed
|
|
if (!$isExistingSession
|
|
|| $anonymousSession
|
|
|| (int)($userRecordCandidate[$this->userid_column] ?? 0) !== $this->userSession->getUserId()
|
|
) {
|
|
$sessionData = $this->userSession->getData();
|
|
// Create a new session with a fixated user
|
|
$this->userSession = $this->createUserSession($userRecordCandidate);
|
|
|
|
// Preserve session data on login
|
|
if ($anonymousSession || $isExistingSession) {
|
|
$this->userSession->overrideData($sessionData);
|
|
}
|
|
|
|
$this->user = array_merge($userRecordCandidate, $this->user ?? []);
|
|
|
|
// The login session is started.
|
|
$this->loginSessionStarted = true;
|
|
$this->logger->debug('User session finally read', [
|
|
$this->userid_column => $this->getUserId(),
|
|
$this->username_column => $this->getUserName(),
|
|
]);
|
|
} else {
|
|
// if we come here the current session is for sure not anonymous as this is a pre-condition for $authenticated = true
|
|
$this->user = $authenticatedUserFromSession;
|
|
}
|
|
|
|
if ($activeLogin && !$this->userSession->isNew()) {
|
|
$this->regenerateSessionId();
|
|
}
|
|
|
|
// Since the user is not fully authenticated we need to unpack UC here to be
|
|
// able to retrieve a possible defined default (preferred) MFA provider.
|
|
$this->unpack_uc();
|
|
|
|
if ($activeLogin) {
|
|
// User logged in - write that to the log!
|
|
if ($this->writeStdLog) {
|
|
$this->writelog(SystemLogType::LOGIN, SystemLogLoginAction::LOGIN, SystemLogErrorClassification::MESSAGE, null, 'User %s logged in from ###IP###', [$userRecordCandidate[$this->username_column]], '', '');
|
|
}
|
|
$this->logger->info('User {username} logged in from {ip}', [
|
|
'username' => $userRecordCandidate[$this->username_column],
|
|
'ip' => $request->getAttribute('normalizedParams')->getRemoteAddress(),
|
|
]);
|
|
} else {
|
|
$this->logger->debug('User {username} authenticated from {ip}', [
|
|
'username' => $userRecordCandidate[$this->username_column],
|
|
'ip' => $request->getAttribute('normalizedParams')->getRemoteAddress(),
|
|
]);
|
|
}
|
|
// Check if multi-factor authentication is required
|
|
$this->evaluateMfaRequirements();
|
|
} else {
|
|
// Mark the current login attempt as failed
|
|
if (empty($possibleUsers) && $activeLogin) {
|
|
$this->logger->debug('Login failed', [
|
|
'loginData' => $this->removeSensitiveLoginDataForLoggingInfo($loginData),
|
|
]);
|
|
} elseif (!empty($possibleUsers)) {
|
|
$this->logger->debug('Login failed', [
|
|
$this->userid_column => $userRecordCandidate[$this->userid_column],
|
|
$this->username_column => $userRecordCandidate[$this->username_column],
|
|
]);
|
|
}
|
|
|
|
// If there were a login failure, check to see if a warning email should be sent
|
|
if ($activeLogin) {
|
|
GeneralUtility::makeInstance(EventDispatcherInterface::class)->dispatch(
|
|
new LoginAttemptFailedEvent($this, $request, $this->removeSensitiveLoginDataForLoggingInfo($loginData))
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Loads users from various sources (= authentication services) as an array of arrays.
|
|
*
|
|
* @param array|null $authenticatedUserFromSession if we have a user from an existing session, this is set here, otherwise null
|
|
*/
|
|
protected function fetchPossibleUsers(array $loginData, bool $activeLogin, bool $isExistingSession, ?array $authenticatedUserFromSession, ServerRequestInterface $request): array
|
|
{
|
|
$possibleUsers = [];
|
|
$authConfiguration = $this->getAuthServiceConfiguration();
|
|
$alwaysFetchUsers = !empty($authConfiguration[$this->loginType . '_alwaysFetchUser']);
|
|
$fetchUsersIfNoSessionIsGiven = !empty($authConfiguration[$this->loginType . '_fetchUserIfNoSession']);
|
|
if (
|
|
$activeLogin
|
|
|| $alwaysFetchUsers
|
|
|| (!$isExistingSession && $fetchUsersIfNoSessionIsGiven)
|
|
) {
|
|
// Use 'auth' service to find the user
|
|
// First found user will be used
|
|
$subType = 'getUser' . $this->loginType;
|
|
/** @var AuthenticationService $serviceObj */
|
|
foreach ($this->getAuthServices($subType, $loginData, $authenticatedUserFromSession, $request) as $serviceObj) {
|
|
$row = $serviceObj->getUser();
|
|
if (is_array($row)) {
|
|
$possibleUsers[] = $row;
|
|
$this->logger->debug('User found', [
|
|
$this->userid_column => $row[$this->userid_column],
|
|
$this->username_column => $row[$this->username_column],
|
|
]);
|
|
// User found, just stop to search for more if not configured to go on
|
|
if (empty($authConfiguration[$this->loginType . '_fetchAllUsers'])) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($alwaysFetchUsers) {
|
|
$this->logger->debug($this->loginType . '_alwaysFetchUser option is enabled');
|
|
}
|
|
if (empty($possibleUsers)) {
|
|
$this->logger->debug('No user found by services');
|
|
} else {
|
|
$this->logger->debug('{count} user records found by services', ['count' => count($possibleUsers)]);
|
|
}
|
|
}
|
|
return $possibleUsers;
|
|
}
|
|
|
|
/**
|
|
* This method checks if the user is authenticated but has not succeeded in
|
|
* passing his MFA challenge. This method can therefore only be used if a user
|
|
* has been authenticated against his first authentication method (username+password
|
|
* or any other authentication token).
|
|
*
|
|
* @throws MfaRequiredException
|
|
* @internal
|
|
*/
|
|
protected function evaluateMfaRequirements(): void
|
|
{
|
|
// MFA has been validated already, nothing to do
|
|
if ($this->getSessionData('mfa')) {
|
|
return;
|
|
}
|
|
// If the user session does not contain the 'mfa' key - indicating that MFA is already
|
|
// passed - get the first provider for authentication, which is either the default provider
|
|
// or the first active provider (based on the providers configured ordering).
|
|
$provider = GeneralUtility::makeInstance(MfaProviderRegistry::class)->getFirstAuthenticationAwareProvider($this);
|
|
// Throw an exception (hopefully caught in a middleware) when an active provider for the user exists
|
|
if ($provider !== null) {
|
|
throw new MfaRequiredException($provider, 1613687097);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Whether the user is required to set up MFA
|
|
*
|
|
* @internal
|
|
*/
|
|
public function isMfaSetupRequired(): bool
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Initializes authentication services to be used in a foreach loop
|
|
*
|
|
* @param string $subType e.g. getUserFE
|
|
* @param array|null $authenticatedUserFromSession the user which was loaded from the session, or null if none was found
|
|
* @return \Traversable A generator of service objects
|
|
*/
|
|
protected function getAuthServices(string $subType, array $loginData, ?array $authenticatedUserFromSession, ServerRequestInterface $request): \Traversable
|
|
{
|
|
$serviceChain = [];
|
|
// The info array provide additional information for auth services
|
|
$authInfo = $this->getAuthInfoArray($request);
|
|
if ($authenticatedUserFromSession !== null) {
|
|
$authInfo['user'] = $authenticatedUserFromSession;
|
|
}
|
|
while (is_object($serviceObj = GeneralUtility::makeInstanceService('auth', $subType, $serviceChain))) {
|
|
$serviceChain[] = $serviceObj->getServiceKey();
|
|
$serviceObj->initAuth($subType, $loginData, $authInfo, $this);
|
|
yield $serviceObj;
|
|
}
|
|
if (!empty($serviceChain)) {
|
|
$this->logger->debug('{subtype} auth services called: {chain}', [
|
|
'subtype' => $subType,
|
|
'chain' => implode(',', $serviceChain),
|
|
]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Regenerate the session ID and transfer the session to new ID
|
|
* Call this method whenever a user proceeds to a higher authorization level
|
|
* e.g. when an anonymous session is now authenticated.
|
|
*/
|
|
protected function regenerateSessionId()
|
|
{
|
|
$this->userSession = $this->userSessionManager->regenerateSession($this->userSession->getIdentifier());
|
|
}
|
|
|
|
/*************************
|
|
*
|
|
* User Sessions
|
|
*
|
|
*************************/
|
|
|
|
/**
|
|
* Creates a user session record and returns its values.
|
|
*
|
|
* @param array $userRecordCandidate User data array
|
|
* @return UserSession The session data for the newly created session.
|
|
*/
|
|
public function createUserSession(array $userRecordCandidate): UserSession
|
|
{
|
|
// Needed for testing framework
|
|
if (!isset($this->userSessionManager)) {
|
|
$this->initializeUserSessionManager();
|
|
}
|
|
$userRecordCandidateId = (int)($userRecordCandidate[$this->userid_column] ?? 0);
|
|
$session = $this->userSessionManager->elevateToFixatedUserSession($this->userSession, $userRecordCandidateId);
|
|
// Updating lastLogin_column carrying information about last login.
|
|
$this->updateLoginTimestamp($userRecordCandidateId);
|
|
return $session;
|
|
}
|
|
|
|
/**
|
|
* Updates the last login column in the user with the given id
|
|
*/
|
|
protected function updateLoginTimestamp(int $userId)
|
|
{
|
|
if ($this->lastLogin_column) {
|
|
$connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->user_table);
|
|
$connection->update(
|
|
$this->user_table,
|
|
[$this->lastLogin_column => $GLOBALS['EXEC_TIME']],
|
|
[$this->userid_column => $userId]
|
|
);
|
|
$this->user[$this->lastLogin_column] = $GLOBALS['EXEC_TIME'];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* If the session is bound to a user, this method fetches the user record, and returns it.
|
|
* If the session has a timeout, the session date is extended if needed. Also the ìs_online
|
|
* flag is updated for the user.
|
|
*
|
|
* However, if the session has expired the session is removed and the request is treated as an anonymous session.
|
|
*
|
|
* @param bool $skipSessionUpdate
|
|
*/
|
|
protected function fetchValidUserFromSessionOrDestroySession(bool $skipSessionUpdate = false): ?array
|
|
{
|
|
if ($this->userSession->isAnonymous()) {
|
|
return null;
|
|
}
|
|
// Fetch the user from the DB
|
|
$userRecord = $this->getRawUserByUid($this->userSession->getUserId() ?? 0);
|
|
if ($userRecord) {
|
|
// A user was found
|
|
$userRecord['is_online'] = $this->userSession->getLastUpdated();
|
|
if (!$this->userSessionManager->hasExpired($this->userSession)) {
|
|
if (!$skipSessionUpdate) {
|
|
$this->userSession = $this->userSessionManager->updateSessionTimestamp($this->userSession);
|
|
}
|
|
} else {
|
|
// Delete any user set...
|
|
$this->logoff();
|
|
$userRecord = false;
|
|
$this->createAnonymousSession();
|
|
}
|
|
}
|
|
return is_array($userRecord) ? $userRecord : null;
|
|
}
|
|
|
|
/**
|
|
* Regenerates the session ID and sets the cookie again.
|
|
*
|
|
* @internal
|
|
*/
|
|
public function enforceNewSessionId()
|
|
{
|
|
$this->regenerateSessionId();
|
|
$this->setSessionCookie();
|
|
}
|
|
|
|
/**
|
|
* Log out current user!
|
|
* Removes the current session record, sets the internal ->user array to a blank string;
|
|
* Thereby the current user (if any) is effectively logged out!
|
|
*/
|
|
public function logoff()
|
|
{
|
|
$this->logger->debug('logoff: ses_id = {session}', ['session' => sha1($this->userSession->getIdentifier())]);
|
|
|
|
$dispatcher = GeneralUtility::makeInstance(EventDispatcherInterface::class);
|
|
|
|
$event = new BeforeUserLogoutEvent($this, $this->userSession);
|
|
$event = $dispatcher->dispatch($event);
|
|
|
|
if ($event->shouldLogout()) {
|
|
$this->performLogoff();
|
|
}
|
|
|
|
$dispatcher->dispatch(new AfterUserLoggedOutEvent($this));
|
|
}
|
|
|
|
/**
|
|
* Perform the logoff action. Called from logoff() as a way to allow subclasses to override
|
|
* what happens when a user logs off, without needing to reproduce the hook calls and logging
|
|
* that happens in the public logoff() API method.
|
|
*/
|
|
protected function performLogoff()
|
|
{
|
|
if ($this->userSession) {
|
|
$this->userSessionManager->removeSession($this->userSession);
|
|
}
|
|
$this->createAnonymousSession();
|
|
$this->user = null;
|
|
if ($this->isCookieSet()) {
|
|
$this->removeCookie();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Empty / unset the cookie
|
|
*
|
|
* @param string|null $cookieName usually, this is $this->name
|
|
* @internal
|
|
*/
|
|
public function removeCookie($cookieName = null)
|
|
{
|
|
$this->setCookie = SetCookieBehavior::Remove;
|
|
}
|
|
|
|
/**
|
|
* Returns whether this request is going to set a cookie
|
|
* or a cookie was already found in the system
|
|
*
|
|
* @return bool Returns TRUE if a cookie is set
|
|
* @internal
|
|
*/
|
|
protected function isCookieSet()
|
|
{
|
|
return SetCookieService::create($this->name, $this->loginType)->isCookieSet(
|
|
$GLOBALS['TYPO3_REQUEST'] ?? null,
|
|
$this->userSession
|
|
);
|
|
}
|
|
|
|
/*************************
|
|
*
|
|
* SQL Functions
|
|
*
|
|
*************************/
|
|
/**
|
|
* This returns the restrictions needed to select the user respecting
|
|
* enable columns and flags like deleted, hidden, starttime, endtime
|
|
* and rootLevel
|
|
*
|
|
* @internal
|
|
*/
|
|
protected function userConstraints(): QueryRestrictionContainerInterface
|
|
{
|
|
$restrictionContainer = GeneralUtility::makeInstance(DefaultRestrictionContainer::class);
|
|
|
|
if (empty($this->enablecolumns['disabled'])) {
|
|
$restrictionContainer->removeByType(HiddenRestriction::class);
|
|
}
|
|
|
|
if (empty($this->enablecolumns['deleted'])) {
|
|
$restrictionContainer->removeByType(DeletedRestriction::class);
|
|
}
|
|
|
|
if (empty($this->enablecolumns['starttime'])) {
|
|
$restrictionContainer->removeByType(StartTimeRestriction::class);
|
|
}
|
|
|
|
if (empty($this->enablecolumns['endtime'])) {
|
|
$restrictionContainer->removeByType(EndTimeRestriction::class);
|
|
}
|
|
|
|
if (!empty($this->enablecolumns['rootLevel'])) {
|
|
$restrictionContainer->add(GeneralUtility::makeInstance(RootLevelRestriction::class, [$this->user_table]));
|
|
}
|
|
|
|
if ($this->checkPid && $this->checkPid_value !== null && $this->checkPid_value !== '') {
|
|
$restrictionContainer->add(
|
|
GeneralUtility::makeInstance(
|
|
PageIdListRestriction::class,
|
|
[$this->user_table],
|
|
GeneralUtility::intExplode(',', (string)$this->checkPid_value, true)
|
|
)
|
|
);
|
|
}
|
|
|
|
return $restrictionContainer;
|
|
}
|
|
|
|
/*************************
|
|
*
|
|
* Session and Configuration Handling
|
|
*
|
|
*************************/
|
|
/**
|
|
* This writes $this->>uc to the user-record. This is a way of providing session-data.
|
|
* You can fetch the data again through $this->uc in this class!
|
|
*/
|
|
public function writeUC()
|
|
{
|
|
$userId = $this->getUserId();
|
|
if ($userId) {
|
|
$this->logger->debug('writeUC: {userid_column}={value}', [
|
|
'userid_column' => $this->userid_column,
|
|
'value' => $userId,
|
|
]);
|
|
GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->user_table)->update(
|
|
$this->user_table,
|
|
['uc' => serialize($this->uc)],
|
|
[$this->userid_column => $userId],
|
|
['uc' => Connection::PARAM_LOB]
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Unserializes the user configuration from the user record into $this->>uc
|
|
* @internal
|
|
*/
|
|
protected function unpack_uc()
|
|
{
|
|
if (isset($this->user['uc'])) {
|
|
$theUC = unserialize($this->user['uc'], ['allowed_classes' => false]);
|
|
if (is_array($theUC)) {
|
|
$this->uc = $theUC;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Stores data for a module.
|
|
* The data is stored with the session ID, so you can even check upon retrieval
|
|
* if the module data is from a previous session or from the current session.
|
|
*
|
|
* @param string $module Is the identifier of the module, e.g. "content_status"
|
|
* @param mixed $data Is the data you want to store for that module (array, string, ...)
|
|
* @param bool $dontPersistImmediately If set, then the ->uc array (which carries all kinds of user data) is NOT written immediately, but must be written by some subsequent call.
|
|
*/
|
|
public function pushModuleData(string $module, mixed $data, bool $dontPersistImmediately = false): void
|
|
{
|
|
$hashService = GeneralUtility::makeInstance(HashService::class);
|
|
$sessionHash = $hashService->hmac(
|
|
$this->userSession->getIdentifier(),
|
|
'core-session-hash'
|
|
);
|
|
$this->uc['moduleData'][$module] = $data;
|
|
$this->uc['moduleSessionID'][$module] = $sessionHash;
|
|
if ($dontPersistImmediately === false) {
|
|
$this->writeUC();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets module data for a module (from a loaded ->uc array)
|
|
*
|
|
* @param string $module Is the identifier of the module, e.g. "content_status"
|
|
* @param string $type If $type = 'ses' then module data is returned only if it was stored in the current session, otherwise data from a previous session will be returned (if available).
|
|
* @return mixed The module data if available: $this->uc['moduleData'][$module];
|
|
*/
|
|
public function getModuleData(string $module, string $type = ''): mixed
|
|
{
|
|
$hashService = GeneralUtility::makeInstance(HashService::class);
|
|
$sessionHash = $hashService->hmac(
|
|
$this->userSession->getIdentifier(),
|
|
'core-session-hash'
|
|
);
|
|
$sessionData = $this->uc['moduleData'][$module] ?? null;
|
|
$moduleSessionIdHash = $this->uc['moduleSessionID'][$module] ?? null;
|
|
if ($type !== 'ses' || ($sessionData !== null && $moduleSessionIdHash === $sessionHash)) {
|
|
return $sessionData;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Returns the session data stored for $key.
|
|
* The data will last only for this login session since it is stored in the user session.
|
|
*
|
|
* @param string $key The key associated with the session data
|
|
* @return mixed
|
|
*/
|
|
public function getSessionData($key)
|
|
{
|
|
return $this->userSession ? $this->userSession->get($key) : '';
|
|
}
|
|
|
|
/**
|
|
* Set session data by key.
|
|
* The data will last only for this login session since it is stored in the user session.
|
|
*
|
|
* @param string $key A non empty string to store the data under
|
|
* @param mixed $data Data store store in session
|
|
*/
|
|
public function setSessionData($key, $data)
|
|
{
|
|
$this->userSession->set($key, $data);
|
|
}
|
|
|
|
/**
|
|
* Sets the session data ($data) for $key and writes all session data (from ->user['ses_data']) to the database.
|
|
* The data will last only for this login session since it is stored in the session table.
|
|
*
|
|
* @param string $key Pointer to an associative key in the session data array which is stored serialized in the field "ses_data" of the session table.
|
|
* @param mixed $data The data to store in index $key
|
|
*/
|
|
public function setAndSaveSessionData($key, $data)
|
|
{
|
|
$this->userSession->set($key, $data);
|
|
$this->logger->debug('setAndSaveSessionData: ses_id = {session}', ['session' => sha1($this->userSession->getIdentifier())]);
|
|
$this->userSession = $this->userSessionManager->updateSession($this->userSession);
|
|
}
|
|
|
|
/*************************
|
|
*
|
|
* Misc
|
|
*
|
|
*************************/
|
|
/**
|
|
* Returns an info array with Login/Logout data submitted by a form or params
|
|
*
|
|
* @return array
|
|
* @internal
|
|
*/
|
|
public function getLoginFormData(ServerRequestInterface $request)
|
|
{
|
|
$parsedBody = $request->getParsedBody();
|
|
$queryParams = $request->getQueryParams();
|
|
$loginData = [
|
|
'status' => StringUtility::filter($parsedBody[$this->formfield_status] ?? $queryParams[$this->formfield_status] ?? null),
|
|
'uname' => StringUtility::filter($parsedBody[$this->formfield_uname] ?? '', ''),
|
|
'uident' => StringUtility::filter($parsedBody[$this->formfield_uident] ?? '', ''),
|
|
];
|
|
// Only process the login data if a login is requested
|
|
if (LoginType::tryFrom($loginData['status'] ?? '') === LoginType::LOGIN) {
|
|
$loginData = $this->processLoginData($loginData, $request);
|
|
}
|
|
return $loginData;
|
|
}
|
|
|
|
public function isActiveLogin(ServerRequestInterface $request): bool
|
|
{
|
|
$status = $request->getParsedBody()[$this->formfield_status] ?? $request->getQueryParams()[$this->formfield_status] ?? '';
|
|
return LoginType::tryFrom($status) === LoginType::LOGIN;
|
|
}
|
|
|
|
/**
|
|
* Processes Login data submitted by a form or params
|
|
*
|
|
* @param array $loginData Login data array
|
|
* @param ServerRequestInterface $request
|
|
* @return array
|
|
* @internal
|
|
*/
|
|
public function processLoginData(array $loginData, ServerRequestInterface $request): array
|
|
{
|
|
$this->logger->debug('Login data before processing', $this->removeSensitiveLoginDataForLoggingInfo($loginData));
|
|
$subType = 'processLoginData' . $this->loginType;
|
|
$isLoginDataProcessed = false;
|
|
$processedLoginData = $loginData;
|
|
/** @var AuthenticationService $serviceObject */
|
|
foreach ($this->getAuthServices($subType, $loginData, null, $request) as $serviceObject) {
|
|
$serviceResult = $serviceObject->processLoginData($processedLoginData);
|
|
if (!empty($serviceResult)) {
|
|
$isLoginDataProcessed = true;
|
|
// If the service returns >=200 then no more processing is needed
|
|
if ((int)$serviceResult >= 200) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if ($isLoginDataProcessed) {
|
|
$loginData = $processedLoginData;
|
|
$this->logger->debug('Processed login data', $this->removeSensitiveLoginDataForLoggingInfo($processedLoginData));
|
|
}
|
|
return $loginData;
|
|
}
|
|
|
|
/**
|
|
* Removes any sensitive data from the incoming data (either from loginData, processedLogin data
|
|
* or the user record from the DB).
|
|
*
|
|
* No type hinting is added because it might be possible that the incoming data is of any other type.
|
|
*
|
|
* @param mixed|array $data
|
|
* @param bool $isUserRecord
|
|
* @return mixed
|
|
*/
|
|
protected function removeSensitiveLoginDataForLoggingInfo($data, bool $isUserRecord = false)
|
|
{
|
|
if ($isUserRecord && is_array($data)) {
|
|
$fieldNames = ['uid', 'pid', 'tstamp', 'crdate', 'deleted', 'disabled', 'starttime', 'endtime', 'username', 'admin', 'usergroup', 'db_mountpoints', 'file_mountpoints', 'file_permissions', 'workspace_perms', 'lastlogin', 'workspace_id', 'category_perms'];
|
|
$data = array_intersect_key($data, array_combine($fieldNames, $fieldNames));
|
|
}
|
|
if (isset($data['uident'])) {
|
|
$data['uident'] = '********';
|
|
}
|
|
if (isset($data['uident_text'])) {
|
|
$data['uident_text'] = '********';
|
|
}
|
|
if (isset($data['password'])) {
|
|
$data['password'] = '********';
|
|
}
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* Returns an info array which provides additional information for auth services
|
|
*
|
|
* @return array
|
|
* @internal
|
|
*/
|
|
public function getAuthInfoArray(ServerRequestInterface $request)
|
|
{
|
|
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->user_table);
|
|
$expressionBuilder = $queryBuilder->expr();
|
|
$authInfo = [];
|
|
$authInfo['loginType'] = $this->loginType;
|
|
$authInfo['request'] = $request;
|
|
$normalizedParams = $request->getAttribute('normalizedParams');
|
|
$authInfo['refInfo'] = $normalizedParams ? parse_url($normalizedParams->getHttpReferer()) : null;
|
|
$authInfo['HTTP_HOST'] = $normalizedParams?->getHttpHost();
|
|
$authInfo['REMOTE_ADDR'] = $normalizedParams?->getRemoteAddress();
|
|
$authInfo['REMOTE_HOST'] = $normalizedParams?->getRemoteHost();
|
|
// Can be overridden in localconf by SVCONF:
|
|
$authInfo['db_user']['table'] = $this->user_table;
|
|
$authInfo['db_user']['userid_column'] = $this->userid_column;
|
|
$authInfo['db_user']['username_column'] = $this->username_column;
|
|
$authInfo['db_user']['userident_column'] = $this->userident_column;
|
|
$authInfo['db_user']['enable_clause'] = $this->userConstraints()->buildExpression(
|
|
[$this->user_table => $this->user_table],
|
|
$expressionBuilder
|
|
);
|
|
return $authInfo;
|
|
}
|
|
|
|
/**
|
|
* DUMMY: Writes to log database table (in some extension classes)
|
|
*
|
|
* @param int $type denotes which module that has submitted the entry. This is the current list: 1=tce_db; 2=tce_file; 3=system (eg. sys_history save); 4=modules; 254=Personal settings changed; 255=login / out action: 1=login, 2=logout, 3=failed login (+ errorcode 3), 4=failure_warning_email sent
|
|
* @param int $action denotes which specific operation that wrote the entry (eg. 'delete', 'upload', 'update' and so on...). Specific for each $type. Also used to trigger update of the interface. (see the log-module for the meaning of each number !!)
|
|
* @param int $error flag. 0 = message, 1 = error (user problem), 2 = System Error (which should not happen), 3 = security notice (admin)
|
|
* @param null $_ unused
|
|
* @param string $details Default text that follows the message
|
|
* @param array $data Data that follows the log. Might be used to carry special information. If an array the first 5 entries (0-4) will be sprintf'ed the details-text...
|
|
* @param string $tablename Special field used by tce_main.php. These ($tablename, $recuid) hold the reference to the record which the log-entry is about.
|
|
* @param int|string $recuid Special field used by tce_main.php. These ($tablename, $recuid) hold the reference to the record which the log-entry is about.
|
|
*/
|
|
public function writelog($type, $action, $error, $_, $details, $data, $tablename, $recuid) {}
|
|
|
|
/**
|
|
* Raw initialization of the be_user with uid=$uid
|
|
* This will circumvent all login procedures and select a be_users record from the
|
|
* database and set the content of ->user to the record selected.
|
|
* Thus the BE_USER object will appear like if a user was authenticated - however without
|
|
* a session id and the fields from the session table of course.
|
|
* Will check the users for disabled, start/endtime, etc. ($this->user_where_clause())
|
|
*
|
|
* @param int $uid The UID of the backend user to set in ->user
|
|
* @internal
|
|
*/
|
|
public function setBeUserByUid($uid)
|
|
{
|
|
$this->user = $this->getRawUserByUid($uid);
|
|
}
|
|
|
|
/**
|
|
* Raw initialization of the be_user with username=$name
|
|
*
|
|
* @param string $name The username to look up.
|
|
* @see \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication::setBeUserByUid()
|
|
* @internal
|
|
*/
|
|
public function setBeUserByName($name)
|
|
{
|
|
$this->user = $this->getRawUserByName($name) ?: null;
|
|
}
|
|
|
|
/**
|
|
* Fetching raw user record with uid=$uid
|
|
*
|
|
* @param int $uid The UID of the backend user to set in ->user
|
|
* @return array user record or FALSE
|
|
* @internal
|
|
*/
|
|
public function getRawUserByUid($uid)
|
|
{
|
|
$query = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->user_table);
|
|
$query->setRestrictions($this->userConstraints());
|
|
$query->select('*')
|
|
->from($this->user_table)
|
|
->where($query->expr()->eq($this->userid_column, $query->createNamedParameter($uid, Connection::PARAM_INT)));
|
|
|
|
return $query->executeQuery()->fetchAssociative();
|
|
}
|
|
|
|
/**
|
|
* Fetching raw user record with username=$name
|
|
*
|
|
* @param string $name The username to look up.
|
|
* @return array user record or FALSE
|
|
* @see \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication::getUserByUid()
|
|
* @internal
|
|
*/
|
|
public function getRawUserByName($name)
|
|
{
|
|
$query = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->user_table);
|
|
$query->setRestrictions($this->userConstraints());
|
|
$query->select('*')
|
|
->from($this->user_table)
|
|
->where($query->expr()->eq($this->username_column, $query->createNamedParameter($name)));
|
|
|
|
return $query->executeQuery()->fetchAssociative();
|
|
}
|
|
|
|
public function getUserId(): ?int
|
|
{
|
|
if (isset($this->user[$this->userid_column])) {
|
|
return (int)$this->user[$this->userid_column];
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public function getUserName(): ?string
|
|
{
|
|
if (isset($this->user[$this->username_column])) {
|
|
return (string)$this->user[$this->username_column];
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public function getSession(): UserSession
|
|
{
|
|
return $this->userSession;
|
|
}
|
|
}
|