*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*
*/
namespace OCA\GroupFolders\Mount;
use OC\Files\Storage\Wrapper\Jail;
use OC\Files\Storage\Wrapper\PermissionsMask;
use OCA\GroupFolders\ACL\ACLManagerFactory;
use OCA\GroupFolders\ACL\ACLStorageWrapper;
use OCA\GroupFolders\Folder\FolderManager;
use OCP\Constants;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\Files\Config\IMountProvider;
use OCP\Files\Config\IMountProviderCollection;
use OCP\Files\Folder;
use OCP\Files\Node;
use OCP\Files\Cache\ICacheEntry;
use OCP\Files\Mount\IMountPoint;
use OCP\Files\NotFoundException;
use OCP\Files\Storage\IStorage;
use OCP\Files\Storage\IStorageFactory;
use OCP\IDBConnection;
use OCP\IGroupManager;
use OCP\IRequest;
use OCP\ISession;
use OCP\IUser;
use OCP\IUserSession;
class MountProvider implements IMountProvider {
/** @var IGroupManager */
private $groupProvider;
/** @var callable */
private $rootProvider;
/** @var Folder|null */
private $root = null;
/** @var FolderManager */
private $folderManager;
private $aclManagerFactory;
private $userSession;
private $request;
private $session;
private $mountProviderCollection;
private $connection;
private bool $allowRootShare;
public function __construct(
IGroupManager $groupProvider,
FolderManager $folderManager,
callable $rootProvider,
ACLManagerFactory $aclManagerFactory,
IUserSession $userSession,
IRequest $request,
ISession $session,
IMountProviderCollection $mountProviderCollection,
IDBConnection $connection,
bool $allowRootShare
) {
$this->groupProvider = $groupProvider;
$this->folderManager = $folderManager;
$this->rootProvider = $rootProvider;
$this->aclManagerFactory = $aclManagerFactory;
$this->userSession = $userSession;
$this->request = $request;
$this->session = $session;
$this->mountProviderCollection = $mountProviderCollection;
$this->connection = $connection;
$this->allowRootShare = $allowRootShare;
}
/**
* @return list
*/
public function getFoldersForUser(IUser $user): array {
return $this->folderManager->getFoldersForUser($user, $this->getRootFolder()->getStorage()->getCache()->getNumericStorageId());
}
public function getMountsForUser(IUser $user, IStorageFactory $loader) {
$folders = $this->getFoldersForUser($user);
$mountPoints = array_map(function (array $folder) {
return 'files/' . $folder['mount_point'];
}, $folders);
$conflicts = $this->findConflictsForUser($user, $mountPoints);
return array_values(array_filter(array_map(function ($folder) use ($user, $loader, $conflicts) {
// check for existing files in the user home and rename them if needed
$originalFolderName = $folder['mount_point'];
if (in_array($originalFolderName, $conflicts)) {
/** @var IStorage $userStorage */
$userStorage = $this->mountProviderCollection->getHomeMountForUser($user)->getStorage();
$userCache = $userStorage->getCache();
$i = 1;
$folderName = $folder['mount_point'] . ' (' . $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['folder_id'],
'/' . $user->getUID() . '/files/' . $folder['mount_point'],
$folder['permissions'],
$folder['quota'],
$folder['rootCacheEntry'],
$loader,
$folder['acl'],
$user
);
}, $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 = \OC::$server->query('OCA\Richdocuments\Db\WopiMapper');
$token = $this->request->getParam('access_token');
if ($token) {
$wopi = $wopiMapper->getPathForToken($token);
return $wopi->getEditorUid();
}
}
} catch (\Exception $e) {
}
$user = $this->userSession->getUser();
return $user ? $user->getUID() : null;
}
public function getMount(int $id, string $mountPoint, int $permissions, int $quota, ?ICacheEntry $cacheEntry = null, IStorageFactory $loader = null, bool $acl = false, IUser $user = null): ?IMountPoint {
if (!$cacheEntry) {
// trigger folder creation
$folder = $this->getFolder($id);
if ($folder === null) {
return null;
}
$cacheEntry = $this->getRootFolder()->getStorage()->getCache()->get($folder->getId());
}
$storage = $this->getRootFolder()->getStorage();
$rootPath = $this->getJailPath($id);
// apply acl before jail
if ($acl && $user) {
$inShare = $this->getCurrentUID() === null || $this->getCurrentUID() !== $user->getUID();
$aclManager = $this->aclManagerFactory->getACLManager($user);
$storage = new ACLStorageWrapper([
'storage' => $storage,
'acl_manager' => $aclManager,
'in_share' => $inShare
]);
$aclRootPermissions = $aclManager->getACLPermissionsForPath($rootPath);
$cacheEntry['permissions'] &= $aclRootPermissions;
}
$baseStorage = new Jail([
'storage' => $storage,
'root' => $rootPath
]);
$quotaStorage = new GroupFolderStorage([
'storage' => $baseStorage,
'quota' => $quota,
'folder_id' => $id,
'rootCacheEntry' => $cacheEntry,
'userSession' => $this->userSession,
'mountOwner' => $user,
]);
$maskedStore = new PermissionsMask([
'storage' => $quotaStorage,
'mask' => $permissions
]);
if (!$this->allowRootShare) {
$maskedStore = new RootPermissionsMask([
'storage' => $maskedStore,
'mask' => Constants::PERMISSION_ALL - Constants::PERMISSION_SHARE,
]);
}
return new GroupMountPoint(
$id,
$maskedStore,
$mountPoint,
null,
$loader
);
}
public function getJailPath(int $folderId): string {
return $this->getRootFolder()->getInternalPath() . '/' . $folderId;
}
private function getRootFolder(): Folder {
if (is_null($this->root)) {
$rootProvider = $this->rootProvider;
$this->root = $rootProvider();
}
return $this->root;
}
public function getFolder(int $id, bool $create = true): ?Node {
try {
return $this->getRootFolder()->get((string)$id);
} catch (NotFoundException $e) {
if ($create) {
return $this->getRootFolder()->newFolder((string)$id);
} else {
return null;
}
}
}
/**
* @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);
}
}