Skip to content

Commit 38e34df

Browse files
feat: implement remote attachment creation
Signed-off-by: Luka Trovic <luka@nextcloud.com>
1 parent 814fbba commit 38e34df

6 files changed

Lines changed: 184 additions & 76 deletions

File tree

‎appinfo/routes.php‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@
169169

170170
['name' => 'attachment_ocs#getAll', 'url' => '/api/v{apiVersion}/cards/{cardId}/attachments', 'verb' => 'GET'],
171171
['name' => 'attachment_ocs#create', 'url' => '/api/v{apiVersion}/cards/{cardId}/attachment', 'verb' => 'POST'],
172+
['name' => 'attachment_ocs#acceptRemote', 'url' => '/api/v{apiVersion}/cards/{cardId}/remote-attachment', 'verb' => 'POST'],
172173
['name' => 'attachment_ocs#update', 'url' => '/api/v{apiVersion}/cards/{cardId}/attachments/{attachmentId}', 'verb' => 'PUT'],
173174
['name' => 'attachment_ocs#delete', 'url' => '/api/v{apiVersion}/cards/{cardId}/attachments/{type}:{attachmentId}', 'verb' => 'DELETE'],
174175
['name' => 'attachment_ocs#restore', 'url' => '/api/v{apiVersion}/cards/{cardId}/attachments/{attachmentId}/restore', 'verb' => 'PUT'],

‎lib/Controller/AttachmentOcsController.php‎

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
use OCA\Deck\NotImplementedException;
1313
use OCA\Deck\Service\AttachmentService;
1414
use OCA\Deck\Service\BoardService;
15+
use OCA\Deck\Service\ExternalBoardService;
1516
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
17+
use OCP\AppFramework\Http\Attribute\PublicPage;
1618
use OCP\AppFramework\Http\DataResponse;
1719
use OCP\AppFramework\OCSController;
1820
use OCP\IRequest;
@@ -23,6 +25,7 @@ public function __construct(
2325
IRequest $request,
2426
private AttachmentService $attachmentService,
2527
private BoardService $boardService,
28+
private ExternalBoardService $externalBoardService,
2629
) {
2730
parent::__construct($appName, $request);
2831
}
@@ -37,19 +40,33 @@ private function ensureLocalBoard(?int $boardId): void {
3740
}
3841

3942
#[NoAdminRequired]
43+
#[PublicPage]
4044
public function getAll(int $cardId, ?int $boardId = null): DataResponse {
41-
$this->ensureLocalBoard($boardId);
45+
$board = $this->boardService->find($boardId, false);
46+
if ($board->getExternalId()) {
47+
return new DataResponse($this->externalBoardService->getAttachmentsFromRemote($board, $cardId));
48+
}
4249
$attachment = $this->attachmentService->findAll($cardId, true);
4350
return new DataResponse($attachment);
4451
}
4552

4653
#[NoAdminRequired]
4754
public function create(int $cardId, string $type, string $data = '', ?int $boardId = null): DataResponse {
48-
$this->ensureLocalBoard($boardId);
55+
$board = $this->boardService->find($boardId, false);
56+
if ($board->getExternalId()) {
57+
return new DataResponse($this->externalBoardService->createAttachmentForRemote($board, $cardId));
58+
}
4959
$attachment = $this->attachmentService->create($cardId, $type, $data);
5060
return new DataResponse($attachment);
5161
}
5262

63+
#[NoAdminRequired]
64+
#[PublicPage]
65+
public function acceptRemote(int $cardId, string $token): DataResponse {
66+
$this->attachmentService->acceptRemoteAttachment($cardId, $token);
67+
return new DataResponse([]);
68+
}
69+
5370
#[NoAdminRequired]
5471
public function update(int $cardId, int $attachmentId, string $data, string $type = 'file', ?int $boardId = null): DataResponse {
5572
$this->ensureLocalBoard($boardId);

‎lib/Service/AttachmentService.php‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use OCA\Deck\AppInfo\Application;
1212
use OCA\Deck\BadRequestException;
1313
use OCA\Deck\Cache\AttachmentCacheHelper;
14+
use OCA\Deck\Cron\ScanFederatedAttachment;
1415
use OCA\Deck\Db\Acl;
1516
use OCA\Deck\Db\Attachment;
1617
use OCA\Deck\Db\AttachmentMapper;
@@ -19,10 +20,13 @@
1920
use OCA\Deck\InvalidAttachmentType;
2021
use OCA\Deck\NoPermissionException;
2122
use OCA\Deck\NotFoundException;
23+
use OCA\Deck\Sharing\DeckShareProvider;
2224
use OCA\Deck\StatusException;
2325
use OCA\Deck\Validators\AttachmentServiceValidator;
2426
use OCP\AppFramework\Db\IMapperException;
2527
use OCP\AppFramework\Http\Response;
28+
use OCP\BackgroundJob\IJobList;
29+
use OCP\Federation\ICloudIdManager;
2630
use OCP\IL10N;
2731
use OCP\IUserManager;
2832
use Psr\Container\ContainerExceptionInterface;
@@ -44,6 +48,9 @@ public function __construct(
4448
private readonly IL10N $l10n,
4549
private readonly ActivityManager $activityManager,
4650
private readonly AttachmentServiceValidator $attachmentServiceValidator,
51+
private readonly DeckShareProvider $deckShareProvider,
52+
private readonly ICloudIdManager $cloudIdManager,
53+
private readonly IJobList $jobList,
4754
) {
4855
// Register shipped attachment services
4956
// TODO: move this to a plugin based approach once we have different types of attachments
@@ -205,6 +212,21 @@ public function create(int $cardId, string $type, string $data = '') {
205212
return $attachment;
206213
}
207214

215+
public function acceptRemoteAttachment(int $cardId, string $token): void {
216+
$this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_EDIT);
217+
218+
$userId = $this->permissionService->getUserId();
219+
if (!$this->cloudIdManager->isValidCloudId($userId)) {
220+
throw new NoPermissionException('Only federated user is allowed');
221+
}
222+
223+
$externalShare = $this->deckShareProvider->ensureAcceptRemoteShare($token, $this->cloudIdManager->resolveCloudId($userId)->getRemote());
224+
$this->jobList->add(ScanFederatedAttachment::class, [
225+
'cardId' => $cardId,
226+
'shareId' => $externalShare->getId(),
227+
]);
228+
}
229+
208230
/**
209231
* Apply import side effects to keep attachment behavior consistent with regular create flow.
210232
*/

‎lib/Service/ExternalBoardService.php‎

Lines changed: 11 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ public function __construct(
3232
private BoardMapper $boardMapper,
3333
private IURLGenerator $urlGenerator,
3434
private DeckShareProvider $deckShareProvider,
35+
private FilesAppService $filesAppService,
3536
private ?string $userId,
3637
) {
3738
}
@@ -600,7 +601,7 @@ public function localizeRemoteAttachments(Board $localBoard, array $attachments)
600601
$attachments[$i]['extendedData']['attachmentCreator']['displayName'] = $createdByUser->getCloudId()->getId();
601602
}
602603
if (!empty($attachment['extendedData']['shareToken'])) {
603-
$file = $this->deckShareProvider->ensureAcceptRemoteShare($attachment['extendedData']['shareToken'], $ownerCloudId->getRemote());
604+
$file = $this->deckShareProvider->getRemoteFile($attachment['extendedData']['shareToken'], $ownerCloudId->getRemote());
604605

605606
if (empty($file)) {
606607
unset($attachments[$i]);
@@ -627,44 +628,22 @@ public function getAttachmentsFromRemote(Board $localBoard, int $cardId): array
627628
return $this->localizeRemoteAttachments($localBoard, $attachments);
628629
}
629630

630-
public function createAttachmentOnRemote(Board $localBoard, int $cardId, string $type, string $data = '', ?array $uploadedFile = null): array {
631+
public function createAttachmentForRemote(Board $localBoard, int $cardId): array {
631632
$this->configService->ensureFederationEnabled();
632633
$this->permissionService->checkPermission($this->boardMapper, $localBoard->getId(), Acl::PERMISSION_EDIT, $this->userId, false, false);
633634
$shareToken = $localBoard->getShareToken();
634635
$participantCloudId = $this->cloudIdManager->getCloudId($this->userId, null);
635636
$ownerCloudId = $this->cloudIdManager->resolveCloudId($localBoard->getOwner());
636-
$url = $ownerCloudId->getRemote() . '/ocs/v2.php/apps/deck/api/v1.0/cards/' . $cardId . '/attachment';
637-
if ($uploadedFile !== null) {
638-
$content = fopen($uploadedFile['tmp_name'], 'rb');
639-
if ($content === false) {
640-
throw new Exception('Could not read uploaded file');
641-
}
642-
$params = [
643-
[
644-
'name' => 'boardId',
645-
'contents' => (string)$localBoard->getExternalId(),
646-
],
647-
[
648-
'name' => 'file',
649-
'contents' => $content,
650-
'filename' => $uploadedFile['name'],
651-
],
652-
[
653-
'name' => 'type',
654-
'contents' => $type,
655-
],
656-
[
657-
'name' => 'data',
658-
'contents' => $data,
659-
],
660-
];
661-
$resp = $this->proxy->post($participantCloudId->getId(), $shareToken, $url, ['multipart' => $params]);
662-
return $this->proxy->getOcsData($resp);
663-
}
637+
638+
// Upload and create remote share
639+
$shareWithFederatedId = $ownerCloudId->getId();
640+
$fileShareToken = $this->filesAppService->createForRemote($shareWithFederatedId);
641+
642+
$url = $ownerCloudId->getRemote() . '/ocs/v2.php/apps/deck/api/v1.0/cards/' . $cardId . '/remote-attachment';
664643
$resp = $this->proxy->post($participantCloudId->getId(), $shareToken, $url, [
665644
'boardId' => $localBoard->getExternalId(),
666-
'type' => $type,
667-
'data' => $data,
645+
'cardId' => $cardId,
646+
'token' => $fileShareToken,
668647
]);
669648
return $this->proxy->getOcsData($resp);
670649
}

‎lib/Service/FilesAppService.php‎

Lines changed: 42 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -302,27 +302,53 @@ public function create(Attachment $attachment) {
302302
$share->setSharedBy($this->userId);
303303
$share = $this->shareManager->createShare($share);
304304

305-
// Create share for federated users
306-
$boardId = $this->cardMapper->findBoardId($attachment->getCardId());
307-
foreach ($this->permissionService->findUsers($boardId) as $user) {
308-
if (!$user instanceof FederatedUser) {
309-
continue;
310-
}
311-
$remoteShare = $this->shareManager->newShare();
312-
$remoteShare->setParent((int)$share->getId());
313-
$remoteShare->setNode($target);
314-
$remoteShare->setShareType(ISHARE::TYPE_REMOTE);
315-
$remoteShare->setSharedWith($user->getUID());
316-
$remoteShare->setPermissions(Constants::PERMISSION_READ);
317-
$remoteShare->setSharedBy($this->userId);
318-
$this->shareManager->createShare($remoteShare);
319-
}
320-
321305
$attachment->setId((int)$share->getId());
322306
$attachment->setData($target->getName());
323307
return $attachment;
324308
}
325309

310+
public function createForRemote(string $federatedCloudId) {
311+
$file = $this->getUploadedFile();
312+
$fileName = $file['name'];
313+
$this->validateFilename($fileName);
314+
315+
$userFolder = $this->rootFolder->getUserFolder($this->userId);
316+
try {
317+
$folder = $userFolder->get($this->configService->getAttachmentFolder());
318+
} catch (NotFoundException) {
319+
$folder = $userFolder->newFolder($this->configService->getAttachmentFolder());
320+
}
321+
322+
if ($folder->isShared()) {
323+
$folderName = $userFolder->getNonExistingName($this->configService->getAttachmentFolder());
324+
$folder = $userFolder->newFolder($folderName);
325+
$this->configService->setAttachmentFolder($this->userId, $folderName);
326+
}
327+
328+
if (!$folder instanceof Folder || $folder->isShared()) {
329+
throw new NotFoundException('No target folder found');
330+
}
331+
332+
$fileName = $folder->getNonExistingName($fileName);
333+
$target = $folder->newFile($fileName);
334+
$content = fopen($file['tmp_name'], 'rb');
335+
if ($content === false) {
336+
throw new StatusException('Could not read file');
337+
}
338+
$target->putContent($content);
339+
340+
$share = $this->shareManager->newShare();
341+
$share->setNode($target);
342+
$share->setShareType(IShare::TYPE_REMOTE);
343+
$share->setSharedWith($federatedCloudId);
344+
$share->setPermissions(Constants::PERMISSION_READ);
345+
$share->setSharedBy($this->userId);
346+
$share->setShareOwner($this->userId);
347+
348+
$createdShare = $this->shareManager->createShare($share);
349+
return $createdShare->getToken();
350+
}
351+
326352
/**
327353
* Ensure the file exists in the owner’s Deck attachment folder, link it to the
328354
* Reuse file/share when already present

0 commit comments

Comments
 (0)