]> source.dussan.org Git - nextcloud-server.git/commitdiff
[POC] Event for failed login attempts 19845/head
authorRoeland Jago Douma <roeland@famdouma.nl>
Sat, 7 Mar 2020 15:18:21 +0000 (16:18 +0100)
committerRoeland Jago Douma <roeland@famdouma.nl>
Tue, 31 Mar 2020 19:41:02 +0000 (21:41 +0200)
Signed-off-by: Roeland Jago Douma <roeland@famdouma.nl>
lib/composer/composer/autoload_classmap.php
lib/composer/composer/autoload_static.php
lib/private/Authentication/Events/LoginFailed.php [new file with mode: 0644]
lib/private/Authentication/Listeners/LoginFailedListener.php [new file with mode: 0644]
lib/private/Authentication/Login/LoggedInCheckCommand.php
lib/private/Server.php
lib/private/User/Session.php
lib/public/Authentication/Events/LoginFailedEvent.php [new file with mode: 0644]
tests/lib/Authentication/Login/LoggedInCheckCommandTest.php

index 388c7906eb81c7ae2311d76ec6e7aa0b3b93d4d5..78e461132a010c61d515e62e498c8c9944eb2251 100644 (file)
@@ -74,6 +74,7 @@ return array(
     'OCP\\App\\AppPathNotFoundException' => $baseDir . '/lib/public/App/AppPathNotFoundException.php',
     'OCP\\App\\IAppManager' => $baseDir . '/lib/public/App/IAppManager.php',
     'OCP\\App\\ManagerEvent' => $baseDir . '/lib/public/App/ManagerEvent.php',
+    'OCP\\Authentication\\Events\\LoginFailedEvent' => $baseDir . '/lib/public/Authentication/Events/LoginFailedEvent.php',
     'OCP\\Authentication\\Exceptions\\CredentialsUnavailableException' => $baseDir . '/lib/public/Authentication/Exceptions/CredentialsUnavailableException.php',
     'OCP\\Authentication\\Exceptions\\PasswordUnavailableException' => $baseDir . '/lib/public/Authentication/Exceptions/PasswordUnavailableException.php',
     'OCP\\Authentication\\IApacheBackend' => $baseDir . '/lib/public/Authentication/IApacheBackend.php',
@@ -592,6 +593,7 @@ return array(
     'OC\\Archive\\TAR' => $baseDir . '/lib/private/Archive/TAR.php',
     'OC\\Archive\\ZIP' => $baseDir . '/lib/private/Archive/ZIP.php',
     'OC\\Authentication\\Events\\ARemoteWipeEvent' => $baseDir . '/lib/private/Authentication/Events/ARemoteWipeEvent.php',
+    'OC\\Authentication\\Events\\LoginFailed' => $baseDir . '/lib/private/Authentication/Events/LoginFailed.php',
     'OC\\Authentication\\Events\\RemoteWipeFinished' => $baseDir . '/lib/private/Authentication/Events/RemoteWipeFinished.php',
     'OC\\Authentication\\Events\\RemoteWipeStarted' => $baseDir . '/lib/private/Authentication/Events/RemoteWipeStarted.php',
     'OC\\Authentication\\Exceptions\\ExpiredTokenException' => $baseDir . '/lib/private/Authentication/Exceptions/ExpiredTokenException.php',
@@ -604,6 +606,7 @@ return array(
     'OC\\Authentication\\Exceptions\\TwoFactorAuthRequiredException' => $baseDir . '/lib/private/Authentication/Exceptions/TwoFactorAuthRequiredException.php',
     'OC\\Authentication\\Exceptions\\UserAlreadyLoggedInException' => $baseDir . '/lib/private/Authentication/Exceptions/UserAlreadyLoggedInException.php',
     'OC\\Authentication\\Exceptions\\WipeTokenException' => $baseDir . '/lib/private/Authentication/Exceptions/WipeTokenException.php',
+    'OC\\Authentication\\Listeners\\LoginFailedListener' => $baseDir . '/lib/private/Authentication/Listeners/LoginFailedListener.php',
     'OC\\Authentication\\Listeners\\RemoteWipeActivityListener' => $baseDir . '/lib/private/Authentication/Listeners/RemoteWipeActivityListener.php',
     'OC\\Authentication\\Listeners\\RemoteWipeEmailListener' => $baseDir . '/lib/private/Authentication/Listeners/RemoteWipeEmailListener.php',
     'OC\\Authentication\\Listeners\\RemoteWipeNotificationsListener' => $baseDir . '/lib/private/Authentication/Listeners/RemoteWipeNotificationsListener.php',
index cfc6d9842dfa765524222b1cc4f785a648ffd098..6d6a2055fcc2f2917f0e5b54bcb226bfbdc0daee 100644 (file)
@@ -103,6 +103,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
         'OCP\\App\\AppPathNotFoundException' => __DIR__ . '/../../..' . '/lib/public/App/AppPathNotFoundException.php',
         'OCP\\App\\IAppManager' => __DIR__ . '/../../..' . '/lib/public/App/IAppManager.php',
         'OCP\\App\\ManagerEvent' => __DIR__ . '/../../..' . '/lib/public/App/ManagerEvent.php',
+        'OCP\\Authentication\\Events\\LoginFailedEvent' => __DIR__ . '/../../..' . '/lib/public/Authentication/Events/LoginFailedEvent.php',
         'OCP\\Authentication\\Exceptions\\CredentialsUnavailableException' => __DIR__ . '/../../..' . '/lib/public/Authentication/Exceptions/CredentialsUnavailableException.php',
         'OCP\\Authentication\\Exceptions\\PasswordUnavailableException' => __DIR__ . '/../../..' . '/lib/public/Authentication/Exceptions/PasswordUnavailableException.php',
         'OCP\\Authentication\\IApacheBackend' => __DIR__ . '/../../..' . '/lib/public/Authentication/IApacheBackend.php',
@@ -621,6 +622,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
         'OC\\Archive\\TAR' => __DIR__ . '/../../..' . '/lib/private/Archive/TAR.php',
         'OC\\Archive\\ZIP' => __DIR__ . '/../../..' . '/lib/private/Archive/ZIP.php',
         'OC\\Authentication\\Events\\ARemoteWipeEvent' => __DIR__ . '/../../..' . '/lib/private/Authentication/Events/ARemoteWipeEvent.php',
+        'OC\\Authentication\\Events\\LoginFailed' => __DIR__ . '/../../..' . '/lib/private/Authentication/Events/LoginFailed.php',
         'OC\\Authentication\\Events\\RemoteWipeFinished' => __DIR__ . '/../../..' . '/lib/private/Authentication/Events/RemoteWipeFinished.php',
         'OC\\Authentication\\Events\\RemoteWipeStarted' => __DIR__ . '/../../..' . '/lib/private/Authentication/Events/RemoteWipeStarted.php',
         'OC\\Authentication\\Exceptions\\ExpiredTokenException' => __DIR__ . '/../../..' . '/lib/private/Authentication/Exceptions/ExpiredTokenException.php',
@@ -633,6 +635,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
         'OC\\Authentication\\Exceptions\\TwoFactorAuthRequiredException' => __DIR__ . '/../../..' . '/lib/private/Authentication/Exceptions/TwoFactorAuthRequiredException.php',
         'OC\\Authentication\\Exceptions\\UserAlreadyLoggedInException' => __DIR__ . '/../../..' . '/lib/private/Authentication/Exceptions/UserAlreadyLoggedInException.php',
         'OC\\Authentication\\Exceptions\\WipeTokenException' => __DIR__ . '/../../..' . '/lib/private/Authentication/Exceptions/WipeTokenException.php',
+        'OC\\Authentication\\Listeners\\LoginFailedListener' => __DIR__ . '/../../..' . '/lib/private/Authentication/Listeners/LoginFailedListener.php',
         'OC\\Authentication\\Listeners\\RemoteWipeActivityListener' => __DIR__ . '/../../..' . '/lib/private/Authentication/Listeners/RemoteWipeActivityListener.php',
         'OC\\Authentication\\Listeners\\RemoteWipeEmailListener' => __DIR__ . '/../../..' . '/lib/private/Authentication/Listeners/RemoteWipeEmailListener.php',
         'OC\\Authentication\\Listeners\\RemoteWipeNotificationsListener' => __DIR__ . '/../../..' . '/lib/private/Authentication/Listeners/RemoteWipeNotificationsListener.php',
diff --git a/lib/private/Authentication/Events/LoginFailed.php b/lib/private/Authentication/Events/LoginFailed.php
new file mode 100644 (file)
index 0000000..cc5a547
--- /dev/null
@@ -0,0 +1,43 @@
+<?php
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2020, Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @author Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OC\Authentication\Events;
+
+use OCP\EventDispatcher\Event;
+
+class LoginFailed extends Event {
+
+       /** @var string */
+       private $loginName;
+
+       public function __construct(string $loginName) {
+               parent::__construct();
+
+               $this->loginName = $loginName;
+       }
+
+       public function getLoginName(): string {
+               return $this->loginName;
+       }
+}
diff --git a/lib/private/Authentication/Listeners/LoginFailedListener.php b/lib/private/Authentication/Listeners/LoginFailedListener.php
new file mode 100644 (file)
index 0000000..d4f46d9
--- /dev/null
@@ -0,0 +1,64 @@
+<?php
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2020, Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @author Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OC\Authentication\Listeners;
+
+use OC\Authentication\Events\LoginFailed;
+use OCP\Authentication\Events\LoginFailedEvent;
+use OCP\EventDispatcher\Event;
+use OCP\EventDispatcher\IEventDispatcher;
+use OCP\EventDispatcher\IEventListener;
+use OCP\IUserManager;
+use OCP\Util;
+
+class LoginFailedListener implements IEventListener {
+
+       /** @var IEventDispatcher */
+       private $dispatcher;
+
+       /** @var IUserManager */
+       private $userManager;
+
+       public function __construct(IEventDispatcher $dispatcher, IUserManager $userManager) {
+               $this->dispatcher = $dispatcher;
+               $this->userManager = $userManager;
+       }
+
+       public function handle(Event $event): void {
+               if (!($event instanceof LoginFailed)) {
+                       return;
+               }
+
+               $uid = $event->getLoginName();
+               Util::emitHook(
+                       '\OCA\Files_Sharing\API\Server2Server',
+                       'preLoginNameUsedAsUserName',
+                       ['uid' => &$uid]
+               );
+               if($this->userManager->userExists($uid)) {
+                       $this->dispatcher->dispatchTyped(new LoginFailedEvent($uid));
+               }
+       }
+
+}
index 9c1e45e0db7bd5e9cdae4abd27af53ff2ae908f7..78f27bfa93796ff6954145327004d4c33bcaa235 100644 (file)
@@ -25,24 +25,36 @@ declare(strict_types=1);
 
 namespace OC\Authentication\Login;
 
+use OC\Authentication\Events\LoginFailed;
 use OC\Core\Controller\LoginController;
+use OCP\Authentication\Events\LoginFailedEvent;
+use OCP\EventDispatcher\IEventDispatcher;
 use OCP\ILogger;
+use OCP\IUserManager;
+use OCP\Util;
 
 class LoggedInCheckCommand extends ALoginCommand {
 
        /** @var ILogger */
        private $logger;
+       /** @var IEventDispatcher */
+       private $dispatcher;
+       /** @var IUserManager */
+       private $userManager;
 
-       public function __construct(ILogger $logger) {
+       public function __construct(ILogger $logger, IEventDispatcher $dispatcher) {
                $this->logger = $logger;
+               $this->dispatcher = $dispatcher;
        }
 
        public function process(LoginData $loginData): LoginResult {
                if ($loginData->getUser() === false) {
-                       $username = $loginData->getUsername();
+                       $loginName = $loginData->getUsername();
                        $ip = $loginData->getRequest()->getRemoteAddress();
 
-                       $this->logger->warning("Login failed: $username (Remote IP: $ip)");
+                       $this->logger->warning("Login failed: $loginName (Remote IP: $ip)");
+
+                       $this->dispatcher->dispatchTyped(new LoginFailed($loginName));
 
                        return LoginResult::failure($loginData, LoginController::LOGIN_MSG_INVALIDPASSWORD);
                }
index 27268981c2bcf07a52f7b5781089d7465bc157c9..b9155563c0f97ac985357ca44d01e83af7b5d0f4 100644 (file)
@@ -62,6 +62,8 @@ use OC\App\AppStore\Fetcher\CategoryFetcher;
 use OC\AppFramework\Http\Request;
 use OC\AppFramework\Utility\SimpleContainer;
 use OC\AppFramework\Utility\TimeFactory;
+use OC\Authentication\Events\LoginFailed;
+use OC\Authentication\Listeners\LoginFailedListener;
 use OC\Authentication\LoginCredentials\Store;
 use OC\Authentication\Token\IProvider;
 use OC\Avatar\AvatarManager;
@@ -1416,6 +1418,10 @@ class Server extends ServerContainer implements IServerContainer {
                                // no avatar to remove
                        }
                });
+
+               /** @var IEventDispatcher $eventDispatched */
+               $eventDispatched = $this->query(IEventDispatcher::class);
+               $eventDispatched->addServiceListener(LoginFailed::class, LoginFailedListener::class);
        }
 
        /**
index 3d97ddce7f9cca6a79f8b46b4860d91eca79b24c..9129fb7054b0861c0f973990f7b9f736d18da8fe 100644 (file)
@@ -51,6 +51,7 @@ use OC_User;
 use OC_Util;
 use OCA\DAV\Connector\Sabre\Auth;
 use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\Authentication\Events\LoginFailedEvent;
 use OCP\EventDispatcher\IEventDispatcher;
 use OCP\Files\NotPermittedException;
 use OCP\IConfig;
@@ -58,6 +59,7 @@ use OCP\ILogger;
 use OCP\IRequest;
 use OCP\ISession;
 use OCP\IUser;
+use OCP\IUserManager;
 use OCP\IUserSession;
 use OCP\Lockdown\ILockdownManager;
 use OCP\Security\ISecureRandom;
@@ -137,7 +139,8 @@ class Session implements IUserSession, Emitter {
                                                                ISecureRandom $random,
                                                                ILockdownManager $lockdownManager,
                                                                ILogger $logger,
-                                                               IEventDispatcher $dispatcher) {
+                                                               IEventDispatcher $dispatcher
+       ) {
                $this->manager = $manager;
                $this->session = $session;
                $this->timeFactory = $timeFactory;
@@ -467,6 +470,9 @@ class Session implements IUserSession, Emitter {
                                $this->logger->warning('Login failed: \'' . $user . '\' (Remote IP: \'' . \OC::$server->getRequest()->getRemoteAddress() . '\')', ['app' => 'core']);
 
                                $throttler->registerAttempt('login', $request->getRemoteAddress(), ['user' => $user]);
+
+                               $this->dispatcher->dispatchTyped(new OC\Authentication\Events\LoginFailed($user));
+
                                if ($currentDelay === 0) {
                                        $throttler->sleepDelay($request->getRemoteAddress(), 'login');
                                }
diff --git a/lib/public/Authentication/Events/LoginFailedEvent.php b/lib/public/Authentication/Events/LoginFailedEvent.php
new file mode 100644 (file)
index 0000000..15c8fb3
--- /dev/null
@@ -0,0 +1,58 @@
+<?php
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2020, Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @author Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCP\Authentication\Events;
+
+use OCP\EventDispatcher\Event;
+
+/**
+ * Class LoginFailedEvent
+ *
+ * @package OCP\Authentication\Events
+ *
+ * @since 19.0.0
+ */
+class LoginFailedEvent extends Event {
+
+       /** @var string */
+       private $uid;
+
+       /**
+        * @since 19.0.0
+        */
+       public function __construct(string $uid) {
+               parent::__construct();
+
+               $this->uid = $uid;
+       }
+
+       /**
+        * returns the uid of the user that was tried to login against
+        *
+        * @since 19.0.0
+        */
+       public function getUid(): string {
+               return $this->uid;
+       }
+}
index 3b18c97a6129b8e3762d151beac2375fcac964fd..f051147609c818557fb35ceb3ff84c6567799606 100644 (file)
@@ -25,8 +25,10 @@ declare(strict_types=1);
 
 namespace lib\Authentication\Login;
 
+use Hoa\Iterator\Mock;
 use OC\Authentication\Login\LoggedInCheckCommand;
 use OC\Core\Controller\LoginController;
+use OCP\EventDispatcher\IEventDispatcher;
 use OCP\ILogger;
 use PHPUnit\Framework\MockObject\MockObject;
 
@@ -35,13 +37,18 @@ class LoggedInCheckCommandTest extends ALoginCommandTest {
        /** @var ILogger|MockObject */
        private $logger;
 
+       /** @var IEventDispatcher|MockObject */
+       private $dispatcher;
+
        protected function setUp(): void {
                parent::setUp();
 
                $this->logger = $this->createMock(ILogger::class);
+               $this->dispatcher = $this->createMock(IEventDispatcher::class);
 
                $this->cmd = new LoggedInCheckCommand(
-                       $this->logger
+                       $this->logger,
+                       $this->dispatcher
                );
        }