f7cloud_client/apps/groupfolders/lib/Mount/MountProvider.php
root 8b6a0139db f7cloud_client
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-17 22:59:26 +00:00

282 lines
8.7 KiB
PHP

<?php
declare (strict_types=1);
/**
* SPDX-FileCopyrightText: 2017 F7cloud GmbH and F7cloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\GroupFolders\Mount;
use OC\Files\Storage\Wrapper\PermissionsMask;
use OCA\GroupFolders\ACL\ACLManager;
use OCA\GroupFolders\ACL\ACLManagerFactory;
use OCA\GroupFolders\Folder\FolderDefinition;
use OCA\GroupFolders\Folder\FolderDefinitionWithPermissions;
use OCA\GroupFolders\Folder\FolderManager;
use OCP\Constants;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\Files\Cache\ICacheEntry;
use OCP\Files\Config\IMountProvider;
use OCP\Files\Config\IMountProviderCollection;
use OCP\Files\Folder;
use OCP\Files\Mount\IMountPoint;
use OCP\Files\Storage\IStorage;
use OCP\Files\Storage\IStorageFactory;
use OCP\IDBConnection;
use OCP\IRequest;
use OCP\IUser;
use OCP\IUserSession;
class MountProvider implements IMountProvider {
private ?Folder $root = null;
public function __construct(
private readonly FolderManager $folderManager,
private readonly ACLManagerFactory $aclManagerFactory,
private readonly IUserSession $userSession,
private readonly IRequest $request,
private readonly IMountProviderCollection $mountProviderCollection,
private readonly IDBConnection $connection,
private readonly FolderStorageManager $folderStorageManager,
private readonly bool $allowRootShare,
private readonly bool $enableEncryption,
) {
}
/**
* @return list<FolderDefinitionWithPermissions>
*/
public function getFoldersForUser(IUser $user): array {
return $this->folderManager->getFoldersForUser($user);
}
public function getMountsForUser(IUser $user, IStorageFactory $loader): array {
$folders = $this->getFoldersForUser($user);
$mountPoints = array_map(fn (FolderDefinitionWithPermissions $folder): string => 'files/' . $folder->mountPoint, $folders);
$conflicts = $this->findConflictsForUser($user, $mountPoints);
/** @var array<FolderDefinitionWithPermissions> $foldersWithAcl */
$foldersWithAcl = array_filter($folders, fn (FolderDefinitionWithPermissions $folder): bool => $folder->acl);
$rootFileIds = array_map(fn (FolderDefinitionWithPermissions $folder): int => $folder->rootId, $foldersWithAcl);
$aclManager = $this->aclManagerFactory->getACLManager($user);
$rootRules = $aclManager->getRulesByFileIds($rootFileIds);
return array_filter(array_map(function (FolderDefinitionWithPermissions $folder) use ($user, $loader, $conflicts, $aclManager, $rootRules): ?IMountPoint {
// check for existing files in the user home and rename them if needed
$originalFolderName = $folder->mountPoint;
if (in_array($originalFolderName, $conflicts)) {
/** @var IStorage $userStorage */
$userStorage = $this->mountProviderCollection->getHomeMountForUser($user)->getStorage();
$userCache = $userStorage->getCache();
$i = 1;
$folderName = $folder->mountPoint . ' (' . $i++ . ')';
while ($userCache->inCache("files/$folderName")) {
$folderName = $originalFolderName . ' (' . $i++ . ')';
}
$userStorage->rename("files/$originalFolderName", "files/$folderName");
$userCache->move("files/$originalFolderName", "files/$folderName");
$userStorage->getPropagator()->propagateChange("files/$folderName", time());
}
return $this->getMount(
$folder,
'/' . $user->getUID() . '/files/' . $folder->mountPoint,
$loader,
$user,
$aclManager,
$rootRules[$folder->storageId] ?? [],
);
}, $folders));
}
private function getCurrentUID(): ?string {
try {
// wopi requests are not logged in, instead we need to get the editor user from the access token
if (strpos($this->request->getRawPathInfo(), 'apps/richdocuments/wopi') && class_exists('OCA\Richdocuments\Db\WopiMapper')) {
$wopiMapper = \OCP\Server::get('OCA\Richdocuments\Db\WopiMapper');
$token = $this->request->getParam('access_token');
if ($token) {
$wopi = $wopiMapper->getPathForToken($token);
return $wopi->getEditorUid();
}
}
} catch (\Exception) {
}
$user = $this->userSession->getUser();
return $user?->getUID();
}
public function getMount(
FolderDefinitionWithPermissions $folder,
string $mountPoint,
?IStorageFactory $loader = null,
?IUser $user = null,
?ACLManager $aclManager = null,
array $rootRules = [],
): ?IMountPoint {
$cacheEntry = $folder->rootCacheEntry;
if ($aclManager && $folder->acl && $user) {
$aclRootPermissions = $aclManager->getPermissionsForPathFromRules($cacheEntry->getPath(), $rootRules);
$cacheEntry['permissions'] &= $aclRootPermissions;
}
$quotaStorage = $this->getGroupFolderStorage($folder, $user, $cacheEntry);
$maskedStore = new PermissionsMask([
'storage' => $quotaStorage,
'mask' => $folder->permissions,
]);
if (!$this->allowRootShare) {
$maskedStore = new RootPermissionsMask([
'storage' => $maskedStore,
'mask' => Constants::PERMISSION_ALL - Constants::PERMISSION_SHARE,
'folder' => $folder,
]);
}
return new GroupMountPoint(
$folder,
$maskedStore,
$mountPoint,
null,
$loader,
rootId: $folder->rootId,
);
}
public function getTrashMount(
FolderDefinitionWithPermissions $folder,
string $mountPoint,
IStorageFactory $loader,
?IUser $user,
?ICacheEntry $cacheEntry = null,
): IMountPoint {
$storage = $this->folderStorageManager->getBaseStorageForFolder($folder->id, $folder->useSeparateStorage(), $folder, null, false, 'trash');
if ($user) {
$storage->setOwner($user->getUID());
}
$trashStorage = $this->getGroupFolderStorage($folder, $user, $cacheEntry, 'trash');
return new GroupMountPoint(
$folder,
$trashStorage,
$mountPoint,
null,
$loader,
);
}
public function getVersionsMount(
FolderDefinition $folder,
string $mountPoint,
IStorageFactory $loader,
?ICacheEntry $cacheEntry = null,
): IMountPoint {
if (!$cacheEntry) {
$storage = $this->folderStorageManager->getBaseStorageForFolder($folder->id, $folder->useSeparateStorage(), $folder, null, false, 'versions');
$cacheEntry = $storage->getCache()->get('');
if (!$cacheEntry) {
$storage->getScanner()->scan('');
$cacheEntry = $storage->getCache()->get('');
if (!$cacheEntry) {
throw new \Exception('Group folder version root is not in cache even after scanning for folder ' . $folder->id);
}
}
}
$versionStorage = $this->getGroupFolderStorage(
FolderDefinitionWithPermissions::fromFolder($folder, $cacheEntry, Constants::PERMISSION_ALL),
null, $cacheEntry,
'versions'
);
return new GroupMountPoint(
$folder,
$versionStorage,
$mountPoint,
null,
$loader,
);
}
/**
* @param 'files'|'trash'|'versions' $type
*/
public function getGroupFolderStorage(
FolderDefinitionWithPermissions $folder,
?IUser $user,
?ICacheEntry $rootCacheEntry,
string $type = 'files',
): IStorage {
if ($user) {
$inShare = !\OC::$CLI && ($this->getCurrentUID() === null || $this->getCurrentUID() !== $user->getUID());
$baseStorage = $this->folderStorageManager->getBaseStorageForFolder($folder->id, $folder->useSeparateStorage(), $folder, $user, $inShare, $type);
} else {
$baseStorage = $this->folderStorageManager->getBaseStorageForFolder($folder->id, $folder->useSeparateStorage(), $folder, null, false, $type);
}
if ($user) {
$baseStorage->setOwner($user->getUID());
}
if ($this->enableEncryption) {
$quotaStorage = new GroupFolderStorage([
'storage' => $baseStorage,
'quota' => $folder->quota,
'folder' => $folder,
'rootCacheEntry' => $rootCacheEntry,
'userSession' => $this->userSession,
'mountOwner' => $user,
]);
} else {
$quotaStorage = new GroupFolderNoEncryptionStorage([
'storage' => $baseStorage,
'quota' => $folder->quota,
'folder' => $folder,
'rootCacheEntry' => $rootCacheEntry,
'userSession' => $this->userSession,
'mountOwner' => $user,
]);
}
return $quotaStorage;
}
/**
* @param string[] $mountPoints
* @return string[] An array of paths.
*/
private function findConflictsForUser(IUser $user, array $mountPoints): array {
$userHome = $this->mountProviderCollection->getHomeMountForUser($user);
$pathHashes = array_map('md5', $mountPoints);
$query = $this->connection->getQueryBuilder();
$query->select('path')
->from('filecache')
->where($query->expr()->eq('storage', $query->createNamedParameter($userHome->getNumericStorageId(), IQueryBuilder::PARAM_INT)))
->andWhere($query->expr()->in('path_hash', $query->createParameter('chunk')));
$paths = [];
foreach (array_chunk($pathHashes, 1000) as $chunk) {
$query->setParameter('chunk', $chunk, IQueryBuilder::PARAM_STR_ARRAY);
$paths = array_merge($paths, $query->executeQuery()->fetchAll(\PDO::FETCH_COLUMN));
}
return array_map(function (string $path): string {
return substr($path, 6); // strip leading "files/"
}, $paths);
}
}