Skip to content

Commit ae74dbe

Browse files
authored
Merge pull request #56499 from nextcloud/authoritative-mount-provider-files_external
Add api for authoritative mount providers and implement it for files_external
2 parents 0fa396d + 272d614 commit ae74dbe

29 files changed

+786
-45
lines changed

apps/files_external/composer/composer/autoload_classmap.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@
3737
'OCA\\Files_External\\Controller\\StoragesController' => $baseDir . '/../lib/Controller/StoragesController.php',
3838
'OCA\\Files_External\\Controller\\UserGlobalStoragesController' => $baseDir . '/../lib/Controller/UserGlobalStoragesController.php',
3939
'OCA\\Files_External\\Controller\\UserStoragesController' => $baseDir . '/../lib/Controller/UserStoragesController.php',
40+
'OCA\\Files_External\\Event\\StorageCreatedEvent' => $baseDir . '/../lib/Event/StorageCreatedEvent.php',
41+
'OCA\\Files_External\\Event\\StorageDeletedEvent' => $baseDir . '/../lib/Event/StorageDeletedEvent.php',
42+
'OCA\\Files_External\\Event\\StorageUpdatedEvent' => $baseDir . '/../lib/Event/StorageUpdatedEvent.php',
43+
'OCA\\Files_External\\Lib\\ApplicableHelper' => $baseDir . '/../lib/Lib/ApplicableHelper.php',
4044
'OCA\\Files_External\\Lib\\Auth\\AmazonS3\\AccessKey' => $baseDir . '/../lib/Lib/Auth/AmazonS3/AccessKey.php',
4145
'OCA\\Files_External\\Lib\\Auth\\AuthMechanism' => $baseDir . '/../lib/Lib/Auth/AuthMechanism.php',
4246
'OCA\\Files_External\\Lib\\Auth\\Builtin' => $baseDir . '/../lib/Lib/Auth/Builtin.php',
@@ -116,6 +120,7 @@
116120
'OCA\\Files_External\\Service\\GlobalStoragesService' => $baseDir . '/../lib/Service/GlobalStoragesService.php',
117121
'OCA\\Files_External\\Service\\ImportLegacyStoragesService' => $baseDir . '/../lib/Service/ImportLegacyStoragesService.php',
118122
'OCA\\Files_External\\Service\\LegacyStoragesService' => $baseDir . '/../lib/Service/LegacyStoragesService.php',
123+
'OCA\\Files_External\\Service\\MountCacheService' => $baseDir . '/../lib/Service/MountCacheService.php',
119124
'OCA\\Files_External\\Service\\StoragesService' => $baseDir . '/../lib/Service/StoragesService.php',
120125
'OCA\\Files_External\\Service\\UserGlobalStoragesService' => $baseDir . '/../lib/Service/UserGlobalStoragesService.php',
121126
'OCA\\Files_External\\Service\\UserStoragesService' => $baseDir . '/../lib/Service/UserStoragesService.php',

