--- /dev/null
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Backend\Security;
+
+/*
+ * 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!
+ */
+
+use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
+use TYPO3\CMS\Core\Mail\MailMessage;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
+/**
+ * Sends out an email if a backend user has just been logged in.
+ *
+ * Interesting settings:
+ * $GLOBALS['TYPO3_CONF_VARS']['BE']['warning_mode']
+ * $GLOBALS['TYPO3_CONF_VARS']['BE']['warning_email_addr']
+ * $BE_USER->uc['emailMeAtLogin']
+ *
+ * @internal this is not part of TYPO3 API as this is an internal hook
+ */
+class EmailLoginNotification
+{
+ /**
+ * @var int
+ */
+ private $warningMode;
+
+ /**
+ * @var string
+ */
+ private $warningEmailRecipient;
+
+ public function __construct()
+ {
+ $this->warningMode = (int)($GLOBALS['TYPO3_CONF_VARS']['BE']['warning_mode'] ?? 0);
+ $this->warningEmailRecipient = $GLOBALS['TYPO3_CONF_VARS']['BE']['warning_email_addr'] ?? '';
+ }
+
+ /**
+ * Sends an email notification to warning_email_address and/or the logged-in user's email address.
+ *
+ * @param array $parameters array data
+ * @param BackendUserAuthentication $currentUser the currently just-logged in user
+ */
+ public function emailAtLogin(array $parameters, BackendUserAuthentication $currentUser): void
+ {
+ $user = $parameters['user'];
+
+ $subject = 'At "' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] . '" from ' . GeneralUtility::getIndpEnv('REMOTE_ADDR');
+ $emailBody = $this->compileEmailBody(
+ $user,
+ GeneralUtility::getIndpEnv('REMOTE_ADDR'),
+ GeneralUtility::getIndpEnv('HTTP_HOST'),
+ $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename']
+ );
+
+ if ($this->warningMode > 0 && !empty($this->warningEmailRecipient)) {
+ $prefix = $currentUser->isAdmin() ? '[AdminLoginWarning]' : '[LoginWarning]';
+ if ($this->warningMode & 1) {
+ // First bit: Send warning email on any login
+ $this->sendEmail($this->warningEmailRecipient, $prefix . ' ' . $subject, $emailBody);
+ } elseif ($currentUser->isAdmin() && $this->warningMode & 2) {
+ // Second bit: Only send warning email when an admin logs in
+ $this->sendEmail($this->warningEmailRecipient, $prefix . ' ' . $subject, $emailBody);
+ }
+ }
+ // Trigger an email to the current BE user, if this has been enabled in the user configuration
+ if ($currentUser->uc['emailMeAtLogin'] && GeneralUtility::validEmail($user['email'])) {
+ $this->sendEmail($user['email'], $subject, $emailBody);
+ }
+ }
+
+ /**
+ * Sends an email and returns the number of recipients accepting the email.
+ *
+ * @param string $recipient
+ * @param string $subject
+ * @param string $body
+ * @return int number of recipients that successfully accepted the email
+ */
+ protected function sendEmail(string $recipient, string $subject, string $body): int
+ {
+ return GeneralUtility::makeInstance(MailMessage::class)
+ ->setTo($recipient)
+ ->setSubject($subject)
+ ->setBody($body)
+ ->send();
+ }
+
+ protected function compileEmailBody(array $user, string $ipAddress, string $httpHost, string $siteName): string
+ {
+ return sprintf(
+ 'User "%s" logged in from %s at "%s" (%s)',
+ $user['username'],
+ $ipAddress,
+ $siteName,
+ $httpHost
+ );
+ }
+}
--- /dev/null
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Backend\Tests\Unit\Security;
+
+/*
+ * 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!
+ */
+
+use TYPO3\CMS\Backend\Security\EmailLoginNotification;
+use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
+use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
+
+class EmailLoginNotificationTest extends UnitTestCase
+{
+ /**
+ * @test
+ */
+ public function emailAtLoginSendsAnEmailIfUserHasValidEmailAndOptin(): void
+ {
+ $_SERVER['HTTP_HOST'] = 'localhost';
+ $_SERVER['REMOTE_ADDR'] = '127.0.0.1';
+ $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] = 'My TYPO3 Inc.';
+ $backendUser = $this->getMockBuilder(BackendUserAuthentication::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $backendUser->uc['emailMeAtLogin'] = 1;
+
+ $userData = [
+ 'email' => 'test@acme.com'
+ ];
+
+ $subject = $this->getAccessibleMock(
+ EmailLoginNotification::class,
+ ['sendEmail', 'compileEmailBody']
+ );
+ $subject->expects($this->once())->method('sendEmail');
+ $subject->emailAtLogin(['user' => $userData], $backendUser);
+ }
+
+ /**
+ * @test
+ */
+ public function emailAtLoginDoesNotSendAnEmailIfUserHasNoOptin(): void
+ {
+ $_SERVER['HTTP_HOST'] = 'localhost';
+ $_SERVER['REMOTE_ADDR'] = '127.0.0.1';
+ $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] = 'My TYPO3 Inc.';
+ $backendUser = $this->getMockBuilder(BackendUserAuthentication::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $backendUser->uc['emailMeAtLogin'] = 0;
+
+ $userData = [
+ 'username' => 'karl',
+ 'email' => 'test@acme.com'
+ ];
+
+ $subject = $this->getAccessibleMock(
+ EmailLoginNotification::class,
+ ['sendEmail', 'compileEmailBody']
+ );
+ $subject->expects($this->never())->method('sendEmail');
+ $subject->emailAtLogin(['user' => $userData], $backendUser);
+ }
+
+ /**
+ * @test
+ */
+ public function emailAtLoginDoesNotSendAnEmailIfUserHasInvalidEmail(): void
+ {
+ $_SERVER['HTTP_HOST'] = 'localhost';
+ $_SERVER['REMOTE_ADDR'] = '127.0.0.1';
+ $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] = 'My TYPO3 Inc.';
+ $backendUser = $this->getMockBuilder(BackendUserAuthentication::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $backendUser->uc['emailMeAtLogin'] = 1;
+
+ $userData = [
+ 'username' => 'karl',
+ 'email' => 'dot.com'
+ ];
+
+ $subject = $this->getAccessibleMock(
+ EmailLoginNotification::class,
+ ['sendEmail', 'compileEmailBody']
+ );
+ $subject->expects($this->never())->method('sendEmail');
+ $subject->emailAtLogin(['user' => $userData], $backendUser);
+ }
+
+ /**
+ * @test
+ */
+ public function emailAtLoginSendsEmailToCustomEmailIfAdminWarningIsEnabled(): void
+ {
+ $_SERVER['HTTP_HOST'] = 'localhost';
+ $_SERVER['REMOTE_ADDR'] = '127.0.0.1';
+ $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] = 'My TYPO3 Inc.';
+ $GLOBALS['TYPO3_CONF_VARS']['BE']['warning_email_addr'] = 'typo3-admin@acme.com';
+ $GLOBALS['TYPO3_CONF_VARS']['BE']['warning_mode'] = 2;
+ $backendUser = $this->getMockBuilder(BackendUserAuthentication::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $backendUser->expects($this->any())->method('isAdmin')->willReturn(true);
+
+ $userData = [
+ 'username' => 'karl'
+ ];
+
+ $subject = $this->getAccessibleMock(
+ EmailLoginNotification::class,
+ ['sendEmail', 'compileEmailBody']
+ );
+ $subject->expects($this->once())->method('sendEmail')->with(
+ 'typo3-admin@acme.com',
+ '[AdminLoginWarning] At "My TYPO3 Inc." from 127.0.0.1'
+ );
+ $subject->emailAtLogin(['user' => $userData], $backendUser);
+ }
+
+ /**
+ * @test
+ */
+ public function emailAtLoginSendsEmailToCustomEmailIfRegularWarningIsEnabled(): void
+ {
+ $_SERVER['HTTP_HOST'] = 'localhost';
+ $_SERVER['REMOTE_ADDR'] = '127.0.0.1';
+ $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] = 'My TYPO3 Inc.';
+ $GLOBALS['TYPO3_CONF_VARS']['BE']['warning_email_addr'] = 'typo3-admin@acme.com';
+ $GLOBALS['TYPO3_CONF_VARS']['BE']['warning_mode'] = 1;
+ $backendUser = $this->getMockBuilder(BackendUserAuthentication::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $backendUser->expects($this->any())->method('isAdmin')->willReturn(true);
+
+ $userData = [
+ 'username' => 'karl'
+ ];
+
+ $subject = $this->getAccessibleMock(
+ EmailLoginNotification::class,
+ ['sendEmail', 'compileEmailBody']
+ );
+ $subject->expects($this->once())->method('sendEmail')->with(
+ 'typo3-admin@acme.com',
+ '[AdminLoginWarning] At "My TYPO3 Inc." from 127.0.0.1'
+ );
+ $subject->emailAtLogin(['user' => $userData], $backendUser);
+ }
+
+ /**
+ * @test
+ */
+ public function emailAtLoginSendsEmailToCustomEmailIfRegularWarningIsEnabledAndNoAdminIsLoggingIn(): void
+ {
+ $_SERVER['HTTP_HOST'] = 'localhost';
+ $_SERVER['REMOTE_ADDR'] = '127.0.0.1';
+ $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] = 'My TYPO3 Inc.';
+ $GLOBALS['TYPO3_CONF_VARS']['BE']['warning_email_addr'] = 'typo3-admin@acme.com';
+ $GLOBALS['TYPO3_CONF_VARS']['BE']['warning_mode'] = 1;
+ $backendUser = $this->getMockBuilder(BackendUserAuthentication::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $backendUser->expects($this->any())->method('isAdmin')->willReturn(false);
+
+ $userData = [
+ 'username' => 'karl'
+ ];
+
+ $subject = $this->getAccessibleMock(
+ EmailLoginNotification::class,
+ ['sendEmail', 'compileEmailBody']
+ );
+ $subject->expects($this->once())->method('sendEmail')->with(
+ 'typo3-admin@acme.com',
+ '[LoginWarning] At "My TYPO3 Inc." from 127.0.0.1'
+ );
+ $subject->emailAtLogin(['user' => $userData], $backendUser);
+ }
+
+ /**
+ * @test
+ */
+ public function emailAtLoginSendsNoEmailIfAdminWarningIsEnabledAndNoAdminIsLoggingIn()
+ {
+ $_SERVER['HTTP_HOST'] = 'localhost';
+ $_SERVER['REMOTE_ADDR'] = '127.0.0.1';
+ $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] = 'My TYPO3 Inc.';
+ $GLOBALS['TYPO3_CONF_VARS']['BE']['warning_email_addr'] = 'typo3-admin@acme.com';
+ $GLOBALS['TYPO3_CONF_VARS']['BE']['warning_mode'] = 2;
+ $backendUser = $this->getMockBuilder(BackendUserAuthentication::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $backendUser->expects($this->any())->method('isAdmin')->willReturn(false);
+
+ $userData = [
+ 'username' => 'karl'
+ ];
+
+ $subject = $this->getAccessibleMock(
+ EmailLoginNotification::class,
+ ['sendEmail', 'compileEmailBody']
+ );
+ $subject->expects($this->never())->method('sendEmail');
+ $subject->emailAtLogin(['user' => $userData], $backendUser);
+ }
+}
/**
* Check if user is logged in and if so, call ->fetchGroupData() to load group information and
- * access lists of all kind, further check IP, set the ->uc array and send login-notification email if required.
+ * access lists of all kind, further check IP, set the ->uc array.
* If no user is logged in the default behaviour is to exit with an error message.
* This function is called right after ->start() in fx. the TYPO3 Bootstrap.
*
$_params = ['user' => $this->user];
GeneralUtility::callUserFunction($_funcRef, $_params, $this);
}
- // Email at login, if feature is enabled in configuration
- $this->emailAtLogin();
}
} else {
throw new \RuntimeException('Login Error: TYPO3 is in maintenance mode at the moment. Only administrators are allowed access.', 1294585860);
$this->backendSetUC();
}
- /**
- * Sends an email notification to warning_email_address and/or the logged-in user's email address.
- *
- * @internal
- */
- private function emailAtLogin()
- {
- // Send notify-mail
- $subject = 'At "' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] . '" from ' . GeneralUtility::getIndpEnv('REMOTE_ADDR');
- $msg = sprintf(
- 'User "%s" logged in from %s at "%s" (%s)',
- $this->user['username'],
- GeneralUtility::getIndpEnv('REMOTE_ADDR'),
- $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'],
- GeneralUtility::getIndpEnv('HTTP_HOST')
- );
- // Warning email address
- if ($GLOBALS['TYPO3_CONF_VARS']['BE']['warning_email_addr']) {
- $warn = 0;
- $prefix = '';
- if ((int)$GLOBALS['TYPO3_CONF_VARS']['BE']['warning_mode'] & 1) {
- // first bit: All logins
- $warn = 1;
- $prefix = $this->isAdmin() ? '[AdminLoginWarning]' : '[LoginWarning]';
- }
- if ($this->isAdmin() && (int)$GLOBALS['TYPO3_CONF_VARS']['BE']['warning_mode'] & 2) {
- // second bit: Only admin-logins
- $warn = 1;
- $prefix = '[AdminLoginWarning]';
- }
- if ($warn) {
- /** @var \TYPO3\CMS\Core\Mail\MailMessage $mail */
- $mail = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Mail\MailMessage::class);
- $mail->setTo($GLOBALS['TYPO3_CONF_VARS']['BE']['warning_email_addr'])->setSubject($prefix . ' ' . $subject)->setBody($msg);
- $mail->send();
- }
- }
- // Trigger an email to the current BE user, if this has been enabled in the user configuration
- if ($this->uc['emailMeAtLogin'] && strstr($this->user['email'], '@')) {
- /** @var \TYPO3\CMS\Core\Mail\MailMessage $mail */
- $mail = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Mail\MailMessage::class);
- $mail->setTo($this->user['email'])->setSubject($subject)->setBody($msg);
- $mail->send();
- }
- }
-
/**
* Determines whether a backend user is allowed to access the backend.
*