diff --git a/apps/admin_audit/lib/Actions/AppManagement.php b/apps/admin_audit/lib/Actions/AppManagement.php deleted file mode 100644 index d6bc7d2c61f26..0000000000000 --- a/apps/admin_audit/lib/Actions/AppManagement.php +++ /dev/null @@ -1,61 +0,0 @@ - - * - * @author Joas Schilling - * @author Roeland Jago Douma - * - * @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 . - * - */ -namespace OCA\AdminAudit\Actions; - -class AppManagement extends Action { - - /** - * @param string $appName - */ - public function enableApp(string $appName): void { - $this->log('App "%s" enabled', - ['app' => $appName], - ['app'] - ); - } - - /** - * @param string $appName - * @param string[] $groups - */ - public function enableAppForGroups(string $appName, array $groups): void { - $this->log('App "%1$s" enabled for groups: %2$s', - ['app' => $appName, 'groups' => implode(', ', $groups)], - ['app', 'groups'] - ); - } - - /** - * @param string $appName - */ - public function disableApp(string $appName): void { - $this->log('App "%s" disabled', - ['app' => $appName], - ['app'] - ); - } -} diff --git a/apps/admin_audit/lib/Actions/Auth.php b/apps/admin_audit/lib/Actions/Auth.php deleted file mode 100644 index 27722d4a41c1b..0000000000000 --- a/apps/admin_audit/lib/Actions/Auth.php +++ /dev/null @@ -1,65 +0,0 @@ - - * - * @author Joas Schilling - * @author Lukas Reschke - * @author Roeland Jago Douma - * - * @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 . - * - */ -namespace OCA\AdminAudit\Actions; - -/** - * Class Auth logs all auth related actions - * - * @package OCA\AdminAudit\Actions - */ -class Auth extends Action { - public function loginAttempt(array $params): void { - $this->log( - 'Login attempt: "%s"', - $params, - [ - 'uid', - ], - true - ); - } - - public function loginSuccessful(array $params): void { - $this->log( - 'Login successful: "%s"', - $params, - [ - 'uid', - ], - true - ); - } - - public function logout(array $params): void { - $this->log( - 'Logout occurred', - [], - [] - ); - } -} diff --git a/apps/admin_audit/lib/Actions/GroupManagement.php b/apps/admin_audit/lib/Actions/GroupManagement.php deleted file mode 100644 index e79b86bb88bcc..0000000000000 --- a/apps/admin_audit/lib/Actions/GroupManagement.php +++ /dev/null @@ -1,109 +0,0 @@ - - * - * @author Bjoern Schiessle - * @author Joas Schilling - * @author Lukas Reschke - * @author Roeland Jago Douma - * @author Roger Szabo - * - * @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 . - * - */ -namespace OCA\AdminAudit\Actions; - -use OCP\IGroup; -use OCP\IUser; - -/** - * Class GroupManagement logs all group manager related events - * - * @package OCA\AdminAudit\Actions - */ -class GroupManagement extends Action { - - /** - * log add user to group event - * - * @param IGroup $group - * @param IUser $user - */ - public function addUser(IGroup $group, IUser $user): void { - $this->log('User "%s" added to group "%s"', - [ - 'group' => $group->getGID(), - 'user' => $user->getUID() - ], - [ - 'user', 'group' - ] - ); - } - - /** - * log remove user from group event - * - * @param IGroup $group - * @param IUser $user - */ - public function removeUser(IGroup $group, IUser $user): void { - $this->log('User "%s" removed from group "%s"', - [ - 'group' => $group->getGID(), - 'user' => $user->getUID() - ], - [ - 'user', 'group' - ] - ); - } - - /** - * log create group to group event - * - * @param IGroup $group - */ - public function createGroup(IGroup $group): void { - $this->log('Group created: "%s"', - [ - 'group' => $group->getGID() - ], - [ - 'group' - ] - ); - } - - /** - * log delete group to group event - * - * @param IGroup $group - */ - public function deleteGroup(IGroup $group): void { - $this->log('Group deleted: "%s"', - [ - 'group' => $group->getGID() - ], - [ - 'group' - ] - ); - } -} diff --git a/apps/admin_audit/lib/Actions/Security.php b/apps/admin_audit/lib/Actions/Security.php deleted file mode 100644 index e4831ac6fc1ac..0000000000000 --- a/apps/admin_audit/lib/Actions/Security.php +++ /dev/null @@ -1,78 +0,0 @@ - - * - * @author Christoph Wurst - * @author Roeland Jago Douma - * - * @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 . - * - */ -namespace OCA\AdminAudit\Actions; - -use OCP\IUser; - -/** - * Class Sharing logs the sharing actions - * - * @package OCA\AdminAudit\Actions - */ -class Security extends Action { - /** - * Log twofactor auth enabled - * - * @param IUser $user - * @param array $params - */ - public function twofactorFailed(IUser $user, array $params): void { - $params['uid'] = $user->getUID(); - $params['displayName'] = $user->getDisplayName(); - - $this->log( - 'Failed two factor attempt by user %s (%s) with provider %s', - $params, - [ - 'displayName', - 'uid', - 'provider', - ] - ); - } - - /** - * Logs unsharing of data - * - * @param IUser $user - * @param array $params - */ - public function twofactorSuccess(IUser $user, array $params): void { - $params['uid'] = $user->getUID(); - $params['displayName'] = $user->getDisplayName(); - - $this->log( - 'Successful two factor attempt by user %s (%s) with provider %s', - $params, - [ - 'displayName', - 'uid', - 'provider', - ] - ); - } -} diff --git a/apps/admin_audit/lib/Actions/UserManagement.php b/apps/admin_audit/lib/Actions/UserManagement.php index 02d5b60d2fa29..a3a3afb23e39e 100644 --- a/apps/admin_audit/lib/Actions/UserManagement.php +++ b/apps/admin_audit/lib/Actions/UserManagement.php @@ -31,28 +31,12 @@ */ namespace OCA\AdminAudit\Actions; -use OCP\IUser; - /** * Class UserManagement logs all user management related actions. * * @package OCA\AdminAudit\Actions */ class UserManagement extends Action { - /** - * Log creation of users - * - * @param array $params - */ - public function create(array $params): void { - $this->log( - 'User created: "%s"', - $params, - [ - 'uid', - ] - ); - } /** * Log assignments of users (typically user backends) @@ -67,21 +51,6 @@ public function assign(string $uid): void { ); } - /** - * Log deletion of users - * - * @param array $params - */ - public function delete(array $params): void { - $this->log( - 'User deleted: "%s"', - $params, - [ - 'uid', - ] - ); - } - /** * Log unassignments of users (typically user backends, no data removed) * @@ -94,53 +63,4 @@ public function unassign(string $uid): void { [ 'uid' ] ); } - - /** - * Log enabling of users - * - * @param array $params - */ - public function change(array $params): void { - switch ($params['feature']) { - case 'enabled': - $this->log( - $params['value'] === true - ? 'User enabled: "%s"' - : 'User disabled: "%s"', - ['user' => $params['user']->getUID()], - [ - 'user', - ] - ); - break; - case 'eMailAddress': - $this->log( - 'Email address changed for user %s', - ['user' => $params['user']->getUID()], - [ - 'user', - ] - ); - break; - } - } - - /** - * Logs changing of the user scope - * - * @param IUser $user - */ - public function setPassword(IUser $user): void { - if ($user->getBackendClassName() === 'Database') { - $this->log( - 'Password of user "%s" has been changed', - [ - 'user' => $user->getUID(), - ], - [ - 'user', - ] - ); - } - } } diff --git a/apps/admin_audit/lib/AppInfo/Application.php b/apps/admin_audit/lib/AppInfo/Application.php index 1160d151710c0..0d2ec5f6e5e27 100644 --- a/apps/admin_audit/lib/AppInfo/Application.php +++ b/apps/admin_audit/lib/AppInfo/Application.php @@ -13,6 +13,7 @@ * @author Joas Schilling * @author Morris Jobke * @author Roeland Jago Douma + * @author Thomas Citharel * @author Tiago Flores * * @license GNU AGPL version 3 or any later version @@ -33,49 +34,62 @@ */ namespace OCA\AdminAudit\AppInfo; -use Closure; use OC\Files\Filesystem; -use OC\Files\Node\File; -use OC\Group\Manager as GroupManager; use OC\User\Session as UserSession; -use OCA\AdminAudit\Actions\AppManagement; -use OCA\AdminAudit\Actions\Auth; -use OCA\AdminAudit\Actions\Console; use OCA\AdminAudit\Actions\Files; -use OCA\AdminAudit\Actions\GroupManagement; -use OCA\AdminAudit\Actions\Security; use OCA\AdminAudit\Actions\Sharing; use OCA\AdminAudit\Actions\Trashbin; use OCA\AdminAudit\Actions\UserManagement; use OCA\AdminAudit\Actions\Versions; use OCA\AdminAudit\AuditLogger; use OCA\AdminAudit\IAuditLogger; +use OCA\AdminAudit\Listener\AppManagementEventListener; +use OCA\AdminAudit\Listener\AuthEventListener; +use OCA\AdminAudit\Listener\ConsoleEventListener; use OCA\AdminAudit\Listener\CriticalActionPerformedEventListener; -use OCP\App\ManagerEvent; +use OCA\AdminAudit\Listener\FileEventListener; +use OCA\AdminAudit\Listener\GroupManagementEventListener; +use OCA\AdminAudit\Listener\SecurityEventListener; +use OCA\AdminAudit\Listener\SharingEventListener; +use OCA\AdminAudit\Listener\UserManagementEventListener; +use OCP\App\Events\AppDisableEvent; +use OCP\App\Events\AppEnableEvent; +use OCP\App\Events\AppUpdateEvent; use OCP\AppFramework\App; use OCP\AppFramework\Bootstrap\IBootContext; use OCP\AppFramework\Bootstrap\IBootstrap; use OCP\AppFramework\Bootstrap\IRegistrationContext; -use OCP\Authentication\TwoFactorAuth\IProvider; -use OCP\Console\ConsoleEvent; +use OCP\Authentication\TwoFactorAuth\TwoFactorProviderForUserDisabled; +use OCP\Authentication\TwoFactorAuth\TwoFactorProviderForUserEnabled; +use OCP\Console\ConsoleEventV2; +use OCP\Group\Events\GroupCreatedEvent; +use OCP\Group\Events\GroupDeletedEvent; +use OCP\Group\Events\UserAddedEvent; +use OCP\Group\Events\UserRemovedEvent; use OCP\IConfig; -use OCP\IGroupManager; -use OCP\IPreview; use OCP\IServerContainer; use OCP\IUserSession; use OCP\Log\Audit\CriticalActionPerformedEvent; use OCP\Log\ILogFactory; +use OCP\Preview\BeforePreviewFetchedEvent; use OCP\Share; +use OCP\Share\Events\ShareCreatedEvent; +use OCP\Share\Events\ShareDeletedEvent; +use OCP\User\Events\BeforeUserLoggedInEvent; +use OCP\User\Events\BeforeUserLoggedOutEvent; +use OCP\User\Events\PasswordUpdatedEvent; +use OCP\User\Events\UserChangedEvent; +use OCP\User\Events\UserCreatedEvent; +use OCP\User\Events\UserDeletedEvent; +use OCP\User\Events\UserLoggedInEvent; +use OCP\User\Events\UserLoggedInWithCookieEvent; use OCP\Util; use Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; -use Symfony\Component\EventDispatcher\EventDispatcherInterface; -use Symfony\Component\EventDispatcher\GenericEvent; class Application extends App implements IBootstrap { - /** @var LoggerInterface */ - protected $logger; + protected LoggerInterface $logger; public function __construct() { parent::__construct('admin_audit'); @@ -87,6 +101,43 @@ public function register(IRegistrationContext $context): void { }); $context->registerEventListener(CriticalActionPerformedEvent::class, CriticalActionPerformedEventListener::class); + + // User management events + $context->registerEventListener(UserCreatedEvent::class, UserManagementEventListener::class); + $context->registerEventListener(UserDeletedEvent::class, UserManagementEventListener::class); + $context->registerEventListener(UserChangedEvent::class, UserManagementEventListener::class); + $context->registerEventListener(PasswordUpdatedEvent::class, UserManagementEventListener::class); + + // Group management events + $context->registerEventListener(UserAddedEvent::class, GroupManagementEventListener::class); + $context->registerEventListener(UserRemovedEvent::class, GroupManagementEventListener::class); + $context->registerEventListener(GroupCreatedEvent::class, GroupManagementEventListener::class); + $context->registerEventListener(GroupDeletedEvent::class, GroupManagementEventListener::class); + + // Sharing events + $context->registerEventListener(ShareCreatedEvent::class, SharingEventListener::class); + $context->registerEventListener(ShareDeletedEvent::class, SharingEventListener::class); + + // Auth events + $context->registerEventListener(BeforeUserLoggedInEvent::class, AuthEventListener::class); + $context->registerEventListener(UserLoggedInWithCookieEvent::class, AuthEventListener::class); + $context->registerEventListener(UserLoggedInEvent::class, AuthEventListener::class); + $context->registerEventListener(BeforeUserLoggedOutEvent::class, AuthEventListener::class); + + // File events + $context->registerEventListener(BeforePreviewFetchedEvent::class, FileEventListener::class); + + // Security events + $context->registerEventListener(TwoFactorProviderForUserEnabled::class, SecurityEventListener::class); + $context->registerEventListener(TwoFactorProviderForUserDisabled::class, SecurityEventListener::class); + + // App management events + $context->registerEventListener(AppEnableEvent::class, AppManagementEventListener::class); + $context->registerEventListener(AppDisableEvent::class, AppManagementEventListener::class); + $context->registerEventListener(AppUpdateEvent::class, AppManagementEventListener::class); + + // Console events + $context->registerEventListener(ConsoleEventV2::class, ConsoleEventListener::class); } public function boot(IBootContext $context): void { @@ -97,118 +148,42 @@ public function boot(IBootContext $context): void { * TODO: once the hooks are migrated to lazy events, this should be done * in \OCA\AdminAudit\AppInfo\Application::register */ - $this->registerHooks($logger, $context->getServerContainer()); + $this->registerLegacyHooks($logger, $context->getServerContainer()); } /** * Register hooks in order to log them */ - private function registerHooks(IAuditLogger $logger, + private function registerLegacyHooks(IAuditLogger $logger, IServerContainer $serverContainer): void { - $this->userManagementHooks($logger, $serverContainer->get(IUserSession::class)); - $this->groupHooks($logger, $serverContainer->get(IGroupManager::class)); - $this->authHooks($logger); - - /** @var EventDispatcherInterface $eventDispatcher */ - $eventDispatcher = $serverContainer->get(EventDispatcherInterface::class); - $this->consoleHooks($logger, $eventDispatcher); - $this->appHooks($logger, $eventDispatcher); + $this->userManagementLegacyHooks($logger, $serverContainer->get(IUserSession::class)); - $this->sharingHooks($logger); + $this->sharingLegacyHooks($logger); - $this->fileHooks($logger, $eventDispatcher); + $this->fileHooks($logger); $this->trashbinHooks($logger); $this->versionsHooks($logger); - - $this->securityHooks($logger, $eventDispatcher); } - private function userManagementHooks(IAuditLogger $logger, + private function userManagementLegacyHooks(IAuditLogger $logger, IUserSession $userSession): void { $userActions = new UserManagement($logger); - - Util::connectHook('OC_User', 'post_createUser', $userActions, 'create'); - Util::connectHook('OC_User', 'post_deleteUser', $userActions, 'delete'); - Util::connectHook('OC_User', 'changeUser', $userActions, 'change'); - assert($userSession instanceof UserSession); - $userSession->listen('\OC\User', 'postSetPassword', [$userActions, 'setPassword']); $userSession->listen('\OC\User', 'assignedUserId', [$userActions, 'assign']); $userSession->listen('\OC\User', 'postUnassignedUserId', [$userActions, 'unassign']); } - private function groupHooks(IAuditLogger $logger, - IGroupManager $groupManager): void { - $groupActions = new GroupManagement($logger); - - assert($groupManager instanceof GroupManager); - $groupManager->listen('\OC\Group', 'postRemoveUser', [$groupActions, 'removeUser']); - $groupManager->listen('\OC\Group', 'postAddUser', [$groupActions, 'addUser']); - $groupManager->listen('\OC\Group', 'postDelete', [$groupActions, 'deleteGroup']); - $groupManager->listen('\OC\Group', 'postCreate', [$groupActions, 'createGroup']); - } - - private function sharingHooks(IAuditLogger $logger): void { + private function sharingLegacyHooks(IAuditLogger $logger): void { $shareActions = new Sharing($logger); - Util::connectHook(Share::class, 'post_shared', $shareActions, 'shared'); - Util::connectHook(Share::class, 'post_unshare', $shareActions, 'unshare'); - Util::connectHook(Share::class, 'post_unshareFromSelf', $shareActions, 'unshare'); Util::connectHook(Share::class, 'post_update_permissions', $shareActions, 'updatePermissions'); Util::connectHook(Share::class, 'post_update_password', $shareActions, 'updatePassword'); Util::connectHook(Share::class, 'post_set_expiration_date', $shareActions, 'updateExpirationDate'); Util::connectHook(Share::class, 'share_link_access', $shareActions, 'shareAccessed'); } - private function authHooks(IAuditLogger $logger): void { - $authActions = new Auth($logger); - - Util::connectHook('OC_User', 'pre_login', $authActions, 'loginAttempt'); - Util::connectHook('OC_User', 'post_login', $authActions, 'loginSuccessful'); - Util::connectHook('OC_User', 'logout', $authActions, 'logout'); - } - - private function appHooks(IAuditLogger $logger, - EventDispatcherInterface $eventDispatcher): void { - $eventDispatcher->addListener(ManagerEvent::EVENT_APP_ENABLE, function (ManagerEvent $event) use ($logger) { - $appActions = new AppManagement($logger); - $appActions->enableApp($event->getAppID()); - }); - $eventDispatcher->addListener(ManagerEvent::EVENT_APP_ENABLE_FOR_GROUPS, function (ManagerEvent $event) use ($logger) { - $appActions = new AppManagement($logger); - $appActions->enableAppForGroups($event->getAppID(), $event->getGroups()); - }); - $eventDispatcher->addListener(ManagerEvent::EVENT_APP_DISABLE, function (ManagerEvent $event) use ($logger) { - $appActions = new AppManagement($logger); - $appActions->disableApp($event->getAppID()); - }); - } - - private function consoleHooks(IAuditLogger $logger, - EventDispatcherInterface $eventDispatcher): void { - $eventDispatcher->addListener(ConsoleEvent::EVENT_RUN, function (ConsoleEvent $event) use ($logger) { - $appActions = new Console($logger); - $appActions->runCommand($event->getArguments()); - }); - } - - private function fileHooks(IAuditLogger $logger, - EventDispatcherInterface $eventDispatcher): void { + private function fileHooks(IAuditLogger $logger): void { $fileActions = new Files($logger); - $eventDispatcher->addListener( - IPreview::EVENT, - function (GenericEvent $event) use ($fileActions) { - /** @var File $file */ - $file = $event->getSubject(); - $fileActions->preview([ - 'path' => mb_substr($file->getInternalPath(), 5), - 'width' => $event->getArguments()['width'], - 'height' => $event->getArguments()['height'], - 'crop' => $event->getArguments()['crop'], - 'mode' => $event->getArguments()['mode'] - ]); - } - ); Util::connectHook( Filesystem::CLASSNAME, @@ -265,16 +240,4 @@ private function trashbinHooks(IAuditLogger $logger): void { Util::connectHook('\OCP\Trashbin', 'preDelete', $trashActions, 'delete'); Util::connectHook('\OCA\Files_Trashbin\Trashbin', 'post_restore', $trashActions, 'restore'); } - - private function securityHooks(IAuditLogger $logger, - EventDispatcherInterface $eventDispatcher): void { - $eventDispatcher->addListener(IProvider::EVENT_SUCCESS, function (GenericEvent $event) use ($logger) { - $security = new Security($logger); - $security->twofactorSuccess($event->getSubject(), $event->getArguments()); - }); - $eventDispatcher->addListener(IProvider::EVENT_FAILED, function (GenericEvent $event) use ($logger) { - $security = new Security($logger); - $security->twofactorFailed($event->getSubject(), $event->getArguments()); - }); - } } diff --git a/apps/admin_audit/lib/Listener/AppManagementEventListener.php b/apps/admin_audit/lib/Listener/AppManagementEventListener.php new file mode 100644 index 0000000000000..d9b0e8a264762 --- /dev/null +++ b/apps/admin_audit/lib/Listener/AppManagementEventListener.php @@ -0,0 +1,73 @@ + + * + * @author Thomas Citharel + * + * @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 . + * + */ +namespace OCA\AdminAudit\Listener; + +use OCA\AdminAudit\Actions\Action; +use OCP\App\Events\AppDisableEvent; +use OCP\App\Events\AppEnableEvent; +use OCP\App\Events\AppUpdateEvent; +use OCP\EventDispatcher\Event; +use OCP\EventDispatcher\IEventListener; + +class AppManagementEventListener extends Action implements IEventListener { + public function handle(Event $event): void { + if ($event instanceof AppEnableEvent) { + $this->appEnable($event); + } elseif ($event instanceof AppDisableEvent) { + $this->appDisable($event); + } elseif ($event instanceof AppUpdateEvent) { + $this->appUpdate($event); + } + } + + private function appEnable(AppEnableEvent $event) { + if (empty($event->getGroupIds())) { + $this->log('App "%s" enabled', + ['app' => $event->getAppId()], + ['app'] + ); + } else { + $this->log('App "%1$s" enabled for groups: %2$s', + ['app' => $event->getAppId(), 'groups' => implode(', ', $event->getGroupIds())], + ['app', 'groups'] + ); + } + } + + private function appDisable(AppDisableEvent $event) { + $this->log('App "%s" disabled', + ['app' => $event->getAppId()], + ['app'] + ); + } + + private function appUpdate(AppUpdateEvent $event) { + $this->log('App "%s" updated', + ['app' => $event->getAppId()], + ['app'] + ); + } +} diff --git a/apps/admin_audit/lib/Listener/AuthEventListener.php b/apps/admin_audit/lib/Listener/AuthEventListener.php new file mode 100644 index 0000000000000..4c2dfd3cc1a05 --- /dev/null +++ b/apps/admin_audit/lib/Listener/AuthEventListener.php @@ -0,0 +1,80 @@ + + * + * @author Thomas Citharel + * + * @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 . + * + */ +namespace OCA\AdminAudit\Listener; + +use OCA\AdminAudit\Actions\Action; +use OCP\EventDispatcher\Event; +use OCP\EventDispatcher\IEventListener; +use OCP\User\Events\BeforeUserLoggedInEvent; +use OCP\User\Events\BeforeUserLoggedOutEvent; +use OCP\User\Events\UserLoggedInEvent; +use OCP\User\Events\UserLoggedInWithCookieEvent; + +class AuthEventListener extends Action implements IEventListener { + public function handle(Event $event): void { + if ($event instanceof BeforeUserLoggedInEvent) { + $this->beforeUserLoggedIn($event); + } elseif ($event instanceof UserLoggedInWithCookieEvent || $event instanceof UserLoggedInEvent) { + $this->userLoggedIn($event); + } elseif ($event instanceof BeforeUserLoggedOutEvent) { + $this->beforeUserLogout($event); + } + } + + private function beforeUserLoggedIn(BeforeUserLoggedInEvent $event) { + $this->log( + 'Login attempt: "%s"', + [ + 'uid' => $event->getUsername() + ], + [ + 'uid', + ], + true + ); + } + + private function userLoggedIn(UserLoggedInWithCookieEvent|UserLoggedInEvent $event) { + $this->log( + 'Login successful: "%s"', + [ + 'uid' => $event->getUser()->getUID() + ], + [ + 'uid', + ], + true + ); + } + + private function beforeUserLogout(BeforeUserLoggedOutEvent $event) { + $this->log( + 'Logout occurred', + [], + [] + ); + } +} diff --git a/apps/admin_audit/lib/Actions/Console.php b/apps/admin_audit/lib/Listener/ConsoleEventListener.php similarity index 64% rename from apps/admin_audit/lib/Actions/Console.php rename to apps/admin_audit/lib/Listener/ConsoleEventListener.php index a69d1f5ff82f3..c468a7c7e83eb 100644 --- a/apps/admin_audit/lib/Actions/Console.php +++ b/apps/admin_audit/lib/Listener/ConsoleEventListener.php @@ -3,11 +3,9 @@ declare(strict_types=1); /** - * @copyright Copyright (c) 2017 Joas Schilling + * @copyright Copyright (c) 2023 Thomas Citharel * - * @author Daniel Kesselberg - * @author Joas Schilling - * @author Roeland Jago Douma + * @author Thomas Citharel * * @license GNU AGPL version 3 or any later version * @@ -25,13 +23,22 @@ * along with this program. If not, see . * */ -namespace OCA\AdminAudit\Actions; +namespace OCA\AdminAudit\Listener; -class Console extends Action { - /** - * @param $arguments - */ - public function runCommand(array $arguments): void { +use OCA\AdminAudit\Actions\Action; +use OCP\Console\ConsoleEventV2; +use OCP\EventDispatcher\Event; +use OCP\EventDispatcher\IEventListener; + +class ConsoleEventListener extends Action implements IEventListener { + public function handle(Event $event): void { + if ($event instanceof ConsoleEventV2) { + $this->runCommand($event); + } + } + + private function runCommand(ConsoleEventV2 $event) { + $arguments = $event->getArguments(); if (!isset($arguments[1]) || $arguments[1] === '_completion') { // Don't log autocompletion return; diff --git a/apps/admin_audit/lib/Listener/FileEventListener.php b/apps/admin_audit/lib/Listener/FileEventListener.php new file mode 100644 index 0000000000000..58d2bf75f00a1 --- /dev/null +++ b/apps/admin_audit/lib/Listener/FileEventListener.php @@ -0,0 +1,61 @@ + + * + * @author Thomas Citharel + * + * @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 . + * + */ +namespace OCA\AdminAudit\Listener; + +use OCA\AdminAudit\Actions\Action; +use OCP\EventDispatcher\Event; +use OCP\EventDispatcher\IEventListener; +use OCP\Preview\BeforePreviewFetchedEvent; + +class FileEventListener extends Action implements IEventListener { + public function handle(Event $event): void { + if ($event instanceof BeforePreviewFetchedEvent) { + $this->beforePreviewFetched($event); + } + } + + private function beforePreviewFetched(BeforePreviewFetchedEvent $event) { + $file = $event->getNode(); + + $this->log( + 'Preview accessed: "%s" (width: "%s", height: "%s" crop: "%s", mode: "%s")', + [ + 'path' => mb_substr($file->getInternalPath(), 5), + 'width' => $event->getWidth(), + 'height' => $event->getHeight(), + 'crop' => $event->isCrop(), + 'mode' => $event->getMode(), + ], + [ + 'path', + 'width', + 'height', + 'crop', + 'mode' + ] + ); + } +} diff --git a/apps/admin_audit/lib/Listener/GroupManagementEventListener.php b/apps/admin_audit/lib/Listener/GroupManagementEventListener.php new file mode 100644 index 0000000000000..f44d51c469089 --- /dev/null +++ b/apps/admin_audit/lib/Listener/GroupManagementEventListener.php @@ -0,0 +1,94 @@ + + * + * @author Thomas Citharel + * + * @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 . + * + */ +namespace OCA\AdminAudit\Listener; + +use OCA\AdminAudit\Actions\Action; +use OCP\EventDispatcher\Event; +use OCP\EventDispatcher\IEventListener; +use OCP\Group\Events\GroupCreatedEvent; +use OCP\Group\Events\GroupDeletedEvent; +use OCP\Group\Events\UserAddedEvent; +use OCP\Group\Events\UserRemovedEvent; + +class GroupManagementEventListener extends Action implements IEventListener { + public function handle(Event $event): void { + if ($event instanceof UserAddedEvent) { + $this->userAdded($event); + } elseif ($event instanceof UserRemovedEvent) { + $this->userRemoved($event); + } elseif ($event instanceof GroupCreatedEvent) { + $this->groupCreated($event); + } elseif ($event instanceof GroupDeletedEvent) { + $this->groupDeleted($event); + } + } + + private function userAdded(UserAddedEvent $event) { + $this->log('User "%s" added to group "%s"', + [ + 'group' => $event->getGroup()->getGID(), + 'user' => $event->getUser()->getUID() + ], + [ + 'user', 'group' + ] + ); + } + + private function userRemoved(UserRemovedEvent $event) { + $this->log('User "%s" removed from group "%s"', + [ + 'group' => $event->getGroup()->getGID(), + 'user' => $event->getUser()->getUID() + ], + [ + 'user', 'group' + ] + ); + } + + private function groupCreated(GroupCreatedEvent $event) { + $this->log('Group created: "%s"', + [ + 'group' => $event->getGroup()->getGID() + ], + [ + 'group' + ] + ); + } + + private function groupDeleted(GroupDeletedEvent $event) { + $this->log('Group deleted: "%s"', + [ + 'group' => $event->getGroup()->getGID() + ], + [ + 'group' + ] + ); + } +} diff --git a/apps/admin_audit/lib/Listener/SecurityEventListener.php b/apps/admin_audit/lib/Listener/SecurityEventListener.php new file mode 100644 index 0000000000000..660e10288ba7f --- /dev/null +++ b/apps/admin_audit/lib/Listener/SecurityEventListener.php @@ -0,0 +1,74 @@ + + * + * @author Thomas Citharel + * + * @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 . + * + */ +namespace OCA\AdminAudit\Listener; + +use OCA\AdminAudit\Actions\Action; +use OCP\Authentication\TwoFactorAuth\TwoFactorProviderForUserDisabled; +use OCP\Authentication\TwoFactorAuth\TwoFactorProviderForUserEnabled; +use OCP\EventDispatcher\Event; +use OCP\EventDispatcher\IEventListener; + +class SecurityEventListener extends Action implements IEventListener { + public function handle(Event $event): void { + if ($event instanceof TwoFactorProviderForUserEnabled) { + $this->twoFactorProviderForUserEnabled($event); + } elseif ($event instanceof TwoFactorProviderForUserDisabled) { + $this->twoFactorProviderForUserDisabled($event); + } + } + + private function twoFactorProviderForUserEnabled(TwoFactorProviderForUserEnabled $event) { + $this->log( + 'Successful two factor attempt by user %s (%s) with provider %s', + [ + 'uid' => $event->getUser()->getUID(), + 'displayName' => $event->getUser()->getDisplayName(), + 'provider' => $event->getProvider()->getDisplayName() + ], + [ + 'displayName', + 'uid', + 'provider', + ] + ); + } + + private function twoFactorProviderForUserDisabled(TwoFactorProviderForUserDisabled $event) { + $this->log( + 'Failed two factor attempt by user %s (%s) with provider %s', + [ + 'uid' => $event->getUser()->getUID(), + 'displayName' => $event->getUser()->getDisplayName(), + 'provider' => $event->getProvider()->getDisplayName() + ], + [ + 'displayName', + 'uid', + 'provider', + ] + ); + } +} diff --git a/apps/admin_audit/lib/Listener/SharingEventListener.php b/apps/admin_audit/lib/Listener/SharingEventListener.php new file mode 100644 index 0000000000000..2a6c90b00f108 --- /dev/null +++ b/apps/admin_audit/lib/Listener/SharingEventListener.php @@ -0,0 +1,305 @@ + + * + * @author Thomas Citharel + * + * @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 . + * + */ +namespace OCA\AdminAudit\Listener; + +use OCA\AdminAudit\Actions\Action; +use OCP\EventDispatcher\Event; +use OCP\EventDispatcher\IEventListener; +use OCP\Share\Events\ShareCreatedEvent; +use OCP\Share\Events\ShareDeletedEvent; +use OCP\Share\IShare; + +class SharingEventListener extends Action implements IEventListener { + public function handle(Event $event): void { + if ($event instanceof ShareCreatedEvent) { + $this->shareCreated($event); + } elseif ($event instanceof ShareDeletedEvent) { + $this->shareDeleted($event); + } + } + + private function shareCreated(ShareCreatedEvent $event) { + $share = $event->getShare(); + + $params = [ + 'itemType' => $share->getNodeType(), + 'path' => $share->getNode()->getPath(), + 'itemSource' => $share->getNode()->getId(), + 'shareWith' => $share->getSharedWith(), + 'permissions' => $share->getPermissions(), + 'id' => $share->getId() + ]; + + match ($share->getShareType()) { + IShare::TYPE_LINK => $this->log( + 'The %s "%s" with ID "%s" has been shared via link with permissions "%s" (Share ID: %s)', + $params, + [ + 'itemType', + 'path', + 'itemSource', + 'permissions', + 'id', + ] + ), + IShare::TYPE_USER => $this->log( + 'The %s "%s" with ID "%s" has been shared to the user "%s" with permissions "%s" (Share ID: %s)', + $params, + [ + 'itemType', + 'path', + 'itemSource', + 'shareWith', + 'permissions', + 'id', + ] + ), + IShare::TYPE_GROUP => $this->log( + 'The %s "%s" with ID "%s" has been shared to the group "%s" with permissions "%s" (Share ID: %s)', + $params, + [ + 'itemType', + 'path', + 'itemSource', + 'shareWith', + 'permissions', + 'id', + ] + ), + IShare::TYPE_ROOM => $this->log( + 'The %s "%s" with ID "%s" has been shared to the room "%s" with permissions "%s" (Share ID: %s)', + $params, + [ + 'itemType', + 'path', + 'itemSource', + 'shareWith', + 'permissions', + 'id', + ] + ), + IShare::TYPE_EMAIL => $this->log( + 'The %s "%s" with ID "%s" has been shared to the email recipient "%s" with permissions "%s" (Share ID: %s)', + $params, + [ + 'itemType', + 'path', + 'itemSource', + 'shareWith', + 'permissions', + 'id', + ] + ), + IShare::TYPE_CIRCLE => $this->log( + 'The %s "%s" with ID "%s" has been shared to the circle "%s" with permissions "%s" (Share ID: %s)', + $params, + [ + 'itemType', + 'path', + 'itemSource', + 'shareWith', + 'permissions', + 'id', + ] + ), + IShare::TYPE_REMOTE => $this->log( + 'The %s "%s" with ID "%s" has been shared to the remote user "%s" with permissions "%s" (Share ID: %s)', + $params, + [ + 'itemType', + 'path', + 'itemSource', + 'shareWith', + 'permissions', + 'id', + ] + ), + IShare::TYPE_REMOTE_GROUP => $this->log( + 'The %s "%s" with ID "%s" has been shared to the remote group "%s" with permissions "%s" (Share ID: %s)', + $params, + [ + 'itemType', + 'path', + 'itemSource', + 'shareWith', + 'permissions', + 'id', + ] + ), + IShare::TYPE_DECK => $this->log( + 'The %s "%s" with ID "%s" has been shared to the deck card "%s" with permissions "%s" (Share ID: %s)', + $params, + [ + 'itemType', + 'path', + 'itemSource', + 'shareWith', + 'permissions', + 'id', + ] + ), + IShare::TYPE_SCIENCEMESH => $this->log( + 'The %s "%s" with ID "%s" has been shared to the sciencemesh user "%s" with permissions "%s" (Share ID: %s)', + $params, + [ + 'itemType', + 'path', + 'itemSource', + 'shareWith', + 'permissions', + 'id', + ] + ), + default => null + }; + } + + private function shareDeleted(ShareDeletedEvent $event) { + $share = $event->getShare(); + + $params = [ + 'itemType' => $share->getNodeType(), + 'path' => $share->getNode()->getPath(), + 'itemSource' => $share->getNode()->getId(), + 'shareWith' => $share->getSharedWith(), + 'permissions' => $share->getPermissions(), + 'id' => $share->getId() + ]; + + match ($share->getShareType()) { + IShare::TYPE_LINK => $this->log( + 'The %s "%s" with ID "%s" has been unshared (Share ID: %s)', + $params, + [ + 'itemType', + 'fileTarget', + 'itemSource', + 'id', + ] + ), + IShare::TYPE_USER => $this->log( + 'The %s "%s" with ID "%s" has been unshared from the user "%s" (Share ID: %s)', + $params, + [ + 'itemType', + 'fileTarget', + 'itemSource', + 'shareWith', + 'id', + ] + ), + IShare::TYPE_GROUP => $this->log( + 'The %s "%s" with ID "%s" has been unshared from the group "%s" (Share ID: %s)', + $params, + [ + 'itemType', + 'fileTarget', + 'itemSource', + 'shareWith', + 'id', + ] + ), + IShare::TYPE_ROOM => $this->log( + 'The %s "%s" with ID "%s" has been unshared from the room "%s" (Share ID: %s)', + $params, + [ + 'itemType', + 'fileTarget', + 'itemSource', + 'shareWith', + 'id', + ] + ), + IShare::TYPE_EMAIL => $this->log( + 'The %s "%s" with ID "%s" has been unshared from the email recipient "%s" (Share ID: %s)', + $params, + [ + 'itemType', + 'fileTarget', + 'itemSource', + 'shareWith', + 'id', + ] + ), + IShare::TYPE_CIRCLE => $this->log( + 'The %s "%s" with ID "%s" has been unshared from the circle "%s" (Share ID: %s)', + $params, + [ + 'itemType', + 'fileTarget', + 'itemSource', + 'shareWith', + 'id', + ] + ), + IShare::TYPE_REMOTE => $this->log( + 'The %s "%s" with ID "%s" has been unshared from the remote user "%s" (Share ID: %s)', + $params, + [ + 'itemType', + 'fileTarget', + 'itemSource', + 'shareWith', + 'id', + ] + ), + IShare::TYPE_REMOTE_GROUP => $this->log( + 'The %s "%s" with ID "%s" has been unshared from the remote group "%s" (Share ID: %s)', + $params, + [ + 'itemType', + 'fileTarget', + 'itemSource', + 'shareWith', + 'id', + ] + ), + IShare::TYPE_DECK => $this->log( + 'The %s "%s" with ID "%s" has been unshared from the deck card "%s" (Share ID: %s)', + $params, + [ + 'itemType', + 'fileTarget', + 'itemSource', + 'shareWith', + 'id', + ] + ), + IShare::TYPE_SCIENCEMESH => $this->log( + 'The %s "%s" with ID "%s" has been unshared from the sciencemesh user "%s" (Share ID: %s)', + $params, + [ + 'itemType', + 'fileTarget', + 'itemSource', + 'shareWith', + 'id', + ] + ), + default => null + }; + } +} diff --git a/apps/admin_audit/lib/Listener/UserManagementEventListener.php b/apps/admin_audit/lib/Listener/UserManagementEventListener.php new file mode 100644 index 0000000000000..243f9c7e42f63 --- /dev/null +++ b/apps/admin_audit/lib/Listener/UserManagementEventListener.php @@ -0,0 +1,111 @@ + + * + * @author Thomas Citharel + * + * @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 . + * + */ +namespace OCA\AdminAudit\Listener; + +use OCA\AdminAudit\Actions\Action; +use OCP\EventDispatcher\Event; +use OCP\EventDispatcher\IEventListener; +use OCP\User\Events\PasswordUpdatedEvent; +use OCP\User\Events\UserChangedEvent; +use OCP\User\Events\UserCreatedEvent; +use OCP\User\Events\UserDeletedEvent; + +class UserManagementEventListener extends Action implements IEventListener { + public function handle(Event $event): void { + if ($event instanceof UserCreatedEvent) { + $this->userCreated($event); + } elseif ($event instanceof UserDeletedEvent) { + $this->userDeleted($event); + } elseif ($event instanceof UserChangedEvent) { + $this->userChanged($event); + } elseif ($event instanceof PasswordUpdatedEvent) { + $this->passwordUpdated($event); + } + } + + private function userCreated(UserCreatedEvent $event) { + $this->log( + 'User created: "%s"', + [ + 'uid' => $event->getUid() + ], + [ + 'uid', + ] + ); + } + + private function userDeleted(UserDeletedEvent $event) { + $this->log( + 'User deleted: "%s"', + [ + 'uid' => $event->getUser()->getUID() + ], + [ + 'uid', + ] + ); + } + + private function userChanged(UserChangedEvent $event) { + switch ($event->getFeature()) { + case 'enabled': + $this->log( + $event->getValue() === true + ? 'User enabled: "%s"' + : 'User disabled: "%s"', + ['user' => $event->getUser()->getUID()], + [ + 'user', + ] + ); + break; + case 'eMailAddress': + $this->log( + 'Email address changed for user %s', + ['user' => $event->getUser()->getUID()], + [ + 'user', + ] + ); + break; + } + } + + private function passwordUpdated(PasswordUpdatedEvent $event) { + if ($event->getUser()->getBackendClassName() === 'Database') { + $this->log( + 'Password of user "%s" has been changed', + [ + 'user' => $event->getUser()->getUID(), + ], + [ + 'user', + ] + ); + } + } +} diff --git a/apps/admin_audit/tests/Actions/SecurityTest.php b/apps/admin_audit/tests/Listener/SecurityEventListenerTest.php similarity index 66% rename from apps/admin_audit/tests/Actions/SecurityTest.php rename to apps/admin_audit/tests/Listener/SecurityEventListenerTest.php index d45cbb75a6413..f9f236c4998f9 100644 --- a/apps/admin_audit/tests/Actions/SecurityTest.php +++ b/apps/admin_audit/tests/Listener/SecurityEventListenerTest.php @@ -7,6 +7,7 @@ * * @author Morris Jobke * @author Roeland Jago Douma + * @author Thomas Citharel * * @license GNU AGPL version 3 or any later version * @@ -26,30 +27,38 @@ */ namespace OCA\AdminAudit\Tests\Actions; -use OCA\AdminAudit\Actions\Security; +use OCA\AdminAudit\Listener\SecurityEventListener; +use OCP\Authentication\TwoFactorAuth\IProvider; +use OCP\Authentication\TwoFactorAuth\TwoFactorProviderForUserDisabled; +use OCP\Authentication\TwoFactorAuth\TwoFactorProviderForUserEnabled; use OCP\IUser; use OCA\AdminAudit\AuditLogger; +use PHPUnit\Framework\MockObject\MockObject; use Test\TestCase; -class SecurityTest extends TestCase { - /** @var AuditLogger|\PHPUnit\Framework\MockObject\MockObject */ +class SecurityEventListenerTest extends TestCase { + /** @var AuditLogger|MockObject */ private $logger; - /** @var Security */ - private $security; + private SecurityEventListener $security; - /** @var IUser|\PHPUnit\Framework\MockObject\MockObject */ + /** @var IUser|MockObject */ private $user; + /** @var IProvider|(IProvider&MockObject)|MockObject */ + private $provider; + protected function setUp(): void { parent::setUp(); $this->logger = $this->createMock(AuditLogger::class); - $this->security = new Security($this->logger); + $this->security = new SecurityEventListener($this->logger); $this->user = $this->createMock(IUser::class); $this->user->method('getUID')->willReturn('myuid'); $this->user->method('getDisplayName')->willReturn('mydisplayname'); + $this->provider = $this->createMock(IProvider::class); + $this->provider->method('getDisplayName')->willReturn('myprovider'); } public function testTwofactorFailed() { @@ -60,7 +69,7 @@ public function testTwofactorFailed() { ['app' => 'admin_audit'] ); - $this->security->twofactorFailed($this->user, ['provider' => 'myprovider']); + $this->security->handle(new TwoFactorProviderForUserEnabled($this->user, $this->provider)); } public function testTwofactorSuccess() { @@ -71,6 +80,6 @@ public function testTwofactorSuccess() { ['app' => 'admin_audit'] ); - $this->security->twofactorSuccess($this->user, ['provider' => 'myprovider']); + $this->security->handle(new TwoFactorProviderForUserDisabled($this->user, $this->provider)); } }