apps/files_external/composer/composer/autoload_static.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ class ComposerStaticInitFiles_External
5252
'OCA\\Files_External\\Controller\\StoragesController' => __DIR__ . '/..' . '/../lib/Controller/StoragesController.php',
5353
'OCA\\Files_External\\Controller\\UserGlobalStoragesController' => __DIR__ . '/..' . '/../lib/Controller/UserGlobalStoragesController.php',
5454
'OCA\\Files_External\\Controller\\UserStoragesController' => __DIR__ . '/..' . '/../lib/Controller/UserStoragesController.php',
55+
'OCA\\Files_External\\Event\\StorageCreatedEvent' => __DIR__ . '/..' . '/../lib/Event/StorageCreatedEvent.php',
56+
'OCA\\Files_External\\Event\\StorageDeletedEvent' => __DIR__ . '/..' . '/../lib/Event/StorageDeletedEvent.php',
57+
'OCA\\Files_External\\Event\\StorageUpdatedEvent' => __DIR__ . '/..' . '/../lib/Event/StorageUpdatedEvent.php',
58+
'OCA\\Files_External\\Lib\\ApplicableHelper' => __DIR__ . '/..' . '/../lib/Lib/ApplicableHelper.php',
5559
'OCA\\Files_External\\Lib\\Auth\\AmazonS3\\AccessKey' => __DIR__ . '/..' . '/../lib/Lib/Auth/AmazonS3/AccessKey.php',
5660
'OCA\\Files_External\\Lib\\Auth\\AuthMechanism' => __DIR__ . '/..' . '/../lib/Lib/Auth/AuthMechanism.php',
5761
'OCA\\Files_External\\Lib\\Auth\\Builtin' => __DIR__ . '/..' . '/../lib/Lib/Auth/Builtin.php',
@@ -131,6 +135,7 @@ class ComposerStaticInitFiles_External
131135
'OCA\\Files_External\\Service\\GlobalStoragesService' => __DIR__ . '/..' . '/../lib/Service/GlobalStoragesService.php',
132136
'OCA\\Files_External\\Service\\ImportLegacyStoragesService' => __DIR__ . '/..' . '/../lib/Service/ImportLegacyStoragesService.php',
133137
'OCA\\Files_External\\Service\\LegacyStoragesService' => __DIR__ . '/..' . '/../lib/Service/LegacyStoragesService.php',
138+
'OCA\\Files_External\\Service\\MountCacheService' => __DIR__ . '/..' . '/../lib/Service/MountCacheService.php',
134139
'OCA\\Files_External\\Service\\StoragesService' => __DIR__ . '/..' . '/../lib/Service/StoragesService.php',
135140
'OCA\\Files_External\\Service\\UserGlobalStoragesService' => __DIR__ . '/..' . '/../lib/Service/UserGlobalStoragesService.php',
136141
'OCA\\Files_External\\Service\\UserStoragesService' => __DIR__ . '/..' . '/../lib/Service/UserStoragesService.php',

apps/files_external/lib/AppInfo/Application.php

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
use OCA\Files_External\Config\ConfigAdapter;
1212
use OCA\Files_External\Config\UserPlaceholderHandler;
1313
use OCA\Files_External\ConfigLexicon;
14+
use OCA\Files_External\Event\StorageCreatedEvent;
15+
use OCA\Files_External\Event\StorageDeletedEvent;
16+
use OCA\Files_External\Event\StorageUpdatedEvent;
1417
use OCA\Files_External\Lib\Auth\AmazonS3\AccessKey;
1518
use OCA\Files_External\Lib\Auth\Builtin;
1619
use OCA\Files_External\Lib\Auth\NullMechanism;
@@ -41,19 +44,22 @@
4144
use OCA\Files_External\Lib\Config\IBackendProvider;
4245
use OCA\Files_External\Listener\GroupDeletedListener;
4346
use OCA\Files_External\Listener\LoadAdditionalListener;
44-
use OCA\Files_External\Listener\StorePasswordListener;
4547
use OCA\Files_External\Listener\UserDeletedListener;
4648
use OCA\Files_External\Service\BackendService;
49+
use OCA\Files_External\Service\MountCacheService;
4750
use OCP\AppFramework\App;
4851
use OCP\AppFramework\Bootstrap\IBootContext;
4952
use OCP\AppFramework\Bootstrap\IBootstrap;
5053
use OCP\AppFramework\Bootstrap\IRegistrationContext;
5154
use OCP\AppFramework\QueryException;
5255
use OCP\Files\Config\IMountProviderCollection;
56+
use OCP\Group\Events\BeforeGroupDeletedEvent;
5357
use OCP\Group\Events\GroupDeletedEvent;
54-
use OCP\User\Events\PasswordUpdatedEvent;
58+
use OCP\Group\Events\UserAddedEvent;
59+
use OCP\Group\Events\UserRemovedEvent;
60+
use OCP\User\Events\PostLoginEvent;
61+
use OCP\User\Events\UserCreatedEvent;
5562
use OCP\User\Events\UserDeletedEvent;
56-
use OCP\User\Events\UserLoggedInEvent;
5763

