Passed
Push — master ( 23e8ae...e8872f )
by Robin
16:16 queued 12s
created

SetupManager::setupForPath()   A

Complexity

Conditions 5
Paths 3

Size

Total Lines 16
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 10
nc 3
nop 1
dl 0
loc 16
rs 9.6111
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
/**
5
 * @copyright Copyright (c) 2022 Robin Appelman <[email protected]>
6
 *
7
 * @license GNU AGPL version 3 or any later version
8
 *
9
 * This program is free software: you can redistribute it and/or modify
10
 * it under the terms of the GNU Affero General Public License as
11
 * published by the Free Software Foundation, either version 3 of the
12
 * License, or (at your option) any later version.
13
 *
14
 * This program is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
 * GNU Affero General Public License for more details.
18
 *
19
 * You should have received a copy of the GNU Affero General Public License
20
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
21
 *
22
 */
23
24
namespace OC\Files;
25
26
use OC\Files\Config\MountProviderCollection;
27
use OC\Files\Mount\MountPoint;
28
use OC\Files\ObjectStore\HomeObjectStoreStorage;
29
use OC\Files\Storage\Common;
30
use OC\Files\Storage\Home;
31
use OC\Files\Storage\Storage;
32
use OC\Files\Storage\Wrapper\Availability;
33
use OC\Files\Storage\Wrapper\Encoding;
34
use OC\Files\Storage\Wrapper\PermissionsMask;
35
use OC\Files\Storage\Wrapper\Quota;
36
use OC\Lockdown\Filesystem\NullStorage;
37
use OC_App;
38
use OC_Hook;
39
use OC_Util;
40
use OCP\Constants;
41
use OCP\Diagnostics\IEventLogger;
42
use OCP\EventDispatcher\IEventDispatcher;
43
use OCP\Files\Config\IMountProvider;
44
use OCP\Files\Config\IUserMountCache;
45
use OCP\Files\Events\Node\FilesystemTornDownEvent;
46
use OCP\Files\Mount\IMountManager;
47
use OCP\Files\Mount\IMountPoint;
48
use OCP\Files\Storage\IStorage;
49
use OCP\IUser;
50
use OCP\IUserManager;
51
use OCP\Lockdown\ILockdownManager;
52
53
class SetupManager {
54
	private bool $rootSetup = false;
55
	private IEventLogger $eventLogger;
56
	private MountProviderCollection $mountProviderCollection;
57
	private IMountManager $mountManager;
58
	private IUserManager $userManager;
59
	private array $setupUsers = [];
60
	private IEventDispatcher $eventDispatcher;
61
	private IUserMountCache $userMountCache;
62
	private ILockdownManager $lockdownManager;
63
	private bool $listeningForProviders;
64
65
	public function __construct(
66
		IEventLogger $eventLogger,
67
		MountProviderCollection $mountProviderCollection,
68
		IMountManager $mountManager,
69
		IUserManager $userManager,
70
		IEventDispatcher $eventDispatcher,
71
		IUserMountCache $userMountCache,
72
		ILockdownManager $lockdownManager
73
	) {
74
		$this->eventLogger = $eventLogger;
75
		$this->mountProviderCollection = $mountProviderCollection;
76
		$this->mountManager = $mountManager;
77
		$this->userManager = $userManager;
78
		$this->eventDispatcher = $eventDispatcher;
79
		$this->userMountCache = $userMountCache;
80
		$this->lockdownManager = $lockdownManager;
81
		$this->listeningForProviders = false;
82
	}
83
84
	private function setupBuiltinWrappers() {
85
		Filesystem::addStorageWrapper('mount_options', function ($mountPoint, IStorage $storage, IMountPoint $mount) {
86
			if ($storage->instanceOfStorage(Common::class)) {
87
				$storage->setMountOptions($mount->getOptions());
0 ignored issues
show
Bug introduced by
The method setMountOptions() does not exist on OCP\Files\Storage\IStorage. It seems like you code against a sub-type of said class. However, the method does not exist in OCP\Files\Storage\IDisableEncryptionStorage or OCA\Files_Sharing\ISharedStorage or OCP\Files\IHomeStorage or OCP\Files\Storage\IWriteStreamStorage or OCP\Files\Storage or OC\Files\Storage\Storage. Are you sure you never get one of those? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

87
				$storage->/** @scrutinizer ignore-call */ 
88
              setMountOptions($mount->getOptions());
Loading history...
88
			}
89
			return $storage;
90
		});
91
92
		Filesystem::addStorageWrapper('enable_sharing', function ($mountPoint, IStorage $storage, IMountPoint $mount) {
93
			if (!$mount->getOption('enable_sharing', true)) {
94
				return new PermissionsMask([
95
					'storage' => $storage,
96
					'mask' => Constants::PERMISSION_ALL - Constants::PERMISSION_SHARE,
97
				]);
98
			}
99
			return $storage;
100
		});
101
102
		// install storage availability wrapper, before most other wrappers
103
		Filesystem::addStorageWrapper('oc_availability', function ($mountPoint, IStorage $storage) {
104
			if (!$storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage') && !$storage->isLocal()) {
105
				return new Availability(['storage' => $storage]);
106
			}
107
			return $storage;
108
		});
109
110
		Filesystem::addStorageWrapper('oc_encoding', function ($mountPoint, IStorage $storage, IMountPoint $mount) {
111
			if ($mount->getOption('encoding_compatibility', false) && !$storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage') && !$storage->isLocal()) {
112
				return new Encoding(['storage' => $storage]);
113
			}
114
			return $storage;
115
		});
116
117
		Filesystem::addStorageWrapper('oc_quota', function ($mountPoint, $storage) {
118
			// set up quota for home storages, even for other users
119
			// which can happen when using sharing
120
121
			/**
122
			 * @var Storage $storage
123
			 */
124
			if ($storage->instanceOfStorage(HomeObjectStoreStorage::class) || $storage->instanceOfStorage(Home::class)) {
125
				if (is_object($storage->getUser())) {
0 ignored issues
show
Bug introduced by
The method getUser() does not exist on OC\Files\Storage\Storage. It seems like you code against a sub-type of OC\Files\Storage\Storage such as OC\Files\Storage\Wrapper\Wrapper or OCA\Files_External\Lib\Storage\SFTP or OC\Files\Storage\Home or OC\Files\ObjectStore\HomeObjectStoreStorage. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

125
				if (is_object($storage->/** @scrutinizer ignore-call */ getUser())) {
Loading history...
126
					$quota = OC_Util::getUserQuota($storage->getUser());
127
					if ($quota !== \OCP\Files\FileInfo::SPACE_UNLIMITED) {
128
						return new Quota(['storage' => $storage, 'quota' => $quota, 'root' => 'files']);
129
					}
130
				}
131
			}
132
133
			return $storage;
134
		});
135
136
		Filesystem::addStorageWrapper('readonly', function ($mountPoint, IStorage $storage, IMountPoint $mount) {
137
			/*
138
			 * Do not allow any operations that modify the storage
139
			 */
140
			if ($mount->getOption('readonly', false)) {
141
				return new PermissionsMask([
142
					'storage' => $storage,
143
					'mask' => Constants::PERMISSION_ALL & ~(
144
							Constants::PERMISSION_UPDATE |
145
							Constants::PERMISSION_CREATE |
146
							Constants::PERMISSION_DELETE
147
						),
148
				]);
149
			}
150
			return $storage;
151
		});
152
	}
153
154
	/**
155
	 * Setup the full filesystem for the specified user
156
	 */
157
	public function setupForUser(IUser $user): void {
158
		$this->setupRoot();
159
160
		if (in_array($user->getUID(), $this->setupUsers, true)) {
161
			return;
162
		}
163
		$this->setupUsers[] = $user->getUID();
164
165
		$this->eventLogger->start('setup_fs', 'Setup filesystem');
166
167
		$prevLogging = Filesystem::logWarningWhenAddingStorageWrapper(false);
168
169
		OC_Hook::emit('OC_Filesystem', 'preSetup', ['user' => $user->getUID()]);
170
171
		Filesystem::logWarningWhenAddingStorageWrapper($prevLogging);
172
173
		$userDir = '/' . $user->getUID() . '/files';
174
175
		Filesystem::init($user, $userDir);
176
177
		if ($this->lockdownManager->canAccessFilesystem()) {
178
			// home mounts are handled separate since we need to ensure this is mounted before we call the other mount providers
179
			$homeMount = $this->mountProviderCollection->getHomeMountForUser($user);
180
			$this->mountManager->addMount($homeMount);
181
182
			if ($homeMount->getStorageRootId() === -1) {
183
				$homeMount->getStorage()->mkdir('');
184
				$homeMount->getStorage()->getScanner()->scan('');
185
			}
186
187
			// Chance to mount for other storages
188
			$mounts = $this->mountProviderCollection->addMountForUser($user, $this->mountManager);
189
			$mounts[] = $homeMount;
190
			$this->userMountCache->registerMounts($user, $mounts);
191
192
			$this->listenForNewMountProviders();
193
		} else {
194
			$this->mountManager->addMount(new MountPoint(
195
				new NullStorage([]),
196
				'/' . $user->getUID()
197
			));
198
			$this->mountManager->addMount(new MountPoint(
199
				new NullStorage([]),
200
				'/' . $user->getUID() . '/files'
201
			));
202
		}
203
		\OC_Hook::emit('OC_Filesystem', 'post_initMountPoints', ['user' => $user->getUID()]);
204
205
		OC_Hook::emit('OC_Filesystem', 'setup', ['user' => $user->getUID(), 'user_dir' => $userDir]);
206
207
		$this->eventLogger->end('setup_fs');
208
	}
209
210
	/**
211
	 * Set up the root filesystem
212
	 */
213
	public function setupRoot(): void {
214
		//setting up the filesystem twice can only lead to trouble
215
		if ($this->rootSetup) {
216
			return;
217
		}
218
		$this->rootSetup = true;
219
220
		$this->eventLogger->start('setup_root_fs', 'Setup root filesystem');
221
222
		// load all filesystem apps before, so no setup-hook gets lost
223
		OC_App::loadApps(['filesystem']);
224
		$prevLogging = Filesystem::logWarningWhenAddingStorageWrapper(false);
225
226
		$this->setupBuiltinWrappers();
227
228
		Filesystem::logWarningWhenAddingStorageWrapper($prevLogging);
229
230
		$rootMounts = $this->mountProviderCollection->getRootMounts();
231
		foreach ($rootMounts as $rootMountProvider) {
232
			$this->mountManager->addMount($rootMountProvider);
233
		}
234
235
		$this->eventLogger->end('setup_root_fs');
236
	}
237
238
	/**
239
	 * Set up the filesystem for the specified path
240
	 */
241
	public function setupForPath(string $path): void {
242
		if (substr_count($path, '/') < 2 || strpos($path, '/appdata_' . \OC_Util::getInstanceId()) === 0 || strpos($path, '/files_external/') === 0) {
243
			$this->setupRoot();
244
			return;
245
		} else {
246
			[, $userId] = explode('/', $path);
247
		}
248
249
		$user = $this->userManager->get($userId);
250
251
		if (!$user) {
252
			$this->setupRoot();
253
			return;
254
		}
255
256
		$this->setupForUser($user);
257
	}
258
259
	public function tearDown() {
260
		$this->setupUsers = [];
261
		$this->rootSetup = false;
262
		$this->mountManager->clear();
263
		$this->eventDispatcher->dispatchTyped(new FilesystemTornDownEvent());
264
	}
265
266
	/**
267
	 * Get mounts from mount providers that are registered after setup
268
	 */
269
	private function listenForNewMountProviders() {
270
		if (!$this->listeningForProviders) {
271
			$this->listeningForProviders = true;
272
			$this->mountProviderCollection->listen('\OC\Files\Config', 'registerMountProvider', function (IMountProvider $provider) {
273
				foreach ($this->setupUsers as $userId) {
274
					$user = $this->userManager->get($userId);
275
					if ($user) {
276
						$mounts = $provider->getMountsForUser($user, Filesystem::getLoader());
277
						array_walk($mounts, [$this->mountManager, 'addMount']);
278
					}
279
				}
280
			});
281
		}
282
	}
283
}
284