Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions lib/Model/Mount.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use OCA\Files_Sharing\External\Manager as ExternalShareManager;
use OCP\Federation\ICloudIdManager;
use OCP\Http\Client\IClientService;
use OCP\IUser;

/**
* Class Mount
Expand All @@ -41,6 +42,7 @@ class Mount extends ManagedModel implements IDeserializable, IQueryRow, JsonSeri
private ICloudIdManager $cloudIdManager;
private IClientService $httpClientService;
private ExternalShareManager $externalShareManager;
private IUser $recipient;
private string $remote = '';
private int $remoteShareId = 0;

Expand Down Expand Up @@ -304,6 +306,16 @@ public function getExternalShareManager(): ExternalShareManager {
return $this->externalShareManager;
}

public function setRecipient(IUser $recipient): self {
$this->recipient = $recipient;

return $this;
}

public function getRecipient(): IUser {
return $this->recipient;
}

public function setRemote(string $remote): void {
$this->remote = $remote;
}
Expand Down Expand Up @@ -334,6 +346,7 @@ public function toMount(): array {
'mountpoint' => $this->getMountPoint(false),
'HttpClientService' => $this->getHttpClientService(),
'manager' => $this->getExternalShareManager(),
'recipient' => $this->getRecipient(),
'cloudId' => $this->getCloudIdManager()->getCloudId(
$member->getUserId(),
$member->getRemoteInstance()->getRoot()
Expand Down
12 changes: 7 additions & 5 deletions lib/MountManager/CircleMountProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ public function getMountsForUser(IUser $user, IStorageFactory $loader): array {
foreach ($items as $item) {
try {
$this->fixDuplicateFile($user->getUID(), $item);
$mounts[] = $this->generateCircleMount($item, $loader);
$mounts[] = $this->generateCircleMount($item, $loader, $user);
} catch (\Exception $e) {
$this->logger->warning('issue with teams\' mounts', ['exception' => $e]);
}
Expand All @@ -89,7 +89,7 @@ public function getMountsForUser(IUser $user, IStorageFactory $loader): array {
* @throws InitiatorNotFoundException
* @throws MountPointConstructionException
*/
public function generateCircleMount(Mount $mount, IStorageFactory $storageFactory): CircleMount {
public function generateCircleMount(Mount $mount, IStorageFactory $storageFactory, IUser $user): CircleMount {
$initiator = $mount->getInitiator();

// TODO: right now, limited to Local Nextcloud User
Expand All @@ -100,7 +100,8 @@ public function generateCircleMount(Mount $mount, IStorageFactory $storageFactor

$mount->setCloudIdManager($this->cloudIdManager)
->setHttpClientService($this->clientService)
->setExternalShareManager($this->externalShareManager);
->setExternalShareManager($this->externalShareManager)
->setRecipient($user);

return new CircleMount(
$mount,
Expand Down Expand Up @@ -157,7 +158,7 @@ private function fixDuplicateFile(string $userId, Mount $mount): void {

#[Override]
public function getMountsForPath(string $setupPathHint, bool $forChildren, array $mountProviderArgs, IStorageFactory $loader): array {
/** @var array<string, array{federatedUser: IFederatedUser, paths: string[]}> $userMountRequests */
/** @var array<string, array{federatedUser: IFederatedUser, user: IUser, paths: string[]}> $userMountRequests */
$userMountRequests = [];
/** @var array<string, IMountPoint> $mounts */
$mounts = [];
Expand All @@ -173,6 +174,7 @@ public function getMountsForPath(string $setupPathHint, bool $forChildren, array

$userMountRequests[$user->getUID()] ??= [
'federatedUser' => $this->federatedUserService->getLocalFederatedUser($user->getUID()),
'user' => $user,
'paths' => [],
];

Expand All @@ -193,7 +195,7 @@ public function getMountsForPath(string $setupPathHint, bool $forChildren, array
}
try {
$this->fixDuplicateFile($uid, $item);
$mounts[$mountPoint] = $this->generateCircleMount($item, $loader);
$mounts[$mountPoint] = $this->generateCircleMount($item, $loader, $userMountRequest['user']);
} catch (\Exception $e) {
$this->logger->error('Failed to create Teams mount', ['exception' => $e]);
}
Expand Down
171 changes: 171 additions & 0 deletions tests/unit/lib/MountManager/CircleMountProviderTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
<?php

declare(strict_types=1);

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

namespace OCA\Circles\Tests\MountManager;

use OC\Files\Storage\StorageFactory;
use OCA\Circles\Db\MountPointRequest;
use OCA\Circles\Db\MountRequest;
use OCA\Circles\Model\Federated\RemoteInstance;
use OCA\Circles\Model\FederatedUser;
use OCA\Circles\Model\Member;
use OCA\Circles\Model\Mount;
use OCA\Circles\MountManager\CircleMountProvider;
use OCA\Circles\Service\ConfigService;
use OCA\Circles\Service\FederatedUserService;
use OCA\Files_Sharing\External\Manager as ExternalShareManager;
use OCA\Files_Sharing\External\Storage as ExternalStorage;
use OCP\Federation\ICloudIdManager;
use OCP\Files\Cache\ICache;
use OCP\Files\Cache\ICacheEntry;
use OCP\Files\Config\ICachedMountInfo;
use OCP\Files\Config\MountProviderArgs;
use OCP\Files\IRootFolder;
use OCP\Files\IUserFolder;
use OCP\Files\Mount\IMountPoint;
use OCP\Files\Storage\IStorage;
use OCP\Http\Client\IClientService;
use OCP\IUser;
use OCP\OCM\Exceptions\OCMProviderException;
use OCP\OCM\IOCMDiscoveryService;
use OCP\Server;
use PHPUnit\Framework\MockObject\MockObject;
use Psr\Log\LoggerInterface;
use Test\TestCase;

class CircleMountProviderTest extends TestCase {
private IRootFolder&MockObject $rootFolder;
private MountRequest&MockObject $mountRequest;
private FederatedUserService&MockObject $federatedUserService;
private IUser&MockObject $user;
private CircleMountProvider $provider;

#[\Override]
protected function setUp(): void {
parent::setUp();

$this->rootFolder = $this->createMock(IRootFolder::class);
$this->mountRequest = $this->createMock(MountRequest::class);
$this->federatedUserService = $this->createMock(FederatedUserService::class);
$configService = $this->createMock(ConfigService::class);
$configService->method('isLocalInstance')
->willReturn(true);
$this->user = $this->createMock(IUser::class);
$this->user->method('getUID')
->willReturn('alice');

$this->provider = new CircleMountProvider(
$this->createMock(IClientService::class),
$this->rootFolder,
Server::get(ICloudIdManager::class),
$this->mountRequest,
$this->createMock(MountPointRequest::class),
$this->federatedUserService,
$configService,
$this->createMock(LoggerInterface::class),
$this->createMock(ExternalShareManager::class),
);
}

private function createTeamMount(): Mount {
$localUser = new FederatedUser();
$localUser->setUserId('alice')
->setUserType(Member::TYPE_USER)
->setInstance('');
$initiator = new Member();
$initiator->setUserId('alice')
->setInheritedBy($localUser);

$remoteInstance = new RemoteInstance();
$remoteInstance->setRoot('https://remote.example');
$owner = new Member();
$owner->setUserId('bob')
->setRemoteInstance($remoteInstance);

$mount = new Mount();
$mount->setMountId('mount-id')
->setOriginalMountPoint('/team-share')
->setToken('token')
->setOwner($owner)
->setInitiator($initiator);

return $mount;
}

private function mockFreeMountPoint(): void {
$cache = $this->createMock(ICache::class);
$cache->method('inCache')
->willReturn(false);
$storage = $this->createMock(IStorage::class);
$storage->method('getCache')
->willReturn($cache);
$homeMount = $this->createMock(IMountPoint::class);
$homeMount->method('getStorage')
->willReturn($storage);
$userFolder = $this->createMock(IUserFolder::class);
$userFolder->method('getMountPoint')
->willReturn($homeMount);
$this->rootFolder->method('getUserFolder')
->with('alice')
->willReturn($userFolder);

$federatedUser = new FederatedUser();
$federatedUser->setSingleId('alice-single-id');
$this->federatedUserService->method('getLocalFederatedUser')
->with('alice')
->willReturn($federatedUser);
}

public function testGenerateCircleMountCreatesExternalStorageForUser(): void {
$discoveryService = $this->createMock(IOCMDiscoveryService::class);
$discoveryService->method('discover')
->willThrowException(new OCMProviderException());
$this->overwriteService(IOCMDiscoveryService::class, $discoveryService);
$mount = $this->createTeamMount();

$circleMount = $this->provider->generateCircleMount($mount, new StorageFactory(), $this->user);

$this->assertSame($this->user, $mount->toMount()['recipient']);
$this->assertTrue($circleMount->getStorage()->instanceOfStorage(ExternalStorage::class));
}

public function testGetMountsForUserPassesUserAsRecipient(): void {
$this->mockFreeMountPoint();
$mount = $this->createTeamMount();
$this->mountRequest->method('getForUser')
->willReturn([$mount]);

$mounts = $this->provider->getMountsForUser($this->user, new StorageFactory());

$this->assertCount(1, $mounts);
$this->assertSame($this->user, $mount->toMount()['recipient']);
}

public function testGetMountsForPathPassesUserAsRecipient(): void {
$this->mockFreeMountPoint();
$mount = $this->createTeamMount();
$this->mountRequest->method('getForUser')
->willReturn([$mount]);
$mountInfo = $this->createMock(ICachedMountInfo::class);
$mountInfo->method('getUser')
->willReturn($this->user);
$mountInfo->method('getMountPoint')
->willReturn('/alice/files/team-share/');

$mounts = $this->provider->getMountsForPath(
'/alice/files/team-share',
false,
[new MountProviderArgs($mountInfo, $this->createMock(ICacheEntry::class))],
new StorageFactory(),
);

$this->assertCount(1, $mounts);
$this->assertSame($this->user, $mount->toMount()['recipient']);
}
}
Loading