Skip to content

Commit 1dfc183

Browse files
committed
fix(federated-sharing): avoid duplicate shares
Assisted-by: ClaudeCode:claude-opus-5 Signed-off-by: Hamza <hamzamahjoubi221@gmail.com>
1 parent b3f362f commit 1dfc183

2 files changed

Lines changed: 73 additions & 6 deletions

File tree

apps/federatedfilesharing/lib/FederatedShareProvider.php

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
use OCP\IUserManager;
3131
use OCP\Security\ISecureRandom;
3232
use OCP\Server;
33+
use OCP\Share\Exceptions\AlreadySharedException;
3334
use OCP\Share\Exceptions\GenericShareException;
3435
use OCP\Share\Exceptions\ShareNotFound;
3536
use OCP\Share\IShare;
@@ -101,20 +102,25 @@ public function create(IShare $share): IShare {
101102
throw new \Exception($message_t);
102103
}
103104

105+
$cloudId = $this->cloudIdManager->resolveCloudId($shareWith);
106+
104107
/*
105-
* Check if file is not already shared with the remote user
108+
* Check if file is not already shared with the remote user.
109+
* Has to be looked up by the normalized cloud ID, because that is what
110+
* gets stored below. Otherwise spellings like "user@server.com/" slip
111+
* past this check and create a duplicate share.
106112
*/
107-
$alreadyShared = $this->getSharedWith($shareWith, IShare::TYPE_REMOTE, $share->getNode(), 1, 0);
108-
$alreadySharedGroup = $this->getSharedWith($shareWith, IShare::TYPE_REMOTE_GROUP, $share->getNode(), 1, 0);
109-
if (!empty($alreadyShared) || !empty($alreadySharedGroup)) {
113+
// getSharedWith() ignores its $shareType argument and always
114+
// queries all supported remote types, so a single lookup covers both.
115+
$alreadyShared = $this->getSharedWith($cloudId->getId(), IShare::TYPE_REMOTE, $share->getNode(), 1, 0);
116+
if (!empty($alreadyShared)) {
110117
$message = 'Sharing %1$s failed, because this item is already shared with %2$s';
111118
$message_t = $this->l->t('Sharing %1$s failed, because this item is already shared with the account %2$s', [$share->getNode()->getName(), $shareWith]);
112119
$this->logger->debug(sprintf($message, $share->getNode()->getName(), $shareWith), ['app' => 'Federated File Sharing']);
113-
throw new \Exception($message_t);
120+
throw new AlreadySharedException($message_t, $alreadyShared[0]);
114121
}
115122

116123
// don't allow federated shares if source and target server are the same
117-
$cloudId = $this->cloudIdManager->resolveCloudId($shareWith);
118124
$currentServer = $this->addressHandler->generateRemoteURL();
119125
$currentUser = $sharedBy;
120126
if ($this->addressHandler->compareAddresses($cloudId->getUser(), $cloudId->getRemote(), $currentUser, $currentServer)) {

apps/federatedfilesharing/tests/FederatedShareProviderTest.php

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
use OCP\IUserManager;
3333
use OCP\Security\ISecureRandom;
3434
use OCP\Server;
35+
use OCP\Share\Exceptions\AlreadySharedException;
3536
use OCP\Share\IManager;
3637
use OCP\Share\IShare;
3738
use PHPUnit\Framework\MockObject\MockObject;
@@ -418,6 +419,66 @@ public function testCreateAlreadyShared(): void {
418419
}
419420
}
420421

422+
public static function dataTestCreateAlreadySharedWithEquivalentCloudId(): array {
423+
return [
424+
['user@server.com'],
425+
['user@server.com/'],
426+
['user@server.com/index.php'],
427+
];
428+
}
429+
430+
/**
431+
* Sharing a node with a recipient it is already shared with has to be
432+
* rejected with an AlreadySharedException, for every spelling that
433+
* normalizes to the same cloud ID.
434+
*/
435+
#[\PHPUnit\Framework\Attributes\DataProvider(methodName: 'dataTestCreateAlreadySharedWithEquivalentCloudId')]
436+
public function testCreateAlreadySharedWithEquivalentCloudId(string $shareWith): void {
437+
$node = $this->createMock(File::class);
438+
$node->method('getId')->willReturn(42);
439+
$node->method('getName')->willReturn('myFile');
440+
441+
$this->addressHandler->expects($this->any())->method('splitUserRemote')
442+
->willReturn(['user', 'server.com']);
443+
$this->addressHandler->expects($this->any())->method('generateRemoteURL')
444+
->willReturn('http://localhost/');
445+
$this->tokenHandler->method('generateToken')->willReturn('token');
446+
$this->contactsManager->expects($this->any())->method('search')
447+
->willReturn([]);
448+
$this->notifications->expects($this->once())
449+
->method('sendRemoteShare')
450+
->willReturn(true);
451+
452+
$share = $this->shareManager->newShare();
453+
$share->setSharedWith('user@server.com')
454+
->setSharedBy('sharedBy')
455+
->setShareOwner('shareOwner')
456+
->setPermissions(19)
457+
->setShareType(IShare::TYPE_REMOTE)
458+
->setNode($node)
459+
->setTarget('');
460+
$existingShare = $this->provider->create($share);
461+
462+
// a recipient of the node re-sharing it with the same account
463+
$duplicate = $this->shareManager->newShare();
464+
$duplicate->setSharedWith($shareWith)
465+
->setSharedBy('otherRecipient')
466+
->setShareOwner('shareOwner')
467+
->setPermissions(19)
468+
->setShareType(IShare::TYPE_REMOTE)
469+
->setNode($node)
470+
->setTarget('');
471+
472+
try {
473+
$this->provider->create($duplicate);
474+
$this->fail('Expected an AlreadySharedException');
475+
} catch (AlreadySharedException $e) {
476+
$this->assertEquals($existingShare->getId(), $e->getExistingShare()->getId());
477+
}
478+
479+
$this->assertCount(1, $this->provider->getSharesByPath($node));
480+
}
481+
421482
#[\PHPUnit\Framework\Attributes\DataProvider(methodName: 'dataTestUpdate')]
422483
public function testUpdate(string $owner, string $sharedBy, ?\DateTime $expirationDate): void {
423484
$this->provider = $this->getMockBuilder(FederatedShareProvider::class)

0 commit comments

Comments
 (0)