Merge pull request #47896 from nextcloud/fix/resiliant-user-removal
fix: Make user removal more resilientpull/48623/head
commit
3095a92551
@ -0,0 +1,30 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
namespace OC\Repair; |
||||
|
||||
use OC\User\BackgroundJobs\CleanupDeletedUsers; |
||||
use OCP\BackgroundJob\IJobList; |
||||
use OCP\Migration\IOutput; |
||||
use OCP\Migration\IRepairStep; |
||||
|
||||
class AddCleanupDeletedUsersBackgroundJob implements IRepairStep { |
||||
private IJobList $jobList; |
||||
|
||||
public function __construct(IJobList $jobList) { |
||||
$this->jobList = $jobList; |
||||
} |
||||
|
||||
public function getName(): string { |
||||
return 'Add cleanup-deleted-users background job'; |
||||
} |
||||
|
||||
public function run(IOutput $output) { |
||||
$this->jobList->add(CleanupDeletedUsers::class); |
||||
} |
||||
} |
@ -0,0 +1,64 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
namespace OC\User\BackgroundJobs; |
||||
|
||||
use OC\User\Manager; |
||||
use OC\User\PartiallyDeletedUsersBackend; |
||||
use OC\User\User; |
||||
use OCP\AppFramework\Utility\ITimeFactory; |
||||
use OCP\BackgroundJob\IJob; |
||||
use OCP\BackgroundJob\TimedJob; |
||||
use OCP\EventDispatcher\IEventDispatcher; |
||||
use OCP\IConfig; |
||||
use Psr\Log\LoggerInterface; |
||||
|
||||
class CleanupDeletedUsers extends TimedJob { |
||||
public function __construct( |
||||
ITimeFactory $time, |
||||
private Manager $userManager, |
||||
private IConfig $config, |
||||
private LoggerInterface $logger, |
||||
) { |
||||
parent::__construct($time); |
||||
$this->setTimeSensitivity(IJob::TIME_INSENSITIVE); |
||||
$this->setInterval(24 * 3600); |
||||
} |
||||
|
||||
protected function run($argument): void { |
||||
$backend = new PartiallyDeletedUsersBackend($this->config); |
||||
$users = $backend->getUsers(); |
||||
|
||||
if (empty($users)) { |
||||
$this->logger->debug('No failed deleted users found.'); |
||||
return; |
||||
} |
||||
|
||||
foreach ($users as $userId) { |
||||
if ($this->userManager->userExists($userId)) { |
||||
$this->logger->info('Skipping user {userId}, marked as deleted, as they still exists in user backend.', ['userId' => $userId]); |
||||
$backend->unmarkUser($userId); |
||||
continue; |
||||
} |
||||
|
||||
try { |
||||
$user = new User( |
||||
$userId, |
||||
$backend, |
||||
\OCP\Server::get(IEventDispatcher::class), |
||||
$this->userManager, |
||||
$this->config, |
||||
); |
||||
$user->delete(); |
||||
$this->logger->info('Cleaned up deleted user {userId}', ['userId' => $userId]); |
||||
} catch (\Throwable $error) { |
||||
$this->logger->warning('Could not cleanup deleted user {userId}', ['userId' => $userId, 'exception' => $error]); |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,56 @@ |
||||
<?php |
||||
|
||||
/** |
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
namespace OC\User; |
||||
|
||||
use OCP\IConfig; |
||||
use OCP\IUserBackend; |
||||
use OCP\User\Backend\IGetHomeBackend; |
||||
|
||||
/** |
||||
* This is a "fake" backend for users that were deleted, |
||||
* but not properly removed from Nextcloud (e.g. an exception occurred). |
||||
* This backend is only needed because some APIs in user-deleted-events require a "real" user with backend. |
||||
*/ |
||||
class PartiallyDeletedUsersBackend extends Backend implements IGetHomeBackend, IUserBackend { |
||||
|
||||
public function __construct( |
||||
private IConfig $config, |
||||
) { |
||||
} |
||||
|
||||
public function deleteUser($uid): bool { |
||||
// fake true, deleting failed users is automatically handled by User::delete() |
||||
return true; |
||||
} |
||||
|
||||
public function getBackendName(): string { |
||||
return 'deleted users'; |
||||
} |
||||
|
||||
public function userExists($uid) { |
||||
return $this->config->getUserValue($uid, 'core', 'deleted') === 'true'; |
||||
} |
||||
|
||||
public function getHome(string $uid): string|false { |
||||
return $this->config->getUserValue($uid, 'core', 'deleted.home-path') ?: false; |
||||
} |
||||
|
||||
public function getUsers($search = '', $limit = null, $offset = null) { |
||||
return $this->config->getUsersForUserValue('core', 'deleted', 'true'); |
||||
} |
||||
|
||||
/** |
||||
* Unmark a user as deleted. |
||||
* This typically the case if the user deletion failed in the backend but before the backend deleted the user, |
||||
* meaning the user still exists so we unmark them as it still can be accessed (and deleted) normally. |
||||
*/ |
||||
public function unmarkUser(string $userId): void { |
||||
$this->config->deleteUserValue($userId, 'core', 'deleted'); |
||||
$this->config->deleteUserValue($userId, 'core', 'deleted.home-path'); |
||||
} |
||||
|
||||
} |
Loading…
Reference in new issue