Browse Source
Merge
Merge 272d6141ca into 3e9d2fe208
committed by
GitHub
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 786 additions and 45 deletions
-
5apps/files_external/composer/composer/autoload_classmap.php
-
5apps/files_external/composer/composer/autoload_static.php
-
23apps/files_external/lib/AppInfo/Application.php
-
17apps/files_external/lib/Config/ConfigAdapter.php
-
6apps/files_external/lib/Config/UserContext.php
-
24apps/files_external/lib/Event/StorageCreatedEvent.php
-
24apps/files_external/lib/Event/StorageDeletedEvent.php
-
29apps/files_external/lib/Event/StorageUpdatedEvent.php
-
114apps/files_external/lib/Lib/ApplicableHelper.php
-
10apps/files_external/lib/Lib/StorageConfig.php
-
49apps/files_external/lib/Service/DBConfigService.php
-
35apps/files_external/lib/Service/GlobalStoragesService.php
-
205apps/files_external/lib/Service/MountCacheService.php
-
16apps/files_external/lib/Service/StoragesService.php
-
4apps/files_external/lib/Service/UserGlobalStoragesService.php
-
8apps/files_external/lib/Service/UserStoragesService.php
-
170apps/files_external/tests/ApplicableHelperTest.php
-
2apps/files_external/tests/Service/GlobalStoragesServiceTest.php
-
2apps/files_external/tests/Service/StoragesServiceTestCase.php
-
1apps/files_external/tests/Service/UserGlobalStoragesServiceTest.php
-
4apps/files_external/tests/Service/UserStoragesServiceTest.php
-
6cypress/e2e/files_external/files-external-failed.cy.ts
-
1lib/composer/composer/autoload_classmap.php
-
1lib/composer/composer/autoload_static.php
-
31lib/private/Files/Config/UserMountCache.php
-
2lib/private/User/Manager.php
-
18lib/public/Files/Config/IAuthoritativeMountProvider.php
-
15lib/public/Files/Config/IUserMountCache.php
-
4lib/public/IUserManager.php
@ -0,0 +1,24 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
/** |
|||
* SPDX-FileCopyrightText: 2025 Robin Appelman <robin@icewind.nl> |
|||
* SPDX-License-Identifier: AGPL-3.0-or-later |
|||
*/ |
|||
|
|||
namespace OCA\Files_External\Event; |
|||
|
|||
use OCA\Files_External\Lib\StorageConfig; |
|||
use OCP\EventDispatcher\Event; |
|||
|
|||
class StorageCreatedEvent extends Event { |
|||
public function __construct( |
|||
private readonly StorageConfig $newConfig, |
|||
) { |
|||
parent::__construct(); |
|||
} |
|||
|
|||
public function getNewConfig(): StorageConfig { |
|||
return $this->newConfig; |
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
/** |
|||
* SPDX-FileCopyrightText: 2025 Robin Appelman <robin@icewind.nl> |
|||
* SPDX-License-Identifier: AGPL-3.0-or-later |
|||
*/ |
|||
|
|||
namespace OCA\Files_External\Event; |
|||
|
|||
use OCA\Files_External\Lib\StorageConfig; |
|||
use OCP\EventDispatcher\Event; |
|||
|
|||
class StorageDeletedEvent extends Event { |
|||
public function __construct( |
|||
private readonly StorageConfig $oldConfig, |
|||
) { |
|||
parent::__construct(); |
|||
} |
|||
|
|||
public function getOldConfig(): StorageConfig { |
|||
return $this->oldConfig; |
|||
} |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
/** |
|||
* SPDX-FileCopyrightText: 2025 Robin Appelman <robin@icewind.nl> |
|||
* SPDX-License-Identifier: AGPL-3.0-or-later |
|||
*/ |
|||
|
|||
namespace OCA\Files_External\Event; |
|||
|
|||
use OCA\Files_External\Lib\StorageConfig; |
|||
use OCP\EventDispatcher\Event; |
|||
|
|||
class StorageUpdatedEvent extends Event { |
|||
public function __construct( |
|||
private readonly StorageConfig $oldConfig, |
|||
private readonly StorageConfig $newConfig, |
|||
) { |
|||
parent::__construct(); |
|||
} |
|||
|
|||
public function getOldConfig(): StorageConfig { |
|||
return $this->oldConfig; |
|||
} |
|||
|
|||
public function getNewConfig(): StorageConfig { |
|||
return $this->newConfig; |
|||
} |
|||
} |
|||
@ -0,0 +1,114 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
/** |
|||
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors |
|||
* SPDX-License-Identifier: AGPL-3.0-or-later |
|||
*/ |
|||
|
|||
namespace OCA\Files_External\Lib; |
|||
|
|||
use OC\User\LazyUser; |
|||
use OCP\IGroupManager; |
|||
use OCP\IUser; |
|||
use OCP\IUserManager; |
|||
|
|||
class ApplicableHelper { |
|||
public function __construct( |
|||
private readonly IUserManager $userManager, |
|||
private readonly IGroupManager $groupManager, |
|||
) { |
|||
} |
|||
|
|||
/** |
|||
* Get all users that have access to a storage |
|||
* |
|||
* @return \Iterator<string, IUser> |
|||
*/ |
|||
public function getUsersForStorage(StorageConfig $storage): \Iterator { |
|||
$yielded = []; |
|||
if (count($storage->getApplicableUsers()) + count($storage->getApplicableGroups()) === 0) { |
|||
yield from $this->userManager->getSeenUsers(); |
|||
} |
|||
foreach ($storage->getApplicableUsers() as $userId) { |
|||
$yielded[$userId] = true; |
|||
yield $userId => new LazyUser($userId, $this->userManager); |
|||
} |
|||
foreach ($storage->getApplicableGroups() as $groupId) { |
|||
$group = $this->groupManager->get($groupId); |
|||
if ($group !== null) { |
|||
foreach ($group->getUsers() as $user) { |
|||
if (!isset($yielded[$user->getUID()])) { |
|||
$yielded[$user->getUID()] = true; |
|||
yield $user->getUID() => $user; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
public function isApplicableForUser(StorageConfig $storage, IUser $user): bool { |
|||
if (count($storage->getApplicableUsers()) + count($storage->getApplicableGroups()) === 0) { |
|||
return true; |
|||
} |
|||
if (in_array($user->getUID(), $storage->getApplicableUsers())) { |
|||
return true; |
|||
} |
|||
$groupIds = $this->groupManager->getUserGroupIds($user); |
|||
foreach ($groupIds as $groupId) { |
|||
if (in_array($groupId, $storage->getApplicableGroups())) { |
|||
return true; |
|||
} |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
/** |
|||
* Return all users that are applicable for storage $a, but not for $b |
|||
* |
|||
* @return \Iterator<IUser> |
|||
*/ |
|||
public function diffApplicable(StorageConfig $a, StorageConfig $b): \Iterator { |
|||
$aIsAll = count($a->getApplicableUsers()) + count($a->getApplicableGroups()) === 0; |
|||
$bIsAll = count($b->getApplicableUsers()) + count($b->getApplicableGroups()) === 0; |
|||
if ($bIsAll) { |
|||
return; |
|||
} |
|||
|
|||
if ($aIsAll) { |
|||
foreach ($this->getUsersForStorage($a) as $user) { |
|||
if (!$this->isApplicableForUser($b, $user)) { |
|||
yield $user; |
|||
} |
|||
} |
|||
} else { |
|||
$yielded = []; |
|||
foreach ($a->getApplicableGroups() as $groupId) { |
|||
if (!in_array($groupId, $b->getApplicableGroups())) { |
|||
$group = $this->groupManager->get($groupId); |
|||
if ($group) { |
|||
foreach ($group->getUsers() as $user) { |
|||
if (!$this->isApplicableForUser($b, $user)) { |
|||
if (!isset($yielded[$user->getUID()])) { |
|||
$yielded[$user->getUID()] = true; |
|||
yield $user; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
foreach ($a->getApplicableUsers() as $userId) { |
|||
if (!in_array($userId, $b->getApplicableUsers())) { |
|||
$user = $this->userManager->get($userId); |
|||
if ($user && !$this->isApplicableForUser($b, $user)) { |
|||
if (!isset($yielded[$user->getUID()])) { |
|||
$yielded[$user->getUID()] = true; |
|||
yield $user; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,205 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
/** |
|||
* SPDX-FileCopyrightText: 2025 Robin Appelman <robin@icewind.nl> |
|||
* SPDX-License-Identifier: AGPL-3.0-or-later |
|||
*/ |
|||
|
|||
namespace OCA\Files_External\Service; |
|||
|
|||
use OC\Files\Cache\CacheEntry; |
|||
use OC\Files\Storage\FailedStorage; |
|||
use OCA\Files_External\Config\ConfigAdapter; |
|||
use OCA\Files_External\Event\StorageCreatedEvent; |
|||
use OCA\Files_External\Event\StorageDeletedEvent; |
|||
use OCA\Files_External\Event\StorageUpdatedEvent; |
|||
use OCA\Files_External\Lib\ApplicableHelper; |
|||
use OCA\Files_External\Lib\StorageConfig; |
|||
use OCP\Cache\CappedMemoryCache; |
|||
use OCP\EventDispatcher\Event; |
|||
use OCP\EventDispatcher\IEventListener; |
|||
use OCP\Files\Cache\ICacheEntry; |
|||
use OCP\Files\Config\IUserMountCache; |
|||
use OCP\Group\Events\BeforeGroupDeletedEvent; |
|||
use OCP\Group\Events\UserAddedEvent; |
|||
use OCP\Group\Events\UserRemovedEvent; |
|||
use OCP\IGroup; |
|||
use OCP\IUser; |
|||
use OCP\User\Events\PostLoginEvent; |
|||
use OCP\User\Events\UserCreatedEvent; |
|||
|
|||
/** |
|||
* Listens to config events and update the mounts for the applicable users |
|||
* |
|||
* @template-implements IEventListener<StorageCreatedEvent|StorageDeletedEvent|StorageUpdatedEvent|BeforeGroupDeletedEvent|UserCreatedEvent|UserAddedEvent|UserRemovedEvent|PostLoginEvent|Event> |
|||
*/ |
|||
class MountCacheService implements IEventListener { |
|||
private CappedMemoryCache $storageRootCache; |
|||
|
|||
public function __construct( |
|||
private readonly IUserMountCache $userMountCache, |
|||
private readonly ConfigAdapter $configAdapter, |
|||
private readonly GlobalStoragesService $storagesService, |
|||
private readonly ApplicableHelper $applicableHelper, |
|||
) { |
|||
$this->storageRootCache = new CappedMemoryCache(); |
|||
} |
|||
|
|||
public function handle(Event $event): void { |
|||
if ($event instanceof StorageCreatedEvent) { |
|||
$this->handleAddedStorage($event->getNewConfig()); |
|||
} |
|||
if ($event instanceof StorageDeletedEvent) { |
|||
$this->handleDeletedStorage($event->getOldConfig()); |
|||
} |
|||
if ($event instanceof StorageUpdatedEvent) { |
|||
$this->handleUpdatedStorage($event->getOldConfig(), $event->getNewConfig()); |
|||
} |
|||
if ($event instanceof UserAddedEvent) { |
|||
$this->handleUserAdded($event->getGroup(), $event->getUser()); |
|||
} |
|||
if ($event instanceof UserRemovedEvent) { |
|||
$this->handleUserRemoved($event->getGroup(), $event->getUser()); |
|||
} |
|||
if ($event instanceof BeforeGroupDeletedEvent) { |
|||
$this->handleGroupDeleted($event->getGroup()); |
|||
} |
|||
if ($event instanceof UserCreatedEvent) { |
|||
$this->handleUserCreated($event->getUser()); |
|||
} |
|||
if ($event instanceof PostLoginEvent) { |
|||
$this->onLogin($event->getUser()); |
|||
} |
|||
} |
|||
|
|||
public function handleDeletedStorage(StorageConfig $storage): void { |
|||
foreach ($this->applicableHelper->getUsersForStorage($storage) as $user) { |
|||
$this->userMountCache->removeMount($storage->getMountPointForUser($user)); |
|||
} |
|||
} |
|||
|
|||
public function handleAddedStorage(StorageConfig $storage): void { |
|||
foreach ($this->applicableHelper->getUsersForStorage($storage) as $user) { |
|||
$this->registerForUser($user, $storage); |
|||
} |
|||
} |
|||
|
|||
public function handleUpdatedStorage(StorageConfig $oldStorage, StorageConfig $newStorage): void { |
|||
foreach ($this->applicableHelper->diffApplicable($oldStorage, $newStorage) as $user) { |
|||
$this->userMountCache->removeMount($oldStorage->getMountPointForUser($user)); |
|||
} |
|||
foreach ($this->applicableHelper->diffApplicable($newStorage, $oldStorage) as $user) { |
|||
$this->registerForUser($user, $newStorage); |
|||
} |
|||
} |
|||
|
|||
private function getCacheEntryForRoot(IUser $user, StorageConfig $storage): ICacheEntry { |
|||
try { |
|||
$userStorage = $this->configAdapter->constructStorageForUser($user, clone $storage); |
|||
} catch (\Exception $e) { |
|||
$userStorage = new FailedStorage(['exception' => $e]); |
|||
} |
|||
|
|||
$cachedEntry = $this->storageRootCache->get($userStorage->getId()); |
|||
if ($cachedEntry !== null) { |
|||
return $cachedEntry; |
|||
} |
|||
|
|||
$cache = $userStorage->getCache(); |
|||
$entry = $cache->get(''); |
|||
if ($entry && $entry->getId() !== -1) { |
|||
$this->storageRootCache->set($userStorage->getId(), $entry); |
|||
return $entry; |
|||
} |
|||
|
|||
// create a "fake" root entry so we have a fileid so we don't have to interact with the remote service
|
|||
// this will be scanned on first access
|
|||
$data = [ |
|||
'path' => '', |
|||
'path_hash' => md5(''), |
|||
'size' => 0, |
|||
'unencrypted_size' => 0, |
|||
'mtime' => 0, |
|||
'mimetype' => ICacheEntry::DIRECTORY_MIMETYPE, |
|||
'parent' => -1, |
|||
'name' => '', |
|||
'storage_mtime' => 0, |
|||
'permissions' => 31, |
|||
'storage' => $cache->getNumericStorageId(), |
|||
'etag' => '', |
|||
'encrypted' => 0, |
|||
'checksum' => '', |
|||
]; |
|||
if ($cache->getNumericStorageId() !== -1) { |
|||
$data['fileid'] = $cache->insert('', $data); |
|||
} else { |
|||
$data['fileid'] = -1; |
|||
} |
|||
|
|||
$entry = new CacheEntry($data); |
|||
$this->storageRootCache->set($userStorage->getId(), $entry); |
|||
return $entry; |
|||
} |
|||
|
|||
private function registerForUser(IUser $user, StorageConfig $storage): void { |
|||
$this->userMountCache->addMount( |
|||
$user, |
|||
$storage->getMountPointForUser($user), |
|||
$this->getCacheEntryForRoot($user, $storage), |
|||
ConfigAdapter::class, |
|||
$storage->getId(), |
|||
); |
|||
} |
|||
|
|||
private function handleUserRemoved(IGroup $group, IUser $user): void { |
|||
$storages = $this->storagesService->getAllStoragesForGroup($group); |
|||
foreach ($storages as $storage) { |
|||
if (!$this->applicableHelper->isApplicableForUser($storage, $user)) { |
|||
$this->userMountCache->removeMount($storage->getMountPointForUser($user)); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private function handleUserAdded(IGroup $group, IUser $user): void { |
|||
$storages = $this->storagesService->getAllStoragesForGroup($group); |
|||
foreach ($storages as $storage) { |
|||
$this->registerForUser($user, $storage); |
|||
} |
|||
} |
|||
|
|||
private function handleGroupDeleted(IGroup $group): void { |
|||
$storages = $this->storagesService->getAllStoragesForGroup($group); |
|||
foreach ($storages as $storage) { |
|||
$this->removeGroupFromStorage($storage, $group); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Remove mounts from users in a group, if they don't have access to the storage trough other means |
|||
*/ |
|||
private function removeGroupFromStorage(StorageConfig $storage, IGroup $group): void { |
|||
foreach ($group->searchUsers('') as $user) { |
|||
if (!$this->applicableHelper->isApplicableForUser($storage, $user)) { |
|||
$this->userMountCache->removeMount($storage->getMountPointForUser($user)); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private function handleUserCreated(IUser $user): void { |
|||
$storages = $this->storagesService->getAllGlobalStorages(); |
|||
foreach ($storages as $storage) { |
|||
$this->registerForUser($user, $storage); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Since storage config can rely on login credentials, we might need to update the config |
|||
*/ |
|||
private function onLogin(IUser $user): void { |
|||
$storages = $this->storagesService->getAllGlobalStorages(); |
|||
foreach ($storages as $storage) { |
|||
$this->registerForUser($user, $storage); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,170 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
|
|||
/** |
|||
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors |
|||
* SPDX-License-Identifier: AGPL-3.0-or-later |
|||
*/ |
|||
|
|||
namespace OCA\Files_External\Tests; |
|||
|
|||
use OCA\Files_External\Lib\ApplicableHelper; |
|||
use OCA\Files_External\Lib\StorageConfig; |
|||
use OCP\IGroup; |
|||
use OCP\IGroupManager; |
|||
use OCP\IUser; |
|||
use OCP\IUserManager; |
|||
use PHPUnit\Framework\Attributes\DataProvider; |
|||
use PHPUnit\Framework\MockObject\MockObject; |
|||
use Test\TestCase; |
|||
|
|||
class ApplicableHelperTest extends TestCase { |
|||
private IUserManager|MockObject $userManager; |
|||
private IGroupManager|MockObject $groupManager; |
|||
|
|||
/** @var list<string> */ |
|||
private array $users = []; |
|||
/** @var array<string, list<string>> */ |
|||
private array $groups = []; |
|||
|
|||
private ApplicableHelper $applicableHelper; |
|||
|
|||
protected function setUp(): void { |
|||
parent::setUp(); |
|||
|
|||
$this->userManager = $this->createMock(IUserManager::class); |
|||
$this->groupManager = $this->createMock(IGroupManager::class); |
|||
|
|||
$this->userManager->method('get') |
|||
->willReturnCallback(function (string $id) { |
|||
$user = $this->createMock(IUser::class); |
|||
$user->method('getUID')->willReturn($id); |
|||
return $user; |
|||
}); |
|||
$this->userManager->method('getSeenUsers') |
|||
->willReturnCallback(fn () => new \ArrayIterator(array_map($this->userManager->get(...), $this->users))); |
|||
$this->groupManager->method('get') |
|||
->willReturnCallback(function (string $id) { |
|||
$group = $this->createMock(IGroup::class); |
|||
$group->method('getGID')->willReturn($id); |
|||
$group->method('getUsers') |
|||
->willReturn(array_map($this->userManager->get(...), $this->groups[$id] ?: [])); |
|||
return $group; |
|||
}); |
|||
$this->groupManager->method('getUserGroupIds') |
|||
->willReturnCallback(function (IUser $user) { |
|||
$groups = []; |
|||
foreach ($this->groups as $group => $users) { |
|||
if (in_array($user->getUID(), $users)) { |
|||
$groups[] = $group; |
|||
} |
|||
} |
|||
return $groups; |
|||
}); |
|||
|
|||
$this->applicableHelper = new ApplicableHelper($this->userManager, $this->groupManager); |
|||
|
|||
$this->users = ['user1', 'user2', 'user3', 'user4']; |
|||
$this->groups = [ |
|||
'group1' => ['user1', 'user2'], |
|||
'group2' => ['user3'], |
|||
]; |
|||
} |
|||
|
|||
public static function usersForStorageProvider(): array { |
|||
return [ |
|||
[[], [], ['user1', 'user2', 'user3', 'user4']], |
|||
[['user1'], [], ['user1']], |
|||
[['user1', 'user3'], [], ['user1', 'user3']], |
|||
[['user1'], ['group1'], ['user1', 'user2']], |
|||
[['user1'], ['group2'], ['user1', 'user3']], |
|||
]; |
|||
} |
|||
|
|||
#[DataProvider('usersForStorageProvider')]
|
|||
public function testGetUsersForStorage(array $applicableUsers, array $applicableGroups, array $expected) { |
|||
$storage = $this->createMock(StorageConfig::class); |
|||
$storage->method('getApplicableUsers') |
|||
->willReturn($applicableUsers); |
|||
$storage->method('getApplicableGroups') |
|||
->willReturn($applicableGroups); |
|||
|
|||
$result = iterator_to_array($this->applicableHelper->getUsersForStorage($storage)); |
|||
$result = array_map(fn (IUser $user) => $user->getUID(), $result); |
|||
sort($result); |
|||
sort($expected); |
|||
$this->assertEquals($expected, $result); |
|||
} |
|||
|
|||
public static function applicableProvider(): array { |
|||
return [ |
|||
[[], [], 'user1', true], |
|||
[['user1'], [], 'user1', true], |
|||
[['user1'], [], 'user2', false], |
|||
[['user1', 'user3'], [], 'user1', true], |
|||
[['user1', 'user3'], [], 'user2', false], |
|||
[['user1'], ['group1'], 'user1', true], |
|||
[['user1'], ['group1'], 'user2', true], |
|||
[['user1'], ['group1'], 'user3', false], |
|||
[['user1'], ['group1'], 'user4', false], |
|||
[['user1'], ['group2'], 'user1', true], |
|||
[['user1'], ['group2'], 'user2', false], |
|||
[['user1'], ['group2'], 'user3', true], |
|||
[['user1'], ['group1'], 'user4', false], |
|||
]; |
|||
} |
|||
|
|||
#[DataProvider('applicableProvider')]
|
|||
public function testIsApplicable(array $applicableUsers, array $applicableGroups, string $user, bool $expected) { |
|||
$storage = $this->createMock(StorageConfig::class); |
|||
$storage->method('getApplicableUsers') |
|||
->willReturn($applicableUsers); |
|||
$storage->method('getApplicableGroups') |
|||
->willReturn($applicableGroups); |
|||
|
|||
$this->assertEquals($expected, $this->applicableHelper->isApplicableForUser($storage, $this->userManager->get($user))); |
|||
} |
|||
|
|||
public static function diffProvider(): array { |
|||
return [ |
|||
[[], [], [], [], []], // both all
|
|||
[['user1'], [], [], [], []], // all added
|
|||
[[], [], ['user1'], [], ['user2', 'user3', 'user4']], // all removed
|
|||
[[], [], [], ['group1'], ['user3', 'user4']], // all removed
|
|||
[[], [], ['user3'], ['group1'], ['user4']], // all removed
|
|||
[['user1'], [], ['user1'], [], []], |
|||
[['user1'], [], ['user1', 'user2'], [], []], |
|||
[['user1'], [], ['user2'], [], ['user1']], |
|||
[['user1'], [], [], ['group1'], []], |
|||
[['user1'], [], [], ['group2'], ['user1']], |
|||
[[], ['group1'], [], ['group2'], ['user1', 'user2']], |
|||
[[], ['group1'], ['user1'], [], ['user2']], |
|||
[['user1'], ['group1'], ['user1'], [], ['user2']], |
|||
[['user1'], ['group1'], [], ['group1'], []], |
|||
[['user1'], ['group1'], [], ['group2'], ['user1', 'user2']], |
|||
[['user1'], ['group1'], ['user1'], ['group2'], ['user2']], |
|||
]; |
|||
} |
|||
|
|||
#[DataProvider('diffProvider')]
|
|||
public function testDiff(array $applicableUsersA, array $applicableGroupsA, array $applicableUsersB, array $applicableGroupsB, array $expected) { |
|||
$storageA = $this->createMock(StorageConfig::class); |
|||
$storageA->method('getApplicableUsers') |
|||
->willReturn($applicableUsersA); |
|||
$storageA->method('getApplicableGroups') |
|||
->willReturn($applicableGroupsA); |
|||
|
|||
$storageB = $this->createMock(StorageConfig::class); |
|||
$storageB->method('getApplicableUsers') |
|||
->willReturn($applicableUsersB); |
|||
$storageB->method('getApplicableGroups') |
|||
->willReturn($applicableGroupsB); |
|||
|
|||
$result = iterator_to_array($this->applicableHelper->diffApplicable($storageA, $storageB)); |
|||
$result = array_map(fn (IUser $user) => $user->getUID(), $result); |
|||
sort($result); |
|||
sort($expected); |
|||
$this->assertEquals($expected, $result); |
|||
} |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
|
|||
/** |
|||
* SPDX-FileCopyrightText: 2025 Robin Appelman <robin@icewind.nl> |
|||
* SPDX-License-Identifier: AGPL-3.0-or-later |
|||
*/ |
|||
namespace OCP\Files\Config; |
|||
|
|||
/** |
|||
* Marks a mount provider as being authoritative, meaning that it will proactively update the cached mounts |
|||
* |
|||
* @since 33.0.0 |
|||
*/ |
|||
interface IAuthoritativeMountProvider { |
|||
|
|||
} |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue