[TASK] Move emailAtLogin for BE Users in custom class 38/60838/9
authorBenni Mack <benni@typo3.org>
Wed, 29 May 2019 13:18:48 +0000 (15:18 +0200)
committerAndreas Fernandez <a.fernandez@scripting-base.de>
Fri, 7 Jun 2019 10:03:16 +0000 (12:03 +0200)
The fat BE User object needs a haircut, and the obvious
logic for sending out an email is moved out.

Resolves: #88454
Releases: master
Change-Id: Id009a975c1d4b75d17c8f5ec2ce06f384dda6e12
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/60838
Tested-by: TYPO3com <noreply@typo3.com>
Tested-by: Georg Ringer <georg.ringer@gmail.com>
Tested-by: Andreas Fernandez <a.fernandez@scripting-base.de>
Reviewed-by: Georg Ringer <georg.ringer@gmail.com>
Reviewed-by: Andreas Fernandez <a.fernandez@scripting-base.de>
typo3/sysext/backend/Classes/Security/EmailLoginNotification.php [new file with mode: 0644]
typo3/sysext/backend/Tests/Unit/Security/EmailLoginNotificationTest.php [new file with mode: 0644]
typo3/sysext/backend/ext_localconf.php
typo3/sysext/core/Classes/Authentication/BackendUserAuthentication.php

diff --git a/typo3/sysext/backend/Classes/Security/EmailLoginNotification.php b/typo3/sysext/backend/Classes/Security/EmailLoginNotification.php
new file mode 100644 (file)
index 0000000..0f9b6c1
--- /dev/null
@@ -0,0 +1,112 @@
+<?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
+        );
+    }
+}
diff --git a/typo3/sysext/backend/Tests/Unit/Security/EmailLoginNotificationTest.php b/typo3/sysext/backend/Tests/Unit/Security/EmailLoginNotificationTest.php
new file mode 100644 (file)
index 0000000..b8c7d7b
--- /dev/null
@@ -0,0 +1,218 @@
+<?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);
+    }
+}
index c6a2e4c..7a8b4ce 100644 (file)
@@ -41,3 +41,4 @@ $GLOBALS['TYPO3_CONF_VARS']['SYS']['livesearch']['page'] = 'pages';
 
 // Register BackendLayoutDataProvider for PageTs
 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['BackendLayoutDataProvider']['pagets'] = \TYPO3\CMS\Backend\Provider\PageTsBackendLayoutDataProvider::class;
+$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauthgroup.php']['backendUserLogin']['sendEmailOnLogin'] = \TYPO3\CMS\Backend\Security\EmailLoginNotification::class . '->emailAtLogin';
index 3a92f3d..4c69c2c 100644 (file)
@@ -2369,7 +2369,7 @@ This is a dump of the failures:
 
     /**
      * 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.
      *
@@ -2399,8 +2399,6 @@ This is a dump of the failures:
                             $_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);
@@ -2483,52 +2481,6 @@ This is a dump of the failures:
         $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.
      *