5864
/**
5965
* @package OCA\Files_External\AppInfo
@@ -74,8 +80,15 @@ public function register(IRegistrationContext $context): void {
7480
$context->registerEventListener(UserDeletedEvent::class, UserDeletedListener::class);
7581
$context->registerEventListener(GroupDeletedEvent::class, GroupDeletedListener::class);
7682
$context->registerEventListener(LoadAdditionalScriptsEvent::class, LoadAdditionalListener::class);
77-
$context->registerEventListener(UserLoggedInEvent::class, StorePasswordListener::class);
78-
$context->registerEventListener(PasswordUpdatedEvent::class, StorePasswordListener::class);
83+
$context->registerEventListener(StorageCreatedEvent::class, MountCacheService::class);
84+
$context->registerEventListener(StorageDeletedEvent::class, MountCacheService::class);
85+
$context->registerEventListener(StorageUpdatedEvent::class, MountCacheService::class);
86+
$context->registerEventListener(BeforeGroupDeletedEvent::class, MountCacheService::class);
87+
$context->registerEventListener(UserCreatedEvent::class, MountCacheService::class);
88+
$context->registerEventListener(UserAddedEvent::class, MountCacheService::class);
89+
$context->registerEventListener(UserRemovedEvent::class, MountCacheService::class);
90+
$context->registerEventListener(PostLoginEvent::class, MountCacheService::class);
91+
7992
$context->registerConfigLexicon(ConfigLexicon::class);
8093
}
8194

apps/files_external/lib/Config/ConfigAdapter.php

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use OCA\Files_External\Service\UserGlobalStoragesService;
1818
use OCA\Files_External\Service\UserStoragesService;
1919
use OCP\AppFramework\QueryException;
20+
use OCP\Files\Config\IAuthoritativeMountProvider;
2021
use OCP\Files\Config\IMountProvider;
2122
use OCP\Files\Mount\IMountPoint;
2223
use OCP\Files\ObjectStore\IObjectStore;
@@ -32,7 +33,7 @@
3233
/**
3334
* Make the old files_external config work with the new public mount config api
3435
*/
35-
class ConfigAdapter implements IMountProvider {
36+
class ConfigAdapter implements IMountProvider, IAuthoritativeMountProvider {
3637
public function __construct(
3738
private UserStoragesService $userStoragesService,
3839
private UserGlobalStoragesService $userGlobalStoragesService,
@@ -73,6 +74,11 @@ private function prepareStorageConfig(StorageConfig &$storage, IUser $user): voi
7374
$storage->getBackend()->manipulateStorageConfig($storage, $user);
7475
}
7576

77+
public function constructStorageForUser(IUser $user, StorageConfig $storage) {
78+
$this->prepareStorageConfig($storage, $user);
79+
return $this->constructStorage($storage);
80+
}
81+
7682
/**
7783
* Construct the storage implementation
7884
*
@@ -105,8 +111,7 @@ public function getMountsForUser(IUser $user, IStorageFactory $loader) {
105111

106112
$storages = array_map(function (StorageConfig $storageConfig) use ($user) {
107113
try {
108-
$this->prepareStorageConfig($storageConfig, $user);
109-
return $this->constructStorage($storageConfig);
114+
return $this->constructStorageForUser($user, $storageConfig);
110115
} catch (\Exception $e) {
111116
// propagate exception into filesystem
112117
return new FailedStorage(['exception' => $e]);
@@ -123,7 +128,7 @@ public function getMountsForUser(IUser $user, IStorageFactory $loader) {
123128
$availability = $storage->getAvailability();
124129
if (!$availability['available'] && !Availability::shouldRecheck($availability)) {
125130
$storage = new FailedStorage([
126-
'exception' => new StorageNotAvailableException('Storage with mount id ' . $storageConfig->getId() . ' is not available')
131+
'exception' => new StorageNotAvailableException('Storage with mount id ' . $storageConfig->getId() . ' is not available'),
127132
]);
128133
}
129134
} catch (\Exception $e) {
@@ -148,7 +153,7 @@ public function getMountsForUser(IUser $user, IStorageFactory $loader) {
148153
null,
149154
$loader,
150155
$storageConfig->getMountOptions(),
151-
$storageConfig->getId()
156+
$storageConfig->getId(),
152157
);
153158
} else {
154159
return new SystemMountPoint(
@@ -158,7 +163,7 @@ public function getMountsForUser(IUser $user, IStorageFactory $loader) {
158163
null,
159164
$loader,
160165
$storageConfig->getMountOptions(),
161-
$storageConfig->getId()
166+
$storageConfig->getId(),
162167
);
163168
}
164169
}, $storageConfigs, $availableStorages);

apps/files_external/lib/Config/UserContext.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,10 @@ protected function getUserId(): ?string {
4343
}
4444
try {
4545
$shareToken = $this->request->getParam('token');
46-
$share = $this->shareManager->getShareByToken($shareToken);
47-
return $share->getShareOwner();
46+
if ($shareToken !== null) {
47+
$share = $this->shareManager->getShareByToken($shareToken);
48+
return $share->getShareOwner();
49+
}
4850
} catch (ShareNotFound $e) {
4951
}
5052

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
/**
5+
* SPDX-FileCopyrightText: 2025 Robin Appelman <robin@icewind.nl>
6+
* SPDX-License-Identifier: AGPL-3.0-or-later
7+
*/
8+
9+
namespace OCA\Files_External\Event;
10+
11+
use OCA\Files_External\Lib\StorageConfig;
12+
use OCP\EventDispatcher\Event;
13+
14+
class StorageCreatedEvent extends Event {
15+
public function __construct(
16+
private readonly StorageConfig $newConfig,
17+
) {
18+
parent::__construct();
19+
}
20+
21+
public function getNewConfig(): StorageConfig {
22+
return $this->newConfig;
23+
}
24+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
/**
5+
* SPDX-FileCopyrightText: 2025 Robin Appelman <robin@icewind.nl>
6+
* SPDX-License-Identifier: AGPL-3.0-or-later
7+
*/
8+
9+
namespace OCA\Files_External\Event;
10+
11+
use OCA\Files_External\Lib\StorageConfig;
12+
use OCP\EventDispatcher\Event;
13+
14+
class StorageDeletedEvent extends Event {
15+
public function __construct(
16+
private readonly StorageConfig $oldConfig,
17+
) {
18+
parent::__construct();
19+
}
20+
21+
public function getOldConfig(): StorageConfig {
22+
return $this->oldConfig;
23+
}
24+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
/**
5+
* SPDX-FileCopyrightText: 2025 Robin Appelman <robin@icewind.nl>
6+
* SPDX-License-Identifier: AGPL-3.0-or-later
7+
*/
8+
9+
namespace OCA\Files_External\Event;
10+
11+
use OCA\Files_External\Lib\StorageConfig;
12+
use OCP\EventDispatcher\Event;
13+
14+
class StorageUpdatedEvent extends Event {
15+
public function __construct(
16+
private readonly StorageConfig $oldConfig,
17+
private readonly StorageConfig $newConfig,
18+
) {
19+
parent::__construct();
20+
}
21+
22+
public function getOldConfig(): StorageConfig {
23+
return $this->oldConfig;
24+
}
25+
26+
public function getNewConfig(): StorageConfig {
27+
return $this->newConfig;
28+
}
29+
}
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
/**
5+
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
6+
* SPDX-License-Identifier: AGPL-3.0-or-later
7+
*/
8+
9+
namespace OCA\Files_External\Lib;
10+
11+
use OC\User\LazyUser;
12+
use OCP\IGroupManager;
13+
use OCP\IUser;
14+
use OCP\IUserManager;
15+
16+
class ApplicableHelper {
17+
public function __construct(
18+
private readonly IUserManager $userManager,
19+
private readonly IGroupManager $groupManager,
20+
) {
21+
}
22+
23+
/**
24+
* Get all users that have access to a storage
25+
*
26+
* @return \Iterator<string, IUser>
27+
*/
28+
public function getUsersForStorage(StorageConfig $storage): \Iterator {
29+
$yielded = [];
30+
if (count($storage->getApplicableUsers()) + count($storage->getApplicableGroups()) === 0) {
31+
yield from $this->userManager->getSeenUsers();
32+
}
33+
foreach ($storage->getApplicableUsers() as $userId) {
34+
$yielded[$userId] = true;
35+
yield $userId => new LazyUser($userId, $this->userManager);
36+
}
37+
foreach ($storage->getApplicableGroups() as $groupId) {
38+
$group = $this->groupManager->get($groupId);
39+
if ($group !== null) {
40+
foreach ($group->getUsers() as $user) {
41+
if (!isset($yielded[$user->getUID()])) {
42+
$yielded[$user->getUID()] = true;
43+
yield $user->getUID() => $user;
44+
}
45+
}
46+
}
47+
}
48+
}
49+
50+
public function isApplicableForUser(StorageConfig $storage, IUser $user): bool {
51+
if (count($storage->getApplicableUsers()) + count($storage->getApplicableGroups()) === 0) {
52+
return true;
53+
}
54+
if (in_array($user->getUID(), $storage->getApplicableUsers())) {
55+
return true;
56+
}
57+
$groupIds = $this->groupManager->getUserGroupIds($user);
58+
foreach ($groupIds as $groupId) {
59+
if (in_array($groupId, $storage->getApplicableGroups())) {
60+
return true;
61+
}
62+
}
63+
return false;
64+
}
65+
66+
/**
67+
* Return all users that are applicable for storage $a, but not for $b
68+
*
69+
* @return \Iterator<IUser>
70+
*/
71+
public function diffApplicable(StorageConfig $a, StorageConfig $b): \Iterator {
72+
$aIsAll = count($a->getApplicableUsers()) + count($a->getApplicableGroups()) === 0;
73+
$bIsAll = count($b->getApplicableUsers()) + count($b->getApplicableGroups()) === 0;
74+
if ($bIsAll) {
75+
return;
76+
}
77+
78+
if ($aIsAll) {
79+
foreach ($this->getUsersForStorage($a) as $user) {
80+
if (!$this->isApplicableForUser($b, $user)) {
81+
yield $user;
82+
}
83+
}
84+
} else {
85+
$yielded = [];
86+
foreach ($a->getApplicableGroups() as $groupId) {
87+
if (!in_array($groupId, $b->getApplicableGroups())) {
88+
$group = $this->groupManager->get($groupId);
89+
if ($group) {
90+
foreach ($group->getUsers() as $user) {
91+
if (!$this->isApplicableForUser($b, $user)) {
92+
if (!isset($yielded[$user->getUID()])) {
93+
$yielded[$user->getUID()] = true;
94+
yield $user;
95+
}
96+
}
97+
}
98+
}
99+
}
100+
}
101+
foreach ($a->getApplicableUsers() as $userId) {
102+
if (!in_array($userId, $b->getApplicableUsers())) {
103+
$user = $this->userManager->get($userId);
104+
if ($user && !$this->isApplicableForUser($b, $user)) {
105+
if (!isset($yielded[$user->getUID()])) {
106+
$yielded[$user->getUID()] = true;
107+
yield $user;
108+
}
109+
}
110+
}
111+
}
112+
}
113+
}
114+
}

apps/files_external/lib/Lib/StorageConfig.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use OCA\Files_External\Lib\Auth\IUserProvided;
1313
use OCA\Files_External\Lib\Backend\Backend;
1414
use OCA\Files_External\ResponseDefinitions;
15+
use OCP\IUser;
1516

1617
/**
1718
* External storage configuration
@@ -435,4 +436,13 @@ protected function formatStorageForUI(): void {
435436
}
436437
}
437438
}
439+
440+
public function getMountPointForUser(IUser $user): string {
441+
return '/' . $user->getUID() . '/files/' . trim($this->mountPoint, '/') . '/';
442+
}
443+
444+
public function __clone() {
445+
$this->backend = clone $this->backend;
446+
$this->authMechanism = clone $this->authMechanism;
447+
}
438448
}

0 commit comments

Comments
 (0)