Merge pull request #19845 from nextcloud/enh/events/loginFailedEvent

Event for failed login attempts
This commit is contained in:
Roeland Jago Douma 2020-03-31 22:17:40 +02:00 committed by GitHub
commit b39fb55ee0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 207 additions and 5 deletions

View File

@ -74,6 +74,7 @@ return array(
'OCP\\App\\AppPathNotFoundException' => $baseDir . '/lib/public/App/AppPathNotFoundException.php', 'OCP\\App\\AppPathNotFoundException' => $baseDir . '/lib/public/App/AppPathNotFoundException.php',
'OCP\\App\\IAppManager' => $baseDir . '/lib/public/App/IAppManager.php', 'OCP\\App\\IAppManager' => $baseDir . '/lib/public/App/IAppManager.php',
'OCP\\App\\ManagerEvent' => $baseDir . '/lib/public/App/ManagerEvent.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\\CredentialsUnavailableException' => $baseDir . '/lib/public/Authentication/Exceptions/CredentialsUnavailableException.php',
'OCP\\Authentication\\Exceptions\\PasswordUnavailableException' => $baseDir . '/lib/public/Authentication/Exceptions/PasswordUnavailableException.php', 'OCP\\Authentication\\Exceptions\\PasswordUnavailableException' => $baseDir . '/lib/public/Authentication/Exceptions/PasswordUnavailableException.php',
'OCP\\Authentication\\IApacheBackend' => $baseDir . '/lib/public/Authentication/IApacheBackend.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\\TAR' => $baseDir . '/lib/private/Archive/TAR.php',
'OC\\Archive\\ZIP' => $baseDir . '/lib/private/Archive/ZIP.php', 'OC\\Archive\\ZIP' => $baseDir . '/lib/private/Archive/ZIP.php',
'OC\\Authentication\\Events\\ARemoteWipeEvent' => $baseDir . '/lib/private/Authentication/Events/ARemoteWipeEvent.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\\RemoteWipeFinished' => $baseDir . '/lib/private/Authentication/Events/RemoteWipeFinished.php',
'OC\\Authentication\\Events\\RemoteWipeStarted' => $baseDir . '/lib/private/Authentication/Events/RemoteWipeStarted.php', 'OC\\Authentication\\Events\\RemoteWipeStarted' => $baseDir . '/lib/private/Authentication/Events/RemoteWipeStarted.php',
'OC\\Authentication\\Exceptions\\ExpiredTokenException' => $baseDir . '/lib/private/Authentication/Exceptions/ExpiredTokenException.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\\TwoFactorAuthRequiredException' => $baseDir . '/lib/private/Authentication/Exceptions/TwoFactorAuthRequiredException.php',
'OC\\Authentication\\Exceptions\\UserAlreadyLoggedInException' => $baseDir . '/lib/private/Authentication/Exceptions/UserAlreadyLoggedInException.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\\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\\RemoteWipeActivityListener' => $baseDir . '/lib/private/Authentication/Listeners/RemoteWipeActivityListener.php',
'OC\\Authentication\\Listeners\\RemoteWipeEmailListener' => $baseDir . '/lib/private/Authentication/Listeners/RemoteWipeEmailListener.php', 'OC\\Authentication\\Listeners\\RemoteWipeEmailListener' => $baseDir . '/lib/private/Authentication/Listeners/RemoteWipeEmailListener.php',
'OC\\Authentication\\Listeners\\RemoteWipeNotificationsListener' => $baseDir . '/lib/private/Authentication/Listeners/RemoteWipeNotificationsListener.php', 'OC\\Authentication\\Listeners\\RemoteWipeNotificationsListener' => $baseDir . '/lib/private/Authentication/Listeners/RemoteWipeNotificationsListener.php',

View File

@ -103,6 +103,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OCP\\App\\AppPathNotFoundException' => __DIR__ . '/../../..' . '/lib/public/App/AppPathNotFoundException.php', 'OCP\\App\\AppPathNotFoundException' => __DIR__ . '/../../..' . '/lib/public/App/AppPathNotFoundException.php',
'OCP\\App\\IAppManager' => __DIR__ . '/../../..' . '/lib/public/App/IAppManager.php', 'OCP\\App\\IAppManager' => __DIR__ . '/../../..' . '/lib/public/App/IAppManager.php',
'OCP\\App\\ManagerEvent' => __DIR__ . '/../../..' . '/lib/public/App/ManagerEvent.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\\CredentialsUnavailableException' => __DIR__ . '/../../..' . '/lib/public/Authentication/Exceptions/CredentialsUnavailableException.php',
'OCP\\Authentication\\Exceptions\\PasswordUnavailableException' => __DIR__ . '/../../..' . '/lib/public/Authentication/Exceptions/PasswordUnavailableException.php', 'OCP\\Authentication\\Exceptions\\PasswordUnavailableException' => __DIR__ . '/../../..' . '/lib/public/Authentication/Exceptions/PasswordUnavailableException.php',
'OCP\\Authentication\\IApacheBackend' => __DIR__ . '/../../..' . '/lib/public/Authentication/IApacheBackend.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\\TAR' => __DIR__ . '/../../..' . '/lib/private/Archive/TAR.php',
'OC\\Archive\\ZIP' => __DIR__ . '/../../..' . '/lib/private/Archive/ZIP.php', 'OC\\Archive\\ZIP' => __DIR__ . '/../../..' . '/lib/private/Archive/ZIP.php',
'OC\\Authentication\\Events\\ARemoteWipeEvent' => __DIR__ . '/../../..' . '/lib/private/Authentication/Events/ARemoteWipeEvent.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\\RemoteWipeFinished' => __DIR__ . '/../../..' . '/lib/private/Authentication/Events/RemoteWipeFinished.php',
'OC\\Authentication\\Events\\RemoteWipeStarted' => __DIR__ . '/../../..' . '/lib/private/Authentication/Events/RemoteWipeStarted.php', 'OC\\Authentication\\Events\\RemoteWipeStarted' => __DIR__ . '/../../..' . '/lib/private/Authentication/Events/RemoteWipeStarted.php',
'OC\\Authentication\\Exceptions\\ExpiredTokenException' => __DIR__ . '/../../..' . '/lib/private/Authentication/Exceptions/ExpiredTokenException.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\\TwoFactorAuthRequiredException' => __DIR__ . '/../../..' . '/lib/private/Authentication/Exceptions/TwoFactorAuthRequiredException.php',
'OC\\Authentication\\Exceptions\\UserAlreadyLoggedInException' => __DIR__ . '/../../..' . '/lib/private/Authentication/Exceptions/UserAlreadyLoggedInException.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\\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\\RemoteWipeActivityListener' => __DIR__ . '/../../..' . '/lib/private/Authentication/Listeners/RemoteWipeActivityListener.php',
'OC\\Authentication\\Listeners\\RemoteWipeEmailListener' => __DIR__ . '/../../..' . '/lib/private/Authentication/Listeners/RemoteWipeEmailListener.php', 'OC\\Authentication\\Listeners\\RemoteWipeEmailListener' => __DIR__ . '/../../..' . '/lib/private/Authentication/Listeners/RemoteWipeEmailListener.php',
'OC\\Authentication\\Listeners\\RemoteWipeNotificationsListener' => __DIR__ . '/../../..' . '/lib/private/Authentication/Listeners/RemoteWipeNotificationsListener.php', 'OC\\Authentication\\Listeners\\RemoteWipeNotificationsListener' => __DIR__ . '/../../..' . '/lib/private/Authentication/Listeners/RemoteWipeNotificationsListener.php',

View File

@ -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;
}
}

