nextcloud/apps/files/lib/Controller/TransferOwnershipController.php
Ferdinand Thiessen 1550af87cd
fix(files): Properly handle denied ownership transfers
When the receiver denies the transfer the notification handler was missing,
so no notification was created for the transfer owner.

But also the internal notification was created two times:
1. When rejecting the transfer
2. By the reject function when dismissing the notification

This is fixed by only relying on the dismiss function.

Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
2024-06-14 17:35:59 +02:00

198 lines
6 KiB
PHP

<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Files\Controller;
use OCA\Files\BackgroundJob\TransferOwnership;
use OCA\Files\Db\TransferOwnership as TransferOwnershipEntity;
use OCA\Files\Db\TransferOwnershipMapper;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCSController;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\BackgroundJob\IJobList;
use OCP\Files\IHomeStorage;
use OCP\Files\IRootFolder;
use OCP\IRequest;
use OCP\IUserManager;
use OCP\Notification\IManager as NotificationManager;
class TransferOwnershipController extends OCSController {
/** @var string */
private $userId;
/** @var NotificationManager */
private $notificationManager;
/** @var ITimeFactory */
private $timeFactory;
/** @var IJobList */
private $jobList;
/** @var TransferOwnershipMapper */
private $mapper;
/** @var IUserManager */
private $userManager;
/** @var IRootFolder */
private $rootFolder;
public function __construct(string $appName,
IRequest $request,
string $userId,
NotificationManager $notificationManager,
ITimeFactory $timeFactory,
IJobList $jobList,
TransferOwnershipMapper $mapper,
IUserManager $userManager,
IRootFolder $rootFolder) {
parent::__construct($appName, $request);
$this->userId = $userId;
$this->notificationManager = $notificationManager;
$this->timeFactory = $timeFactory;
$this->jobList = $jobList;
$this->mapper = $mapper;
$this->userManager = $userManager;
$this->rootFolder = $rootFolder;
}
/**
* @NoAdminRequired
*
* Transfer the ownership to another user
*
* @param string $recipient Username of the recipient
* @param string $path Path of the file
*
* @return DataResponse<Http::STATUS_OK|Http::STATUS_BAD_REQUEST|Http::STATUS_FORBIDDEN, array<empty>, array{}>
*
* 200: Ownership transferred successfully
* 400: Transferring ownership is not possible
* 403: Transferring ownership is not allowed
*/
public function transfer(string $recipient, string $path): DataResponse {
$recipientUser = $this->userManager->get($recipient);
if ($recipientUser === null) {
return new DataResponse([], Http::STATUS_BAD_REQUEST);
}
$userRoot = $this->rootFolder->getUserFolder($this->userId);
try {
$node = $userRoot->get($path);
} catch (\Exception $e) {
return new DataResponse([], Http::STATUS_BAD_REQUEST);
}
if ($node->getOwner()->getUID() !== $this->userId || !$node->getStorage()->instanceOfStorage(IHomeStorage::class)) {
return new DataResponse([], Http::STATUS_FORBIDDEN);
}
$transferOwnership = new TransferOwnershipEntity();
$transferOwnership->setSourceUser($this->userId);
$transferOwnership->setTargetUser($recipient);
$transferOwnership->setFileId($node->getId());
$transferOwnership->setNodeName($node->getName());
$transferOwnership = $this->mapper->insert($transferOwnership);
$notification = $this->notificationManager->createNotification();
$notification->setUser($recipient)
->setApp($this->appName)
->setDateTime($this->timeFactory->getDateTime())
->setSubject('transferownershipRequest', [
'sourceUser' => $this->userId,
'targetUser' => $recipient,
'nodeName' => $node->getName(),
])
->setObject('transfer', (string)$transferOwnership->getId());
$this->notificationManager->notify($notification);
return new DataResponse([]);
}
/**
* @NoAdminRequired
*
* Accept an ownership transfer
*
* @param int $id ID of the ownership transfer
*
* @return DataResponse<Http::STATUS_OK|Http::STATUS_FORBIDDEN|Http::STATUS_NOT_FOUND, array<empty>, array{}>
*
* 200: Ownership transfer accepted successfully
* 403: Accepting ownership transfer is not allowed
* 404: Ownership transfer not found
*/
public function accept(int $id): DataResponse {
try {
$transferOwnership = $this->mapper->getById($id);
} catch (DoesNotExistException $e) {
return new DataResponse([], Http::STATUS_NOT_FOUND);
}
if ($transferOwnership->getTargetUser() !== $this->userId) {
return new DataResponse([], Http::STATUS_FORBIDDEN);
}
$notification = $this->notificationManager->createNotification();
$notification->setApp('files')
->setObject('transfer', (string)$id);
$this->notificationManager->markProcessed($notification);
$newTransferOwnership = new TransferOwnershipEntity();
$newTransferOwnership->setNodeName($transferOwnership->getNodeName());
$newTransferOwnership->setFileId($transferOwnership->getFileId());
$newTransferOwnership->setSourceUser($transferOwnership->getSourceUser());
$newTransferOwnership->setTargetUser($transferOwnership->getTargetUser());
$this->mapper->insert($newTransferOwnership);
$this->jobList->add(TransferOwnership::class, [
'id' => $newTransferOwnership->getId(),
]);
return new DataResponse([], Http::STATUS_OK);
}
/**
* @NoAdminRequired
*
* Reject an ownership transfer
*
* @param int $id ID of the ownership transfer
*
* @return DataResponse<Http::STATUS_OK|Http::STATUS_FORBIDDEN|Http::STATUS_NOT_FOUND, array<empty>, array{}>
*
* 200: Ownership transfer rejected successfully
* 403: Rejecting ownership transfer is not allowed
* 404: Ownership transfer not found
*/
public function reject(int $id): DataResponse {
try {
$transferOwnership = $this->mapper->getById($id);
} catch (DoesNotExistException $e) {
return new DataResponse([], Http::STATUS_NOT_FOUND);
}
if ($transferOwnership->getTargetUser() !== $this->userId) {
return new DataResponse([], Http::STATUS_FORBIDDEN);
}
$notification = $this->notificationManager->createNotification();
$notification->setApp('files')
->setObject('transfer', (string)$id);
$this->notificationManager->markProcessed($notification);
$this->mapper->delete($transferOwnership);
// A "request denied" notification will be created by Notifier::dismissNotification
return new DataResponse([], Http::STATUS_OK);
}
}