‪TYPO3CMS  11.5
FailedLoginAttemptNotification.php
Go to the documentation of this file.
1 <?php
2 
3 declare(strict_types=1);
4 
5 /*
6  * This file is part of the TYPO3 CMS project.
7  *
8  * It is free software; you can redistribute it and/or modify it under
9  * the terms of the GNU General Public License, either version 2
10  * of the License, or any later version.
11  *
12  * For the full copyright and license information, please read the
13  * LICENSE.txt file that was distributed with this source code.
14  *
15  * The TYPO3 project - inspiring people to share!
16  */
17 
19 
20 use Psr\Http\Message\ServerRequestInterface;
21 use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
26 use TYPO3\CMS\Core\Database\Query\QueryBuilder;
30 use ‪TYPO3\CMS\Core\SysLog\Action\Login as SystemLogLoginAction;
31 use ‪TYPO3\CMS\Core\SysLog\Error as SystemLogErrorClassification;
32 use ‪TYPO3\CMS\Core\SysLog\Type as SystemLogType;
34 
45 {
46  use ‪LogDataTrait;
47 
53 
59  protected ‪$warningPeriod;
60 
66 
68  {
69  $this->notificationRecipientEmailAddress = ‪$notificationRecipientEmailAddress ?? (string)‪$GLOBALS['TYPO3_CONF_VARS']['BE']['warning_email_addr'];
70  $this->warningPeriod = ‪$warningPeriod;
71  $this->failedLoginAttemptsThreshold = ‪$failedLoginAttemptsThreshold;
72  }
73 
84  public function ‪sendEmailOnLoginFailures(array $params, ‪AbstractUserAuthentication $user): bool
85  {
86  if (!($user instanceof ‪BackendUserAuthentication)) {
87  // This notification only works for backend users
88  return true;
89  }
90  if (!GeneralUtility::validEmail($this->notificationRecipientEmailAddress)) {
91  return true;
92  }
93 
94  $earliestTimeToCheckForFailures = ‪$GLOBALS['EXEC_TIME'] - ‪$this->warningPeriod;
95  $loginFailures = $this->‪getLoginFailures($earliestTimeToCheckForFailures);
96  // Check for more than a maximum number of login failures with the last period
97  if (count($loginFailures) > $this->failedLoginAttemptsThreshold) {
98  // OK, so there were more than the max allowed number of login failures - so we will send an email then.
99  $this->‪sendLoginAttemptEmail($loginFailures);
100  // Login failure attempt written to log, which will be picked up later-on again
101  $user->‪writelog(
102  SystemLogType::LOGIN,
103  SystemLogLoginAction::SEND_FAILURE_WARNING_EMAIL,
104  SystemLogErrorClassification::MESSAGE,
105  3,
106  'Failure warning (%s failures within %s seconds) sent by email to %s',
107  [count($loginFailures), $this->warningPeriod, $this->notificationRecipientEmailAddress]
108  );
109  }
110  return true;
111  }
112 
119  protected function ‪getLoginFailures(int $earliestTimeToCheckForFailures): array
120  {
121  // Get last flag set in the log for sending an email
122  // If a notification was e.g. sent 20mins ago, only check the entries of the last 20 minutes
123  $queryBuilder = $this->‪createPreparedQueryBuilder($earliestTimeToCheckForFailures, SystemLogLoginAction::SEND_FAILURE_WARNING_EMAIL);
124  $statement = $queryBuilder
125  ->select('tstamp')
126  ->orderBy('tstamp', 'DESC')
127  ->setMaxResults(1)
128  ->executeQuery();
129  if ($lastTimeANotificationWasSent = $statement->fetchOne()) {
130  $earliestTimeToCheckForFailures = (int)$lastTimeANotificationWasSent;
131  }
132  $queryBuilder = $this->‪createPreparedQueryBuilder($earliestTimeToCheckForFailures, SystemLogLoginAction::ATTEMPT);
133  $previousFailures = $queryBuilder
134  ->select('*')
135  ->orderBy('tstamp')
136  ->executeQuery()
137  ->fetchAllAssociative();
138  return is_array($previousFailures) ? $previousFailures : [];
139  }
140 
146  protected function ‪sendLoginAttemptEmail(array $previousFailures): void
147  {
148  $emailData = [];
149  foreach ($previousFailures as $row) {
150  $text = $this->‪formatLogDetails($row['details'] ?? '', $row['log_data'] ?? '');
151  if ((int)$row['type'] === SystemLogType::LOGIN) {
152  $text = str_replace('###IP###', $row['IP'], $text);
153  }
154  $emailData[] = [
155  'row' => $row,
156  'text' => $text,
157  ];
158  }
159  $email = GeneralUtility::makeInstance(FluidEmail::class)
160  ->to($this->notificationRecipientEmailAddress)
161  ->setTemplate('Security/LoginAttemptFailedWarning')
162  ->assign('lines', $emailData);
163  if ((‪$GLOBALS['TYPO3_REQUEST'] ?? null) instanceof ServerRequestInterface) {
164  $email->setRequest(‪$GLOBALS['TYPO3_REQUEST']);
165  }
166 
167  try {
168  GeneralUtility::makeInstance(Mailer::class)->send($email);
169  } catch (TransportExceptionInterface $e) {
170  // Sending mail failed. Probably broken smtp setup.
171  // @todo: Maybe log that sending mail failed.
172  }
173  }
174 
180  protected function ‪createPreparedQueryBuilder(int $earliestLogDate, int $loginAction): QueryBuilder
181  {
182  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
183  ->getQueryBuilderForTable('sys_log');
184  $queryBuilder
185  ->from('sys_log')
186  ->where(
187  $queryBuilder->expr()->eq(
188  'type',
189  $queryBuilder->createNamedParameter(SystemLogType::LOGIN, ‪Connection::PARAM_INT)
190  ),
191  $queryBuilder->expr()->eq(
192  'action',
193  $queryBuilder->createNamedParameter($loginAction, ‪Connection::PARAM_INT)
194  ),
195  $queryBuilder->expr()->gt(
196  'tstamp',
197  $queryBuilder->createNamedParameter($earliestLogDate, ‪Connection::PARAM_INT)
198  )
199  );
200  return $queryBuilder;
201  }
202 }
‪TYPO3\CMS\Core\Database\Connection\PARAM_INT
‪const PARAM_INT
Definition: Connection.php:49
‪TYPO3\CMS\Backend\Security\FailedLoginAttemptNotification\$notificationRecipientEmailAddress
‪string $notificationRecipientEmailAddress
Definition: FailedLoginAttemptNotification.php:50
‪TYPO3\CMS\Backend\Security\FailedLoginAttemptNotification
Definition: FailedLoginAttemptNotification.php:45
‪TYPO3\CMS\Core\Authentication\AbstractUserAuthentication\writelog
‪writelog($type, $action, $error, $details_nr, $details, $data, $tablename, $recuid, $recpid)
Definition: AbstractUserAuthentication.php:1252
‪TYPO3\CMS\Backend\Security\FailedLoginAttemptNotification\createPreparedQueryBuilder
‪QueryBuilder createPreparedQueryBuilder(int $earliestLogDate, int $loginAction)
Definition: FailedLoginAttemptNotification.php:176
‪TYPO3\CMS\Core\SysLog\Action\Login
Definition: Login.php:24
‪TYPO3\CMS\Backend\Security\FailedLoginAttemptNotification\$warningPeriod
‪int $warningPeriod
Definition: FailedLoginAttemptNotification.php:56
‪TYPO3\CMS\Backend\Security\FailedLoginAttemptNotification\sendLoginAttemptEmail
‪sendLoginAttemptEmail(array $previousFailures)
Definition: FailedLoginAttemptNotification.php:142
‪TYPO3\CMS\Core\Mail\FluidEmail
Definition: FluidEmail.php:35
‪TYPO3\CMS\Core\SysLog\Error
Definition: Error.php:24
‪TYPO3\CMS\Backend\Security\FailedLoginAttemptNotification\__construct
‪__construct(string $notificationRecipientEmailAddress=null, int $warningPeriod=3600, int $failedLoginAttemptsThreshold=3)
Definition: FailedLoginAttemptNotification.php:63
‪TYPO3\CMS\Core\Log\LogDataTrait\formatLogDetails
‪string formatLogDetails(string $detailString, $substitutes)
Definition: LogDataTrait.php:50
‪TYPO3\CMS\Backend\Security
Definition: CategoryPermissionsAspect.php:16
‪TYPO3\CMS\Backend\Security\FailedLoginAttemptNotification\getLoginFailures
‪array getLoginFailures(int $earliestTimeToCheckForFailures)
Definition: FailedLoginAttemptNotification.php:115
‪TYPO3\CMS\Core\Authentication\BackendUserAuthentication
Definition: BackendUserAuthentication.php:62
‪TYPO3\CMS\Backend\Security\FailedLoginAttemptNotification\$failedLoginAttemptsThreshold
‪int $failedLoginAttemptsThreshold
Definition: FailedLoginAttemptNotification.php:61
‪TYPO3\CMS\Core\Mail\Mailer
Definition: Mailer.php:38
‪TYPO3\CMS\Core\Database\Connection
Definition: Connection.php:38
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Backend\Security\FailedLoginAttemptNotification\sendEmailOnLoginFailures
‪bool sendEmailOnLoginFailures(array $params, AbstractUserAuthentication $user)
Definition: FailedLoginAttemptNotification.php:80
‪TYPO3\CMS\Core\Database\ConnectionPool
Definition: ConnectionPool.php:46
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:50
‪TYPO3\CMS\Core\SysLog\Type
Definition: Type.php:28
‪TYPO3\CMS\Core\Authentication\AbstractUserAuthentication
Definition: AbstractUserAuthentication.php:56
‪TYPO3\CMS\Core\Log\LogDataTrait
Definition: LogDataTrait.php:25