1<?php 2/** 3 * @copyright Copyright (c) 2017 Robin Appelman <robin@icewind.nl> 4 * 5 * @license GNU AGPL version 3 or any later version 6 * 7 * This program is free software: you can redistribute it and/or modify 8 * it under the terms of the GNU Affero General Public License as 9 * published by the Free Software Foundation, either version 3 of the 10 * License, or (at your option) any later version. 11 * 12 * This program is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 * GNU Affero General Public License for more details. 16 * 17 * You should have received a copy of the GNU Affero General Public License 18 * along with this program. If not, see <http://www.gnu.org/licenses/>. 19 * 20 */ 21 22namespace OCA\GroupFolders\Mount; 23 24use OC\Files\Storage\Wrapper\Jail; 25use OC\Files\Storage\Wrapper\PermissionsMask; 26use OCA\GroupFolders\ACL\ACLManagerFactory; 27use OCA\GroupFolders\ACL\ACLStorageWrapper; 28use OCA\GroupFolders\Folder\FolderManager; 29use OCP\DB\QueryBuilder\IQueryBuilder; 30use OCP\Files\Config\IMountProvider; 31use OCP\Files\Config\IMountProviderCollection; 32use OCP\Files\Folder; 33use OCP\Files\Node; 34use OCP\Files\Cache\ICacheEntry; 35use OCP\Files\Mount\IMountPoint; 36use OCP\Files\NotFoundException; 37use OCP\Files\Storage\IStorage; 38use OCP\Files\Storage\IStorageFactory; 39use OCP\IDBConnection; 40use OCP\IGroupManager; 41use OCP\IRequest; 42use OCP\ISession; 43use OCP\IUser; 44use OCP\IUserSession; 45 46class MountProvider implements IMountProvider { 47 /** @var IGroupManager */ 48 private $groupProvider; 49 50 /** @var callable */ 51 private $rootProvider; 52 53 /** @var Folder|null */ 54 private $root = null; 55 56 /** @var FolderManager */ 57 private $folderManager; 58 59 private $aclManagerFactory; 60 61 private $userSession; 62 63 private $request; 64 65 private $session; 66 67 private $mountProviderCollection; 68 private $connection; 69 70 public function __construct( 71 IGroupManager $groupProvider, 72 FolderManager $folderManager, 73 callable $rootProvider, 74 ACLManagerFactory $aclManagerFactory, 75 IUserSession $userSession, 76 IRequest $request, 77 ISession $session, 78 IMountProviderCollection $mountProviderCollection, 79 IDBConnection $connection 80 ) { 81 $this->groupProvider = $groupProvider; 82 $this->folderManager = $folderManager; 83 $this->rootProvider = $rootProvider; 84 $this->aclManagerFactory = $aclManagerFactory; 85 $this->userSession = $userSession; 86 $this->request = $request; 87 $this->session = $session; 88 $this->mountProviderCollection = $mountProviderCollection; 89 $this->connection = $connection; 90 } 91 92 /** 93 * @return list<array{folder_id: int, mount_point: string, permissions: int, quota: int, acl: bool, rootCacheEntry: ?ICacheEntry}> 94 */ 95 public function getFoldersForUser(IUser $user): array { 96 return $this->folderManager->getFoldersForUser($user, $this->getRootFolder()->getStorage()->getCache()->getNumericStorageId()); 97 } 98 99 public function getMountsForUser(IUser $user, IStorageFactory $loader) { 100 $folders = $this->getFoldersForUser($user); 101 102 $mountPoints = array_map(function (array $folder) { 103 return 'files/' . $folder['mount_point']; 104 }, $folders); 105 $conflicts = $this->findConflictsForUser($user, $mountPoints); 106 107 return array_values(array_filter(array_map(function ($folder) use ($user, $loader, $conflicts) { 108 // check for existing files in the user home and rename them if needed 109 $originalFolderName = $folder['mount_point']; 110 if (in_array($originalFolderName, $conflicts)) { 111 /** @var IStorage $userStorage */ 112 $userStorage = $this->mountProviderCollection->getHomeMountForUser($user)->getStorage(); 113 $userCache = $userStorage->getCache(); 114 $i = 1; 115 $folderName = $folder['mount_point'] . ' (' . $i++ . ')'; 116 117 while ($userCache->inCache("files/$folderName")) { 118 $folderName = $originalFolderName . ' (' . $i++ . ')'; 119 } 120 121 $userStorage->rename("files/$originalFolderName", "files/$folderName"); 122 $userCache->move("files/$originalFolderName", "files/$folderName"); 123 $userStorage->getPropagator()->propagateChange("files/$folderName", time()); 124 } 125 126 return $this->getMount( 127 $folder['folder_id'], 128 '/' . $user->getUID() . '/files/' . $folder['mount_point'], 129 $folder['permissions'], 130 $folder['quota'], 131 $folder['rootCacheEntry'], 132 $loader, 133 $folder['acl'], 134 $user 135 ); 136 }, $folders))); 137 } 138 139 private function getCurrentUID(): ?string { 140 try { 141 // wopi requests are not logged in, instead we need to get the editor user from the access token 142 if (strpos($this->request->getRawPathInfo(), 'apps/richdocuments/wopi') && class_exists('OCA\Richdocuments\Db\WopiMapper')) { 143 $wopiMapper = \OC::$server->query('OCA\Richdocuments\Db\WopiMapper'); 144 $token = $this->request->getParam('access_token'); 145 if ($token) { 146 $wopi = $wopiMapper->getPathForToken($token); 147 return $wopi->getEditorUid(); 148 } 149 } 150 } catch (\Exception $e) { 151 } 152 153 $user = $this->userSession->getUser(); 154 return $user ? $user->getUID() : null; 155 } 156 157 public function getMount(int $id, string $mountPoint, int $permissions, int $quota, ?ICacheEntry $cacheEntry = null, IStorageFactory $loader = null, bool $acl = false, IUser $user = null): ?IMountPoint { 158 if (!$cacheEntry) { 159 // trigger folder creation 160 $folder = $this->getFolder($id); 161 if ($folder === null) { 162 return null; 163 } 164 $cacheEntry = $this->getRootFolder()->getStorage()->getCache()->get($folder->getId()); 165 } 166 167 $storage = $this->getRootFolder()->getStorage(); 168 169 $rootPath = $this->getJailPath($id); 170 171 // apply acl before jail 172 if ($acl && $user) { 173 $inShare = $this->getCurrentUID() === null || $this->getCurrentUID() !== $user->getUID(); 174 $aclManager = $this->aclManagerFactory->getACLManager($user); 175 $storage = new ACLStorageWrapper([ 176 'storage' => $storage, 177 'acl_manager' => $aclManager, 178 'in_share' => $inShare 179 ]); 180 $aclRootPermissions = $aclManager->getACLPermissionsForPath($rootPath); 181 $cacheEntry['permissions'] &= $aclRootPermissions; 182 } 183 184 $baseStorage = new Jail([ 185 'storage' => $storage, 186 'root' => $rootPath 187 ]); 188 $quotaStorage = new GroupFolderStorage([ 189 'storage' => $baseStorage, 190 'quota' => $quota, 191 'folder_id' => $id, 192 'rootCacheEntry' => $cacheEntry, 193 'userSession' => $this->userSession, 194 'mountOwner' => $user, 195 ]); 196 $maskedStore = new PermissionsMask([ 197 'storage' => $quotaStorage, 198 'mask' => $permissions 199 ]); 200 201 return new GroupMountPoint( 202 $id, 203 $maskedStore, 204 $mountPoint, 205 null, 206 $loader 207 ); 208 } 209 210 public function getJailPath(int $folderId): string { 211 return $this->getRootFolder()->getInternalPath() . '/' . $folderId; 212 } 213 214 private function getRootFolder(): Folder { 215 if (is_null($this->root)) { 216 $rootProvider = $this->rootProvider; 217 $this->root = $rootProvider(); 218 } 219 return $this->root; 220 } 221 222 public function getFolder(int $id, bool $create = true): ?Node { 223 try { 224 return $this->getRootFolder()->get((string)$id); 225 } catch (NotFoundException $e) { 226 if ($create) { 227 return $this->getRootFolder()->newFolder((string)$id); 228 } else { 229 return null; 230 } 231 } 232 } 233 234 /** 235 * @param string[] $mountPoints 236 * @return string[] An array of paths. 237 */ 238 private function findConflictsForUser(IUser $user, array $mountPoints): array { 239 $userHome = $this->mountProviderCollection->getHomeMountForUser($user); 240 241 $pathHashes = array_map('md5', $mountPoints); 242 243 $query = $this->connection->getQueryBuilder(); 244 $query->select('path') 245 ->from('filecache') 246 ->where($query->expr()->eq('storage', $query->createNamedParameter($userHome->getNumericStorageId(), IQueryBuilder::PARAM_INT))) 247 ->andWhere($query->expr()->in('path_hash', $query->createNamedParameter($pathHashes, IQueryBuilder::PARAM_STR_ARRAY))); 248 249 $paths = $query->execute()->fetchAll(\PDO::FETCH_COLUMN); 250 return array_map(function ($path) { 251 return substr($path, 6); // strip leading "files/" 252 }, $paths); 253 } 254} 255