Skip to content

Commit

Permalink
feat: Move transfer to background job
Browse files Browse the repository at this point in the history
Signed-off-by: Christopher Ng <chrng8@gmail.com>
  • Loading branch information
Pytal committed Jun 21, 2024
1 parent 9d8420f commit f252c1a
Show file tree
Hide file tree
Showing 5 changed files with 164 additions and 92 deletions.
6 changes: 3 additions & 3 deletions lib/BackgroundJob/TransferJob.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
use OCA\Guests\AppInfo\Application;
use OCA\Guests\Db\Transfer;
use OCA\Guests\Db\TransferMapper;
use OCA\Guests\GuestManager;
use OCA\Guests\TransferService;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\BackgroundJob\QueuedJob;
use OCP\IURLGenerator;
Expand All @@ -28,7 +28,7 @@ public function __construct(
private ISecureRandom $secureRandom,
private NotificationManager $notificationManager,
private IURLGenerator $urlGenerator,
private GuestManager $guestManager,
private TransferService $transferService,
private TransferMapper $transferMapper,
private LoggerInterface $logger,
)
Expand Down Expand Up @@ -113,7 +113,7 @@ public function run($argument): void {
// TODO copy password hash to target user

try {
$this->guestManager->transfer($sourceUser, $targetUser);
$this->transferService->transfer($sourceUser, $targetUser);
$result = $sourceUser->delete();
if (!$result) {
$this->logger->error('Failed to delete user', ['userId' => $sourceUser->getUID()]);
Expand Down
76 changes: 43 additions & 33 deletions lib/Controller/UsersController.php
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Guests\Controller;

use OC\Hooks\PublicEmitter;
use OCA\Guests\Config;
use OCA\Guests\Db\Transfer;
use OCA\Guests\GuestManager;
use OCA\Guests\TransferService;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCS\OCSException;
use OCP\AppFramework\OCS\OCSNotFoundException;
use OCP\AppFramework\OCSController;
use OCP\Group\ISubAdmin;
use OCP\IGroupManager;
Expand All @@ -18,7 +25,6 @@
use OCP\IUserManager;
use OCP\IUserSession;
use OCP\Mail\IMailer;
use Psr\Log\LoggerInterface;

class UsersController extends OCSController {
public function __construct(
Expand All @@ -32,7 +38,7 @@ public function __construct(
private IUserSession $userSession,
private ISubAdmin $subAdmin,
private IGroupManager $groupManager,
private LoggerInterface $logger,
private TransferService $transferService,
) {
parent::__construct($appName, $request);
}
Expand Down Expand Up @@ -166,45 +172,49 @@ public function get(string $userId): DataResponse {
return new DataResponse($guests);
}

public function transfer(string $email, string $userId, string $password): DataResponse {
$user = $this->userManager->get($email);
if (!($user instanceof IUser)) {
throw new OCSNotFoundException();
private function getStatusMessage(string $status): string {
return match ($status) {
Transfer::STATUS_WAITING => $this->l10n->t('Waiting'),
Transfer::STATUS_STARTED => $this->l10n->t('Started'),
};
}

/**
* Transfer guest to a full account
*
* @param string $email Guest email
* @param string $userId User id for full account
*/
public function transfer(string $email, string $userId): DataResponse {
$author = $this->userSession->getUser();
if (!($author instanceof IUser)) {
return new DataResponse([], Http::STATUS_UNAUTHORIZED);
}

if (!$this->guestManager->isGuest($user)) {
return new DataResponse([], Http::STATUS_CONFLICT);
$sourceUser = $this->userManager->get($email);
if (!($sourceUser instanceof IUser)) {
return new DataResponse([], Http::STATUS_NOT_FOUND);
}

if ($this->userManager->userExists($userId)) {
throw new OCSException($this->l10n->t('User already exists'), 102);
return new DataResponse([
'message' => $this->l10n->t('User already exists')
], Http::STATUS_CONFLICT);
}

$newUser = $this->userManager->createUser(
$userId,
$password,
);

if (!($newUser instanceof IUser)) {
throw new OCSException($this->l10n->t('Failed to create new user'));
if (!$this->guestManager->isGuest($sourceUser)) {
return new DataResponse([], Http::STATUS_CONFLICT);
}

$newUser->setSystemEMailAddress($email);

try {
$this->guestManager->transfer($user, $newUser);
$result = $user->delete();
if (!$result) {
$this->logger->error('Failed to delete user', [ 'userId' => $user->getUID() ]);
}
} catch (\Throwable $th) {
$this->logger->error('Failed to transfer guest', [ 'error' => $th ]);
$result = $newUser->delete(); // Rollback created user
if (!$result) {
$this->logger->error('Failed to delete user', [ 'userId' => $newUser->getUID() ]);
}
throw new OCSException($this->l10n->t('Failed to transfer guest'));
$transfer = $this->transferService->getTransfer($sourceUser, $userId);
if ($transfer instanceof Transfer) {
return new DataResponse([
'status' => $this->getStatusMessage($transfer->getStatus()),
'message' => $this->l10n->t("Guest \"{$transfer->getSource()}\" is already pending a transfer to \"{$transfer->getTarget()}\""),
], Http::STATUS_ACCEPTED);
}

$this->transferService->addTransferJob($author, $sourceUser, $userId);
return new DataResponse([], Http::STATUS_CREATED);
}
}
57 changes: 2 additions & 55 deletions lib/GuestManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,25 +21,18 @@

namespace OCA\Guests;

use OCA\Files\Exception\TransferOwnershipException;
use OCA\Files\Service\OwnershipTransferService;
use OCA\Guests\AppInfo\Application;
use OCP\AppFramework\QueryException;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IConfig;
use OCP\IDBConnection;
use OCP\IUser;
use OCP\IUserManager;
use OCP\IUserSession;
use OCP\Notification\IManager as INotificationManager;
use OCP\Security\Events\GenerateSecurePasswordEvent;
use OCP\Security\ICrypto;
use OCP\Security\ISecureRandom;
use OCP\Share\IManager;
use OCP\Share\IShare;
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;

class GuestManager {
public function __construct(
Expand All @@ -52,10 +45,8 @@ public function __construct(
private IUserSession $userSession,
private IEventDispatcher $eventDispatcher,
private IUserManager $userManager,
private ContainerInterface $container,
private LoggerInterface $logger,
private INotificationManager $notificationManager,
) {}
) {
}

/**
* @param IUser|string $user
Expand Down Expand Up @@ -182,48 +173,4 @@ public function getGuestInfo($userId): array {
}, $shares),
];
}

public function transfer(IUser $guestUser, IUser $user): void {
try {
/** @var OwnershipTransferService $ownershipTransferService */
$ownershipTransferService = $this->container->get(OwnershipTransferService::class);
} catch (QueryException $e) {
$this->logger->error('Could not resolve ownership transfer service to import guest user data', [
'exception' => $e,
]);
throw $e;
}

try {
$ownershipTransferService->transfer(
$guestUser,
$user,
'/',
null,
true,
true
);
} catch (TransferOwnershipException $e) {
$this->logger->error('Could not import guest user data', [
'exception' => $e,
]);
throw $e;
}

// Update incomming shares
$shares = $this->shareManager->getSharedWith($guestUser->getUID(), IShare::TYPE_USER);
foreach ($shares as $share) {
$share->setSharedWith($user->getUID());
$this->shareManager->updateShare($share);
}

$notification = $this->notificationManager->createNotification();
$notification
->setApp(Application::APP_ID)
->setSubject('data_migrated_to_system_user')
->setObject('user', $user->getEMailAddress())
->setDateTime(new \DateTime())
->setUser($user->getUID());
$this->notificationManager->notify($notification);
}
}
3 changes: 2 additions & 1 deletion lib/Hooks.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public function __construct(
private GuestManager $guestManager,
private UserBackend $userBackend,
private IAppContainer $container,
private TransferService $transferService,
) {}

public function handlePostShare(ShareCreatedEvent $event): void {
Expand Down Expand Up @@ -172,7 +173,7 @@ public function handleFirstLogin(UserFirstTimeLoggedInEvent $event): void {
return;
}

$this->guestManager->transfer($guestUser, $user);
$this->transferService->transfer($guestUser, $user);

if (!$this->config->getSystemValueBool('remove_guest_account_on_conversion', false)) {
// Disable previous account
Expand Down
114 changes: 114 additions & 0 deletions lib/TransferService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Guests;

use OCA\Files\Exception\TransferOwnershipException;
use OCA\Files\Service\OwnershipTransferService;
use OCA\Guests\AppInfo\Application;
use OCA\Guests\BackgroundJob\TransferJob;
use OCA\Guests\Db\Transfer;
use OCA\Guests\Db\TransferMapper;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\QueryException;
use OCP\BackgroundJob\IJobList;
use OCP\IUser;
use OCP\Notification\IManager as INotificationManager;
use OCP\Share\IManager as IShareManager;
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;

class TransferService {
public function __construct(
private ContainerInterface $container,
private IShareManager $shareManager,
private INotificationManager $notificationManager,
private IJobList $jobList,
private TransferMapper $transferMapper,
private LoggerInterface $logger,
)
{
}

public function transfer(IUser $guestUser, IUser $user): void {
try {
/** @var OwnershipTransferService $ownershipTransferService */
$ownershipTransferService = $this->container->get(OwnershipTransferService::class);
} catch (QueryException $e) {
$this->logger->error('Could not resolve ownership transfer service to import guest user data', [
'exception' => $e,
]);
throw $e;
}

try {
$ownershipTransferService->transfer(
$guestUser,
$user,
'/',
null,
true,
true
);
} catch (TransferOwnershipException $e) {
$this->logger->error('Could not import guest user data', [
'exception' => $e,
]);
throw $e;
}

// Update incomming shares
$shares = $this->shareManager->getSharedWith($guestUser->getUID(), IShare::TYPE_USER);

Check failure on line 67 in lib/TransferService.php

View workflow job for this annotation

GitHub Actions / Nextcloud dev-stable29

UndefinedClass

lib/TransferService.php:67:70: UndefinedClass: Class, interface or enum named OCA\Guests\IShare does not exist (see https://psalm.dev/019)

Check failure on line 67 in lib/TransferService.php

View workflow job for this annotation

GitHub Actions / Nextcloud dev-master

UndefinedClass

lib/TransferService.php:67:70: UndefinedClass: Class, interface or enum named OCA\Guests\IShare does not exist (see https://psalm.dev/019)
foreach ($shares as $share) {
$share->setSharedWith($user->getUID());
$this->shareManager->updateShare($share);
}

$notification = $this->notificationManager->createNotification();
$notification
->setApp(Application::APP_ID)
->setSubject('data_migrated_to_system_user')
->setObject('user', $user->getEMailAddress())
->setDateTime(new \DateTime())
->setUser($user->getUID());
$this->notificationManager->notify($notification);
}

public function addTransferJob(IUser $author, IUser $source, string $target): void {
$transfer = new Transfer();
$transfer->setAuthor($author->getUID());
$transfer->setSource($source->getUID());
$transfer->setTarget($target);
$transfer->setStatus(Transfer::STATUS_WAITING);
/** @var Transfer $transfer */
$transfer = $this->transferMapper->insert($transfer);

$this->jobList->add(TransferJob::class, [
'id' => $transfer->getId(),
]);
}

public function getTransfer(IUser $source, string $target): ?Transfer {
try {
$transfer = $this->transferMapper->getBySource($source->getUID());
return $transfer;
} catch (DoesNotExistException $e) {
// Allow as this just means there is no pending transfer
}

try {
$transfer = $this->transferMapper->getByTarget($target);
return $transfer;
} catch (DoesNotExistException $e) {
// Allow as this just means there is no pending transfer
}

return null;
}
}

0 comments on commit f252c1a

Please sign in to comment.