View File

@ -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));
}
}
}

View File

@ -25,24 +25,36 @@ declare(strict_types=1);
namespace OC\Authentication\Login; namespace OC\Authentication\Login;
use OC\Authentication\Events\LoginFailed;
use OC\Core\Controller\LoginController; use OC\Core\Controller\LoginController;
use OCP\Authentication\Events\LoginFailedEvent;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\ILogger; use OCP\ILogger;
use OCP\IUserManager;
use OCP\Util;
class LoggedInCheckCommand extends ALoginCommand { class LoggedInCheckCommand extends ALoginCommand {
/** @var ILogger */ /** @var ILogger */
private $logger; 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->logger = $logger;
$this->dispatcher = $dispatcher;
} }
public function process(LoginData $loginData): LoginResult { public function process(LoginData $loginData): LoginResult {
if ($loginData->getUser() === false) { if ($loginData->getUser() === false) {
$username = $loginData->getUsername(); $loginName = $loginData->getUsername();
$ip = $loginData->getRequest()->getRemoteAddress(); $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); return LoginResult::failure($loginData, LoginController::LOGIN_MSG_INVALIDPASSWORD);
} }

View File

@ -62,6 +62,8 @@ use OC\App\AppStore\Fetcher\CategoryFetcher;
use OC\AppFramework\Http\Request; use OC\AppFramework\Http\Request;
use OC\AppFramework\Utility\SimpleContainer; use OC\AppFramework\Utility\SimpleContainer;
use OC\AppFramework\Utility\TimeFactory; use OC\AppFramework\Utility\TimeFactory;
use OC\Authentication\Events\LoginFailed;
use OC\Authentication\Listeners\LoginFailedListener;
use OC\Authentication\LoginCredentials\Store; use OC\Authentication\LoginCredentials\Store;
use OC\Authentication\Token\IProvider; use OC\Authentication\Token\IProvider;
use OC\Avatar\AvatarManager; use OC\Avatar\AvatarManager;
@ -1416,6 +1418,10 @@ class Server extends ServerContainer implements IServerContainer {
// no avatar to remove // no avatar to remove
} }
}); });
/** @var IEventDispatcher $eventDispatched */
$eventDispatched = $this->query(IEventDispatcher::class);
$eventDispatched->addServiceListener(LoginFailed::class, LoginFailedListener::class);
} }
/** /**

View File

@ -51,6 +51,7 @@ use OC_User;
use OC_Util; use OC_Util;
use OCA\DAV\Connector\Sabre\Auth; use OCA\DAV\Connector\Sabre\Auth;
use OCP\AppFramework\Utility\ITimeFactory; use OCP\AppFramework\Utility\ITimeFactory;
use OCP\Authentication\Events\LoginFailedEvent;
use OCP\EventDispatcher\IEventDispatcher; use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\NotPermittedException; use OCP\Files\NotPermittedException;
use OCP\IConfig; use OCP\IConfig;
@ -58,6 +59,7 @@ use OCP\ILogger;
use OCP\IRequest; use OCP\IRequest;
use OCP\ISession; use OCP\ISession;
use OCP\IUser; use OCP\IUser;
use OCP\IUserManager;
use OCP\IUserSession; use OCP\IUserSession;
use OCP\Lockdown\ILockdownManager; use OCP\Lockdown\ILockdownManager;
use OCP\Security\ISecureRandom; use OCP\Security\ISecureRandom;
@ -137,7 +139,8 @@ class Session implements IUserSession, Emitter {
ISecureRandom $random, ISecureRandom $random,
ILockdownManager $lockdownManager, ILockdownManager $lockdownManager,
ILogger $logger, ILogger $logger,
IEventDispatcher $dispatcher) { IEventDispatcher $dispatcher
) {
$this->manager = $manager; $this->manager = $manager;
$this->session = $session; $this->session = $session;
$this->timeFactory = $timeFactory; $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']); $this->logger->warning('Login failed: \'' . $user . '\' (Remote IP: \'' . \OC::$server->getRequest()->getRemoteAddress() . '\')', ['app' => 'core']);
$throttler->registerAttempt('login', $request->getRemoteAddress(), ['user' => $user]); $throttler->registerAttempt('login', $request->getRemoteAddress(), ['user' => $user]);
$this->dispatcher->dispatchTyped(new OC\Authentication\Events\LoginFailed($user));
if ($currentDelay === 0) { if ($currentDelay === 0) {
$throttler->sleepDelay($request->getRemoteAddress(), 'login'); $throttler->sleepDelay($request->getRemoteAddress(), 'login');
} }

View File

@ -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;
}
}

View File

@ -25,8 +25,10 @@ declare(strict_types=1);
namespace lib\Authentication\Login; namespace lib\Authentication\Login;
use Hoa\Iterator\Mock;
use OC\Authentication\Login\LoggedInCheckCommand; use OC\Authentication\Login\LoggedInCheckCommand;
use OC\Core\Controller\LoginController; use OC\Core\Controller\LoginController;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\ILogger; use OCP\ILogger;
use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\MockObject\MockObject;
@ -35,13 +37,18 @@ class LoggedInCheckCommandTest extends ALoginCommandTest {
/** @var ILogger|MockObject */ /** @var ILogger|MockObject */
private $logger; private $logger;
/** @var IEventDispatcher|MockObject */
private $dispatcher;
protected function setUp(): void { protected function setUp(): void {
parent::setUp(); parent::setUp();
$this->logger = $this->createMock(ILogger::class); $this->logger = $this->createMock(ILogger::class);
$this->dispatcher = $this->createMock(IEventDispatcher::class);
$this->cmd = new LoggedInCheckCommand( $this->cmd = new LoggedInCheckCommand(
$this->logger $this->logger,
$this->dispatcher
); );
} }