Passed
Push — master ( 48bb54...fe33e9 )
by John
13:16 queued 11s
created
lib/private/Cache/CappedMemoryCache.php 1 patch
Indentation   +89 added lines, -89 removed lines patch added patch discarded remove patch
@@ -30,93 +30,93 @@
 block discarded – undo
30 30
  * @template T
31 31
  */
32 32
 class CappedMemoryCache implements ICache, \ArrayAccess {
33
-	private $capacity;
34
-	/** @var T[] */
35
-	private $cache = [];
36
-
37
-	public function __construct($capacity = 512) {
38
-		$this->capacity = $capacity;
39
-	}
40
-
41
-	public function hasKey($key): bool {
42
-		return isset($this->cache[$key]);
43
-	}
44
-
45
-	/**
46
-	 * @return ?T
47
-	 */
48
-	public function get($key) {
49
-		return $this->cache[$key] ?? null;
50
-	}
51
-
52
-	/**
53
-	 * @param string $key
54
-	 * @param T $value
55
-	 * @param int $ttl
56
-	 * @return bool
57
-	 */
58
-	public function set($key, $value, $ttl = 0): bool {
59
-		if (is_null($key)) {
60
-			$this->cache[] = $value;
61
-		} else {
62
-			$this->cache[$key] = $value;
63
-		}
64
-		$this->garbageCollect();
65
-		return true;
66
-	}
67
-
68
-	public function remove($key) {
69
-		unset($this->cache[$key]);
70
-		return true;
71
-	}
72
-
73
-	public function clear($prefix = '') {
74
-		$this->cache = [];
75
-		return true;
76
-	}
77
-
78
-	public function offsetExists($offset): bool {
79
-		return $this->hasKey($offset);
80
-	}
81
-
82
-	/**
83
-	 * @return T
84
-	 */
85
-	#[\ReturnTypeWillChange]
86
-	public function &offsetGet($offset) {
87
-		return $this->cache[$offset];
88
-	}
89
-
90
-	/**
91
-	 * @param string $offset
92
-	 * @param T $value
93
-	 * @return void
94
-	 */
95
-	public function offsetSet($offset, $value): void {
96
-		$this->set($offset, $value);
97
-	}
98
-
99
-	public function offsetUnset($offset): void {
100
-		$this->remove($offset);
101
-	}
102
-
103
-	/**
104
-	 * @return T[]
105
-	 */
106
-	public function getData() {
107
-		return $this->cache;
108
-	}
109
-
110
-
111
-	private function garbageCollect() {
112
-		while (count($this->cache) > $this->capacity) {
113
-			reset($this->cache);
114
-			$key = key($this->cache);
115
-			$this->remove($key);
116
-		}
117
-	}
118
-
119
-	public static function isAvailable(): bool {
120
-		return true;
121
-	}
33
+    private $capacity;
34
+    /** @var T[] */
35
+    private $cache = [];
36
+
37
+    public function __construct($capacity = 512) {
38
+        $this->capacity = $capacity;
39
+    }
40
+
41
+    public function hasKey($key): bool {
42
+        return isset($this->cache[$key]);
43
+    }
44
+
45
+    /**
46
+     * @return ?T
47
+     */
48
+    public function get($key) {
49
+        return $this->cache[$key] ?? null;
50
+    }
51
+
52
+    /**
53
+     * @param string $key
54
+     * @param T $value
55
+     * @param int $ttl
56
+     * @return bool
57
+     */
58
+    public function set($key, $value, $ttl = 0): bool {
59
+        if (is_null($key)) {
60
+            $this->cache[] = $value;
61
+        } else {
62
+            $this->cache[$key] = $value;
63
+        }
64
+        $this->garbageCollect();
65
+        return true;
66
+    }
67
+
68
+    public function remove($key) {
69
+        unset($this->cache[$key]);
70
+        return true;
71
+    }
72
+
73
+    public function clear($prefix = '') {
74
+        $this->cache = [];
75
+        return true;
76
+    }
77
+
78
+    public function offsetExists($offset): bool {
79
+        return $this->hasKey($offset);
80
+    }
81
+
82
+    /**
83
+     * @return T
84
+     */
85
+    #[\ReturnTypeWillChange]
86
+    public function &offsetGet($offset) {
87
+        return $this->cache[$offset];
88
+    }
89
+
90
+    /**
91
+     * @param string $offset
92
+     * @param T $value
93
+     * @return void
94
+     */
95
+    public function offsetSet($offset, $value): void {
96
+        $this->set($offset, $value);
97
+    }
98
+
99
+    public function offsetUnset($offset): void {
100
+        $this->remove($offset);
101
+    }
102
+
103
+    /**
104
+     * @return T[]
105
+     */
106
+    public function getData() {
107
+        return $this->cache;
108
+    }
109
+
110
+
111
+    private function garbageCollect() {
112
+        while (count($this->cache) > $this->capacity) {
113
+            reset($this->cache);
114
+            $key = key($this->cache);
115
+            $this->remove($key);
116
+        }
117
+    }
118
+
119
+    public static function isAvailable(): bool {
120
+        return true;
121
+    }
122 122
 }
Please login to merge, or discard this patch.
lib/private/Files/Config/UserMountCache.php 1 patch
Indentation   +421 added lines, -421 removed lines patch added patch discarded remove patch
@@ -45,425 +45,425 @@
 block discarded – undo
45 45
  * Cache mounts points per user in the cache so we can easilly look them up
46 46
  */
47 47
 class UserMountCache implements IUserMountCache {
48
-	private IDBConnection $connection;
49
-	private IUserManager $userManager;
50
-
51
-	/**
52
-	 * Cached mount info.
53
-	 * @var CappedMemoryCache<ICachedMountInfo[]>
54
-	 **/
55
-	private CappedMemoryCache $mountsForUsers;
56
-	private LoggerInterface $logger;
57
-	/** @var CappedMemoryCache<array> */
58
-	private CappedMemoryCache $cacheInfoCache;
59
-
60
-	/**
61
-	 * UserMountCache constructor.
62
-	 */
63
-	public function __construct(IDBConnection $connection, IUserManager $userManager, LoggerInterface $logger) {
64
-		$this->connection = $connection;
65
-		$this->userManager = $userManager;
66
-		$this->logger = $logger;
67
-		$this->cacheInfoCache = new CappedMemoryCache();
68
-		$this->mountsForUsers = new CappedMemoryCache();
69
-	}
70
-
71
-	public function registerMounts(IUser $user, array $mounts, array $mountProviderClasses = null) {
72
-		// filter out non-proper storages coming from unit tests
73
-		$mounts = array_filter($mounts, function (IMountPoint $mount) {
74
-			return $mount instanceof SharedMount || ($mount->getStorage() && $mount->getStorage()->getCache());
75
-		});
76
-		/** @var ICachedMountInfo[] $newMounts */
77
-		$newMounts = array_map(function (IMountPoint $mount) use ($user) {
78
-			// filter out any storages which aren't scanned yet since we aren't interested in files from those storages (yet)
79
-			if ($mount->getStorageRootId() === -1) {
80
-				return null;
81
-			} else {
82
-				return new LazyStorageMountInfo($user, $mount);
83
-			}
84
-		}, $mounts);
85
-		$newMounts = array_values(array_filter($newMounts));
86
-		$newMountRootIds = array_map(function (ICachedMountInfo $mount) {
87
-			return $mount->getRootId();
88
-		}, $newMounts);
89
-		$newMounts = array_combine($newMountRootIds, $newMounts);
90
-
91
-		$cachedMounts = $this->getMountsForUser($user);
92
-		if (is_array($mountProviderClasses)) {
93
-			$cachedMounts = array_filter($cachedMounts, function (ICachedMountInfo $mountInfo) use ($mountProviderClasses) {
94
-				return in_array($mountInfo->getMountProvider(), $mountProviderClasses);
95
-			});
96
-		}
97
-		$cachedMountRootIds = array_map(function (ICachedMountInfo $mount) {
98
-			return $mount->getRootId();
99
-		}, $cachedMounts);
100
-		$cachedMounts = array_combine($cachedMountRootIds, $cachedMounts);
101
-
102
-		$addedMounts = [];
103
-		$removedMounts = [];
104
-
105
-		foreach ($newMounts as $rootId => $newMount) {
106
-			if (!isset($cachedMounts[$rootId])) {
107
-				$addedMounts[] = $newMount;
108
-			}
109
-		}
110
-
111
-		foreach ($cachedMounts as $rootId => $cachedMount) {
112
-			if (!isset($newMounts[$rootId])) {
113
-				$removedMounts[] = $cachedMount;
114
-			}
115
-		}
116
-
117
-		$changedMounts = $this->findChangedMounts($newMounts, $cachedMounts);
118
-
119
-		foreach ($addedMounts as $mount) {
120
-			$this->addToCache($mount);
121
-			/** @psalm-suppress InvalidArgument */
122
-			$this->mountsForUsers[$user->getUID()][] = $mount;
123
-		}
124
-		foreach ($removedMounts as $mount) {
125
-			$this->removeFromCache($mount);
126
-			$index = array_search($mount, $this->mountsForUsers[$user->getUID()]);
127
-			unset($this->mountsForUsers[$user->getUID()][$index]);
128
-		}
129
-		foreach ($changedMounts as $mount) {
130
-			$this->updateCachedMount($mount);
131
-		}
132
-	}
133
-
134
-	/**
135
-	 * @param ICachedMountInfo[] $newMounts
136
-	 * @param ICachedMountInfo[] $cachedMounts
137
-	 * @return ICachedMountInfo[]
138
-	 */
139
-	private function findChangedMounts(array $newMounts, array $cachedMounts) {
140
-		$new = [];
141
-		foreach ($newMounts as $mount) {
142
-			$new[$mount->getRootId()] = $mount;
143
-		}
144
-		$changed = [];
145
-		foreach ($cachedMounts as $cachedMount) {
146
-			$rootId = $cachedMount->getRootId();
147
-			if (isset($new[$rootId])) {
148
-				$newMount = $new[$rootId];
149
-				if (
150
-					$newMount->getMountPoint() !== $cachedMount->getMountPoint() ||
151
-					$newMount->getStorageId() !== $cachedMount->getStorageId() ||
152
-					$newMount->getMountId() !== $cachedMount->getMountId() ||
153
-					$newMount->getMountProvider() !== $cachedMount->getMountProvider()
154
-				) {
155
-					$changed[] = $newMount;
156
-				}
157
-			}
158
-		}
159
-		return $changed;
160
-	}
161
-
162
-	private function addToCache(ICachedMountInfo $mount) {
163
-		if ($mount->getStorageId() !== -1) {
164
-			$this->connection->insertIfNotExist('*PREFIX*mounts', [
165
-				'storage_id' => $mount->getStorageId(),
166
-				'root_id' => $mount->getRootId(),
167
-				'user_id' => $mount->getUser()->getUID(),
168
-				'mount_point' => $mount->getMountPoint(),
169
-				'mount_id' => $mount->getMountId(),
170
-				'mount_provider_class' => $mount->getMountProvider(),
171
-			], ['root_id', 'user_id']);
172
-		} else {
173
-			// in some cases this is legitimate, like orphaned shares
174
-			$this->logger->debug('Could not get storage info for mount at ' . $mount->getMountPoint());
175
-		}
176
-	}
177
-
178
-	private function updateCachedMount(ICachedMountInfo $mount) {
179
-		$builder = $this->connection->getQueryBuilder();
180
-
181
-		$query = $builder->update('mounts')
182
-			->set('storage_id', $builder->createNamedParameter($mount->getStorageId()))
183
-			->set('mount_point', $builder->createNamedParameter($mount->getMountPoint()))
184
-			->set('mount_id', $builder->createNamedParameter($mount->getMountId(), IQueryBuilder::PARAM_INT))
185
-			->set('mount_provider_class', $builder->createNamedParameter($mount->getMountProvider()))
186
-			->where($builder->expr()->eq('user_id', $builder->createNamedParameter($mount->getUser()->getUID())))
187
-			->andWhere($builder->expr()->eq('root_id', $builder->createNamedParameter($mount->getRootId(), IQueryBuilder::PARAM_INT)));
188
-
189
-		$query->execute();
190
-	}
191
-
192
-	private function removeFromCache(ICachedMountInfo $mount) {
193
-		$builder = $this->connection->getQueryBuilder();
194
-
195
-		$query = $builder->delete('mounts')
196
-			->where($builder->expr()->eq('user_id', $builder->createNamedParameter($mount->getUser()->getUID())))
197
-			->andWhere($builder->expr()->eq('root_id', $builder->createNamedParameter($mount->getRootId(), IQueryBuilder::PARAM_INT)));
198
-		$query->execute();
199
-	}
200
-
201
-	private function dbRowToMountInfo(array $row) {
202
-		$user = $this->userManager->get($row['user_id']);
203
-		if (is_null($user)) {
204
-			return null;
205
-		}
206
-		$mount_id = $row['mount_id'];
207
-		if (!is_null($mount_id)) {
208
-			$mount_id = (int)$mount_id;
209
-		}
210
-		return new CachedMountInfo(
211
-			$user,
212
-			(int)$row['storage_id'],
213
-			(int)$row['root_id'],
214
-			$row['mount_point'],
215
-			$row['mount_provider_class'] ?? '',
216
-			$mount_id,
217
-			isset($row['path']) ? $row['path'] : '',
218
-		);
219
-	}
220
-
221
-	/**
222
-	 * @param IUser $user
223
-	 * @return ICachedMountInfo[]
224
-	 */
225
-	public function getMountsForUser(IUser $user) {
226
-		if (!isset($this->mountsForUsers[$user->getUID()])) {
227
-			$builder = $this->connection->getQueryBuilder();
228
-			$query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'f.path', 'mount_provider_class')
229
-				->from('mounts', 'm')
230
-				->innerJoin('m', 'filecache', 'f', $builder->expr()->eq('m.root_id', 'f.fileid'))
231
-				->where($builder->expr()->eq('user_id', $builder->createPositionalParameter($user->getUID())));
232
-
233
-			$result = $query->execute();
234
-			$rows = $result->fetchAll();
235
-			$result->closeCursor();
236
-
237
-			$this->mountsForUsers[$user->getUID()] = array_filter(array_map([$this, 'dbRowToMountInfo'], $rows));
238
-		}
239
-		return $this->mountsForUsers[$user->getUID()];
240
-	}
241
-
242
-	/**
243
-	 * @param int $numericStorageId
244
-	 * @param string|null $user limit the results to a single user
245
-	 * @return CachedMountInfo[]
246
-	 */
247
-	public function getMountsForStorageId($numericStorageId, $user = null) {
248
-		$builder = $this->connection->getQueryBuilder();
249
-		$query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'f.path', 'mount_provider_class')
250
-			->from('mounts', 'm')
251
-			->innerJoin('m', 'filecache', 'f', $builder->expr()->eq('m.root_id', 'f.fileid'))
252
-			->where($builder->expr()->eq('storage_id', $builder->createPositionalParameter($numericStorageId, IQueryBuilder::PARAM_INT)));
253
-
254
-		if ($user) {
255
-			$query->andWhere($builder->expr()->eq('user_id', $builder->createPositionalParameter($user)));
256
-		}
257
-
258
-		$result = $query->execute();
259
-		$rows = $result->fetchAll();
260
-		$result->closeCursor();
261
-
262
-		return array_filter(array_map([$this, 'dbRowToMountInfo'], $rows));
263
-	}
264
-
265
-	/**
266
-	 * @param int $rootFileId
267
-	 * @return CachedMountInfo[]
268
-	 */
269
-	public function getMountsForRootId($rootFileId) {
270
-		$builder = $this->connection->getQueryBuilder();
271
-		$query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'f.path', 'mount_provider_class')
272
-			->from('mounts', 'm')
273
-			->innerJoin('m', 'filecache', 'f', $builder->expr()->eq('m.root_id', 'f.fileid'))
274
-			->where($builder->expr()->eq('root_id', $builder->createPositionalParameter($rootFileId, IQueryBuilder::PARAM_INT)));
275
-
276
-		$result = $query->execute();
277
-		$rows = $result->fetchAll();
278
-		$result->closeCursor();
279
-
280
-		return array_filter(array_map([$this, 'dbRowToMountInfo'], $rows));
281
-	}
282
-
283
-	/**
284
-	 * @param $fileId
285
-	 * @return array{int, string, int}
286
-	 * @throws \OCP\Files\NotFoundException
287
-	 */
288
-	private function getCacheInfoFromFileId($fileId): array {
289
-		if (!isset($this->cacheInfoCache[$fileId])) {
290
-			$builder = $this->connection->getQueryBuilder();
291
-			$query = $builder->select('storage', 'path', 'mimetype')
292
-				->from('filecache')
293
-				->where($builder->expr()->eq('fileid', $builder->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)));
294
-
295
-			$result = $query->execute();
296
-			$row = $result->fetch();
297
-			$result->closeCursor();
298
-
299
-			if (is_array($row)) {
300
-				$this->cacheInfoCache[$fileId] = [
301
-					(int)$row['storage'],
302
-					(string)$row['path'],
303
-					(int)$row['mimetype']
304
-				];
305
-			} else {
306
-				throw new NotFoundException('File with id "' . $fileId . '" not found');
307
-			}
308
-		}
309
-		return $this->cacheInfoCache[$fileId];
310
-	}
311
-
312
-	/**
313
-	 * @param int $fileId
314
-	 * @param string|null $user optionally restrict the results to a single user
315
-	 * @return ICachedMountFileInfo[]
316
-	 * @since 9.0.0
317
-	 */
318
-	public function getMountsForFileId($fileId, $user = null) {
319
-		try {
320
-			[$storageId, $internalPath] = $this->getCacheInfoFromFileId($fileId);
321
-		} catch (NotFoundException $e) {
322
-			return [];
323
-		}
324
-		$builder = $this->connection->getQueryBuilder();
325
-		$query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'f.path', 'mount_provider_class')
326
-			->from('mounts', 'm')
327
-			->innerJoin('m', 'filecache', 'f', $builder->expr()->eq('m.root_id', 'f.fileid'))
328
-			->where($builder->expr()->eq('storage_id', $builder->createPositionalParameter($storageId, IQueryBuilder::PARAM_INT)));
329
-
330
-		if ($user) {
331
-			$query->andWhere($builder->expr()->eq('user_id', $builder->createPositionalParameter($user)));
332
-		}
333
-
334
-		$result = $query->execute();
335
-		$rows = $result->fetchAll();
336
-		$result->closeCursor();
337
-		// filter mounts that are from the same storage but a different directory
338
-		$filteredMounts = array_filter($rows, function (array $row) use ($internalPath, $fileId) {
339
-			if ($fileId === (int)$row['root_id']) {
340
-				return true;
341
-			}
342
-			$internalMountPath = $row['path'] ?? '';
343
-
344
-			return $internalMountPath === '' || substr($internalPath, 0, strlen($internalMountPath) + 1) === $internalMountPath . '/';
345
-		});
346
-
347
-		$filteredMounts = array_filter(array_map([$this, 'dbRowToMountInfo'], $filteredMounts));
348
-		return array_map(function (ICachedMountInfo $mount) use ($internalPath) {
349
-			return new CachedMountFileInfo(
350
-				$mount->getUser(),
351
-				$mount->getStorageId(),
352
-				$mount->getRootId(),
353
-				$mount->getMountPoint(),
354
-				$mount->getMountId(),
355
-				$mount->getMountProvider(),
356
-				$mount->getRootInternalPath(),
357
-				$internalPath
358
-			);
359
-		}, $filteredMounts);
360
-	}
361
-
362
-	/**
363
-	 * Remove all cached mounts for a user
364
-	 *
365
-	 * @param IUser $user
366
-	 */
367
-	public function removeUserMounts(IUser $user) {
368
-		$builder = $this->connection->getQueryBuilder();
369
-
370
-		$query = $builder->delete('mounts')
371
-			->where($builder->expr()->eq('user_id', $builder->createNamedParameter($user->getUID())));
372
-		$query->execute();
373
-	}
374
-
375
-	public function removeUserStorageMount($storageId, $userId) {
376
-		$builder = $this->connection->getQueryBuilder();
377
-
378
-		$query = $builder->delete('mounts')
379
-			->where($builder->expr()->eq('user_id', $builder->createNamedParameter($userId)))
380
-			->andWhere($builder->expr()->eq('storage_id', $builder->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)));
381
-		$query->execute();
382
-	}
383
-
384
-	public function remoteStorageMounts($storageId) {
385
-		$builder = $this->connection->getQueryBuilder();
386
-
387
-		$query = $builder->delete('mounts')
388
-			->where($builder->expr()->eq('storage_id', $builder->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)));
389
-		$query->execute();
390
-	}
391
-
392
-	/**
393
-	 * @param array $users
394
-	 * @return array
395
-	 */
396
-	public function getUsedSpaceForUsers(array $users) {
397
-		$builder = $this->connection->getQueryBuilder();
398
-
399
-		$slash = $builder->createNamedParameter('/');
400
-
401
-		$mountPoint = $builder->func()->concat(
402
-			$builder->func()->concat($slash, 'user_id'),
403
-			$slash
404
-		);
405
-
406
-		$userIds = array_map(function (IUser $user) {
407
-			return $user->getUID();
408
-		}, $users);
409
-
410
-		$query = $builder->select('m.user_id', 'f.size')
411
-			->from('mounts', 'm')
412
-			->innerJoin('m', 'filecache', 'f',
413
-				$builder->expr()->andX(
414
-					$builder->expr()->eq('m.storage_id', 'f.storage'),
415
-					$builder->expr()->eq('f.path_hash', $builder->createNamedParameter(md5('files')))
416
-				))
417
-			->where($builder->expr()->eq('m.mount_point', $mountPoint))
418
-			->andWhere($builder->expr()->in('m.user_id', $builder->createNamedParameter($userIds, IQueryBuilder::PARAM_STR_ARRAY)));
419
-
420
-		$result = $query->execute();
421
-
422
-		$results = [];
423
-		while ($row = $result->fetch()) {
424
-			$results[$row['user_id']] = $row['size'];
425
-		}
426
-		$result->closeCursor();
427
-		return $results;
428
-	}
429
-
430
-	public function clear(): void {
431
-		$this->cacheInfoCache = new CappedMemoryCache();
432
-		$this->mountsForUsers = new CappedMemoryCache();
433
-	}
434
-
435
-	public function getMountForPath(IUser $user, string $path): ICachedMountInfo {
436
-		$mounts = $this->getMountsForUser($user);
437
-		$mountPoints = array_map(function (ICachedMountInfo $mount) {
438
-			return $mount->getMountPoint();
439
-		}, $mounts);
440
-		$mounts = array_combine($mountPoints, $mounts);
441
-
442
-		$current = $path;
443
-		// walk up the directory tree until we find a path that has a mountpoint set
444
-		// the loop will return if a mountpoint is found or break if none are found
445
-		while (true) {
446
-			$mountPoint = $current . '/';
447
-			if (isset($mounts[$mountPoint])) {
448
-				return $mounts[$mountPoint];
449
-			} elseif ($current === '') {
450
-				break;
451
-			}
452
-
453
-			$current = dirname($current);
454
-			if ($current === '.' || $current === '/') {
455
-				$current = '';
456
-			}
457
-		}
458
-
459
-		throw new NotFoundException("No cached mount for path " . $path);
460
-	}
461
-
462
-	public function getMountsInPath(IUser $user, string $path): array {
463
-		$path = rtrim($path, '/') . '/';
464
-		$mounts = $this->getMountsForUser($user);
465
-		return array_filter($mounts, function (ICachedMountInfo $mount) use ($path) {
466
-			return $mount->getMountPoint() !== $path && strpos($mount->getMountPoint(), $path) === 0;
467
-		});
468
-	}
48
+    private IDBConnection $connection;
49
+    private IUserManager $userManager;
50
+
51
+    /**
52
+     * Cached mount info.
53
+     * @var CappedMemoryCache<ICachedMountInfo[]>
54
+     **/
55
+    private CappedMemoryCache $mountsForUsers;
56
+    private LoggerInterface $logger;
57
+    /** @var CappedMemoryCache<array> */
58
+    private CappedMemoryCache $cacheInfoCache;
59
+
60
+    /**
61
+     * UserMountCache constructor.
62
+     */
63
+    public function __construct(IDBConnection $connection, IUserManager $userManager, LoggerInterface $logger) {
64
+        $this->connection = $connection;
65
+        $this->userManager = $userManager;
66
+        $this->logger = $logger;
67
+        $this->cacheInfoCache = new CappedMemoryCache();
68
+        $this->mountsForUsers = new CappedMemoryCache();
69
+    }
70
+
71
+    public function registerMounts(IUser $user, array $mounts, array $mountProviderClasses = null) {
72
+        // filter out non-proper storages coming from unit tests
73
+        $mounts = array_filter($mounts, function (IMountPoint $mount) {
74
+            return $mount instanceof SharedMount || ($mount->getStorage() && $mount->getStorage()->getCache());
75
+        });
76
+        /** @var ICachedMountInfo[] $newMounts */
77
+        $newMounts = array_map(function (IMountPoint $mount) use ($user) {
78
+            // filter out any storages which aren't scanned yet since we aren't interested in files from those storages (yet)
79
+            if ($mount->getStorageRootId() === -1) {
80
+                return null;
81
+            } else {
82
+                return new LazyStorageMountInfo($user, $mount);
83
+            }
84
+        }, $mounts);
85
+        $newMounts = array_values(array_filter($newMounts));
86
+        $newMountRootIds = array_map(function (ICachedMountInfo $mount) {
87
+            return $mount->getRootId();
88
+        }, $newMounts);
89
+        $newMounts = array_combine($newMountRootIds, $newMounts);
90
+
91
+        $cachedMounts = $this->getMountsForUser($user);
92
+        if (is_array($mountProviderClasses)) {
93
+            $cachedMounts = array_filter($cachedMounts, function (ICachedMountInfo $mountInfo) use ($mountProviderClasses) {
94
+                return in_array($mountInfo->getMountProvider(), $mountProviderClasses);
95
+            });
96
+        }
97
+        $cachedMountRootIds = array_map(function (ICachedMountInfo $mount) {
98
+            return $mount->getRootId();
99
+        }, $cachedMounts);
100
+        $cachedMounts = array_combine($cachedMountRootIds, $cachedMounts);
101
+
102
+        $addedMounts = [];
103
+        $removedMounts = [];
104
+
105
+        foreach ($newMounts as $rootId => $newMount) {
106
+            if (!isset($cachedMounts[$rootId])) {
107
+                $addedMounts[] = $newMount;
108
+            }
109
+        }
110
+
111
+        foreach ($cachedMounts as $rootId => $cachedMount) {
112
+            if (!isset($newMounts[$rootId])) {
113
+                $removedMounts[] = $cachedMount;
114
+            }
115
+        }
116
+
117
+        $changedMounts = $this->findChangedMounts($newMounts, $cachedMounts);
118
+
119
+        foreach ($addedMounts as $mount) {
120
+            $this->addToCache($mount);
121
+            /** @psalm-suppress InvalidArgument */
122
+            $this->mountsForUsers[$user->getUID()][] = $mount;
123
+        }
124
+        foreach ($removedMounts as $mount) {
125
+            $this->removeFromCache($mount);
126
+            $index = array_search($mount, $this->mountsForUsers[$user->getUID()]);
127
+            unset($this->mountsForUsers[$user->getUID()][$index]);
128
+        }
129
+        foreach ($changedMounts as $mount) {
130
+            $this->updateCachedMount($mount);
131
+        }
132
+    }
133
+
134
+    /**
135
+     * @param ICachedMountInfo[] $newMounts
136
+     * @param ICachedMountInfo[] $cachedMounts
137
+     * @return ICachedMountInfo[]
138
+     */
139
+    private function findChangedMounts(array $newMounts, array $cachedMounts) {
140
+        $new = [];
141
+        foreach ($newMounts as $mount) {
142
+            $new[$mount->getRootId()] = $mount;
143
+        }
144
+        $changed = [];
145
+        foreach ($cachedMounts as $cachedMount) {
146
+            $rootId = $cachedMount->getRootId();
147
+            if (isset($new[$rootId])) {
148
+                $newMount = $new[$rootId];
149
+                if (
150
+                    $newMount->getMountPoint() !== $cachedMount->getMountPoint() ||
151
+                    $newMount->getStorageId() !== $cachedMount->getStorageId() ||
152
+                    $newMount->getMountId() !== $cachedMount->getMountId() ||
153
+                    $newMount->getMountProvider() !== $cachedMount->getMountProvider()
154
+                ) {
155
+                    $changed[] = $newMount;
156
+                }
157
+            }
158
+        }
159
+        return $changed;
160
+    }
161
+
162
+    private function addToCache(ICachedMountInfo $mount) {
163
+        if ($mount->getStorageId() !== -1) {
164
+            $this->connection->insertIfNotExist('*PREFIX*mounts', [
165
+                'storage_id' => $mount->getStorageId(),
166
+                'root_id' => $mount->getRootId(),
167
+                'user_id' => $mount->getUser()->getUID(),
168
+                'mount_point' => $mount->getMountPoint(),
169
+                'mount_id' => $mount->getMountId(),
170
+                'mount_provider_class' => $mount->getMountProvider(),
171
+            ], ['root_id', 'user_id']);
172
+        } else {
173
+            // in some cases this is legitimate, like orphaned shares
174
+            $this->logger->debug('Could not get storage info for mount at ' . $mount->getMountPoint());
175
+        }
176
+    }
177
+
178
+    private function updateCachedMount(ICachedMountInfo $mount) {
179
+        $builder = $this->connection->getQueryBuilder();
180
+
181
+        $query = $builder->update('mounts')
182
+            ->set('storage_id', $builder->createNamedParameter($mount->getStorageId()))
183
+            ->set('mount_point', $builder->createNamedParameter($mount->getMountPoint()))
184
+            ->set('mount_id', $builder->createNamedParameter($mount->getMountId(), IQueryBuilder::PARAM_INT))
185
+            ->set('mount_provider_class', $builder->createNamedParameter($mount->getMountProvider()))
186
+            ->where($builder->expr()->eq('user_id', $builder->createNamedParameter($mount->getUser()->getUID())))
187
+            ->andWhere($builder->expr()->eq('root_id', $builder->createNamedParameter($mount->getRootId(), IQueryBuilder::PARAM_INT)));
188
+
189
+        $query->execute();
190
+    }
191
+
192
+    private function removeFromCache(ICachedMountInfo $mount) {
193
+        $builder = $this->connection->getQueryBuilder();
194
+
195
+        $query = $builder->delete('mounts')
196
+            ->where($builder->expr()->eq('user_id', $builder->createNamedParameter($mount->getUser()->getUID())))
197
+            ->andWhere($builder->expr()->eq('root_id', $builder->createNamedParameter($mount->getRootId(), IQueryBuilder::PARAM_INT)));
198
+        $query->execute();
199
+    }
200
+
201
+    private function dbRowToMountInfo(array $row) {
202
+        $user = $this->userManager->get($row['user_id']);
203
+        if (is_null($user)) {
204
+            return null;
205
+        }
206
+        $mount_id = $row['mount_id'];
207
+        if (!is_null($mount_id)) {
208
+            $mount_id = (int)$mount_id;
209
+        }
210
+        return new CachedMountInfo(
211
+            $user,
212
+            (int)$row['storage_id'],
213
+            (int)$row['root_id'],
214
+            $row['mount_point'],
215
+            $row['mount_provider_class'] ?? '',
216
+            $mount_id,
217
+            isset($row['path']) ? $row['path'] : '',
218
+        );
219
+    }
220
+
221
+    /**
222
+     * @param IUser $user
223
+     * @return ICachedMountInfo[]
224
+     */
225
+    public function getMountsForUser(IUser $user) {
226
+        if (!isset($this->mountsForUsers[$user->getUID()])) {
227
+            $builder = $this->connection->getQueryBuilder();
228
+            $query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'f.path', 'mount_provider_class')
229
+                ->from('mounts', 'm')
230
+                ->innerJoin('m', 'filecache', 'f', $builder->expr()->eq('m.root_id', 'f.fileid'))
231
+                ->where($builder->expr()->eq('user_id', $builder->createPositionalParameter($user->getUID())));
232
+
233
+            $result = $query->execute();
234
+            $rows = $result->fetchAll();
235
+            $result->closeCursor();
236
+
237
+            $this->mountsForUsers[$user->getUID()] = array_filter(array_map([$this, 'dbRowToMountInfo'], $rows));
238
+        }
239
+        return $this->mountsForUsers[$user->getUID()];
240
+    }
241
+
242
+    /**
243
+     * @param int $numericStorageId
244
+     * @param string|null $user limit the results to a single user
245
+     * @return CachedMountInfo[]
246
+     */
247
+    public function getMountsForStorageId($numericStorageId, $user = null) {
248
+        $builder = $this->connection->getQueryBuilder();
249
+        $query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'f.path', 'mount_provider_class')
250
+            ->from('mounts', 'm')
251
+            ->innerJoin('m', 'filecache', 'f', $builder->expr()->eq('m.root_id', 'f.fileid'))
252
+            ->where($builder->expr()->eq('storage_id', $builder->createPositionalParameter($numericStorageId, IQueryBuilder::PARAM_INT)));
253
+
254
+        if ($user) {
255
+            $query->andWhere($builder->expr()->eq('user_id', $builder->createPositionalParameter($user)));
256
+        }
257
+
258
+        $result = $query->execute();
259
+        $rows = $result->fetchAll();
260
+        $result->closeCursor();
261
+
262
+        return array_filter(array_map([$this, 'dbRowToMountInfo'], $rows));
263
+    }
264
+
265
+    /**
266
+     * @param int $rootFileId
267
+     * @return CachedMountInfo[]
268
+     */
269
+    public function getMountsForRootId($rootFileId) {
270
+        $builder = $this->connection->getQueryBuilder();
271
+        $query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'f.path', 'mount_provider_class')
272
+            ->from('mounts', 'm')
273
+            ->innerJoin('m', 'filecache', 'f', $builder->expr()->eq('m.root_id', 'f.fileid'))
274
+            ->where($builder->expr()->eq('root_id', $builder->createPositionalParameter($rootFileId, IQueryBuilder::PARAM_INT)));
275
+
276
+        $result = $query->execute();
277
+        $rows = $result->fetchAll();
278
+        $result->closeCursor();
279
+
280
+        return array_filter(array_map([$this, 'dbRowToMountInfo'], $rows));
281
+    }
282
+
283
+    /**
284
+     * @param $fileId
285
+     * @return array{int, string, int}
286
+     * @throws \OCP\Files\NotFoundException
287
+     */
288
+    private function getCacheInfoFromFileId($fileId): array {
289
+        if (!isset($this->cacheInfoCache[$fileId])) {
290
+            $builder = $this->connection->getQueryBuilder();
291
+            $query = $builder->select('storage', 'path', 'mimetype')
292
+                ->from('filecache')
293
+                ->where($builder->expr()->eq('fileid', $builder->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)));
294
+
295
+            $result = $query->execute();
296
+            $row = $result->fetch();
297
+            $result->closeCursor();
298
+
299
+            if (is_array($row)) {
300
+                $this->cacheInfoCache[$fileId] = [
301
+                    (int)$row['storage'],
302
+                    (string)$row['path'],
303
+                    (int)$row['mimetype']
304
+                ];
305
+            } else {
306
+                throw new NotFoundException('File with id "' . $fileId . '" not found');
307
+            }
308
+        }
309
+        return $this->cacheInfoCache[$fileId];
310
+    }
311
+
312
+    /**
313
+     * @param int $fileId
314
+     * @param string|null $user optionally restrict the results to a single user
315
+     * @return ICachedMountFileInfo[]
316
+     * @since 9.0.0
317
+     */
318
+    public function getMountsForFileId($fileId, $user = null) {
319
+        try {
320
+            [$storageId, $internalPath] = $this->getCacheInfoFromFileId($fileId);
321
+        } catch (NotFoundException $e) {
322
+            return [];
323
+        }
324
+        $builder = $this->connection->getQueryBuilder();
325
+        $query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'f.path', 'mount_provider_class')
326
+            ->from('mounts', 'm')
327
+            ->innerJoin('m', 'filecache', 'f', $builder->expr()->eq('m.root_id', 'f.fileid'))
328
+            ->where($builder->expr()->eq('storage_id', $builder->createPositionalParameter($storageId, IQueryBuilder::PARAM_INT)));
329
+
330
+        if ($user) {
331
+            $query->andWhere($builder->expr()->eq('user_id', $builder->createPositionalParameter($user)));
332
+        }
333
+
334
+        $result = $query->execute();
335
+        $rows = $result->fetchAll();
336
+        $result->closeCursor();
337
+        // filter mounts that are from the same storage but a different directory
338
+        $filteredMounts = array_filter($rows, function (array $row) use ($internalPath, $fileId) {
339
+            if ($fileId === (int)$row['root_id']) {
340
+                return true;
341
+            }
342
+            $internalMountPath = $row['path'] ?? '';
343
+
344
+            return $internalMountPath === '' || substr($internalPath, 0, strlen($internalMountPath) + 1) === $internalMountPath . '/';
345
+        });
346
+
347
+        $filteredMounts = array_filter(array_map([$this, 'dbRowToMountInfo'], $filteredMounts));
348
+        return array_map(function (ICachedMountInfo $mount) use ($internalPath) {
349
+            return new CachedMountFileInfo(
350
+                $mount->getUser(),
351
+                $mount->getStorageId(),
352
+                $mount->getRootId(),
353
+                $mount->getMountPoint(),
354
+                $mount->getMountId(),
355
+                $mount->getMountProvider(),
356
+                $mount->getRootInternalPath(),
357
+                $internalPath
358
+            );
359
+        }, $filteredMounts);
360
+    }
361
+
362
+    /**
363
+     * Remove all cached mounts for a user
364
+     *
365
+     * @param IUser $user
366
+     */
367
+    public function removeUserMounts(IUser $user) {
368
+        $builder = $this->connection->getQueryBuilder();
369
+
370
+        $query = $builder->delete('mounts')
371
+            ->where($builder->expr()->eq('user_id', $builder->createNamedParameter($user->getUID())));
372
+        $query->execute();
373
+    }
374
+
375
+    public function removeUserStorageMount($storageId, $userId) {
376
+        $builder = $this->connection->getQueryBuilder();
377
+
378
+        $query = $builder->delete('mounts')
379
+            ->where($builder->expr()->eq('user_id', $builder->createNamedParameter($userId)))
380
+            ->andWhere($builder->expr()->eq('storage_id', $builder->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)));
381
+        $query->execute();
382
+    }
383
+
384
+    public function remoteStorageMounts($storageId) {
385
+        $builder = $this->connection->getQueryBuilder();
386
+
387
+        $query = $builder->delete('mounts')
388
+            ->where($builder->expr()->eq('storage_id', $builder->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)));
389
+        $query->execute();
390
+    }
391
+
392
+    /**
393
+     * @param array $users
394
+     * @return array
395
+     */
396
+    public function getUsedSpaceForUsers(array $users) {
397
+        $builder = $this->connection->getQueryBuilder();
398
+
399
+        $slash = $builder->createNamedParameter('/');
400
+
401
+        $mountPoint = $builder->func()->concat(
402
+            $builder->func()->concat($slash, 'user_id'),
403
+            $slash
404
+        );
405
+
406
+        $userIds = array_map(function (IUser $user) {
407
+            return $user->getUID();
408
+        }, $users);
409
+
410
+        $query = $builder->select('m.user_id', 'f.size')
411
+            ->from('mounts', 'm')
412
+            ->innerJoin('m', 'filecache', 'f',
413
+                $builder->expr()->andX(
414
+                    $builder->expr()->eq('m.storage_id', 'f.storage'),
415
+                    $builder->expr()->eq('f.path_hash', $builder->createNamedParameter(md5('files')))
416
+                ))
417
+            ->where($builder->expr()->eq('m.mount_point', $mountPoint))
418
+            ->andWhere($builder->expr()->in('m.user_id', $builder->createNamedParameter($userIds, IQueryBuilder::PARAM_STR_ARRAY)));
419
+
420
+        $result = $query->execute();
421
+
422
+        $results = [];
423
+        while ($row = $result->fetch()) {
424
+            $results[$row['user_id']] = $row['size'];
425
+        }
426
+        $result->closeCursor();
427
+        return $results;
428
+    }
429
+
430
+    public function clear(): void {
431
+        $this->cacheInfoCache = new CappedMemoryCache();
432
+        $this->mountsForUsers = new CappedMemoryCache();
433
+    }
434
+
435
+    public function getMountForPath(IUser $user, string $path): ICachedMountInfo {
436
+        $mounts = $this->getMountsForUser($user);
437
+        $mountPoints = array_map(function (ICachedMountInfo $mount) {
438
+            return $mount->getMountPoint();
439
+        }, $mounts);
440
+        $mounts = array_combine($mountPoints, $mounts);
441
+
442
+        $current = $path;
443
+        // walk up the directory tree until we find a path that has a mountpoint set
444
+        // the loop will return if a mountpoint is found or break if none are found
445
+        while (true) {
446
+            $mountPoint = $current . '/';
447
+            if (isset($mounts[$mountPoint])) {
448
+                return $mounts[$mountPoint];
449
+            } elseif ($current === '') {
450
+                break;
451
+            }
452
+
453
+            $current = dirname($current);
454
+            if ($current === '.' || $current === '/') {
455
+                $current = '';
456
+            }
457
+        }
458
+
459
+        throw new NotFoundException("No cached mount for path " . $path);
460
+    }
461
+
462
+    public function getMountsInPath(IUser $user, string $path): array {
463
+        $path = rtrim($path, '/') . '/';
464
+        $mounts = $this->getMountsForUser($user);
465
+        return array_filter($mounts, function (ICachedMountInfo $mount) use ($path) {
466
+            return $mount->getMountPoint() !== $path && strpos($mount->getMountPoint(), $path) === 0;
467
+        });
468
+    }
469 469
 }
Please login to merge, or discard this patch.
lib/private/Files/AppData/AppData.php 1 patch
Indentation   +131 added lines, -131 removed lines patch added patch discarded remove patch
@@ -38,135 +38,135 @@
 block discarded – undo
38 38
 use OCP\Files\SimpleFS\ISimpleFolder;
39 39
 
40 40
 class AppData implements IAppData {
41
-	private IRootFolder $rootFolder;
42
-	private SystemConfig $config;
43
-	private string $appId;
44
-	private ?Folder $folder = null;
45
-	/** @var CappedMemoryCache<ISimpleFolder|NotFoundException> */
46
-	private CappedMemoryCache $folders;
47
-
48
-	/**
49
-	 * AppData constructor.
50
-	 *
51
-	 * @param IRootFolder $rootFolder
52
-	 * @param SystemConfig $systemConfig
53
-	 * @param string $appId
54
-	 */
55
-	public function __construct(IRootFolder $rootFolder,
56
-								SystemConfig $systemConfig,
57
-								string $appId) {
58
-		$this->rootFolder = $rootFolder;
59
-		$this->config = $systemConfig;
60
-		$this->appId = $appId;
61
-		$this->folders = new CappedMemoryCache();
62
-	}
63
-
64
-	private function getAppDataFolderName() {
65
-		$instanceId = $this->config->getValue('instanceid', null);
66
-		if ($instanceId === null) {
67
-			throw new \RuntimeException('no instance id!');
68
-		}
69
-
70
-		return 'appdata_' . $instanceId;
71
-	}
72
-
73
-	protected function getAppDataRootFolder(): Folder {
74
-		$name = $this->getAppDataFolderName();
75
-
76
-		try {
77
-			/** @var Folder $node */
78
-			$node = $this->rootFolder->get($name);
79
-			return $node;
80
-		} catch (NotFoundException $e) {
81
-			try {
82
-				return $this->rootFolder->newFolder($name);
83
-			} catch (NotPermittedException $e) {
84
-				throw new \RuntimeException('Could not get appdata folder');
85
-			}
86
-		}
87
-	}
88
-
89
-	/**
90
-	 * @return Folder
91
-	 * @throws \RuntimeException
92
-	 */
93
-	private function getAppDataFolder(): Folder {
94
-		if ($this->folder === null) {
95
-			$name = $this->getAppDataFolderName();
96
-
97
-			try {
98
-				$this->folder = $this->rootFolder->get($name . '/' . $this->appId);
99
-			} catch (NotFoundException $e) {
100
-				$appDataRootFolder = $this->getAppDataRootFolder();
101
-
102
-				try {
103
-					$this->folder = $appDataRootFolder->get($this->appId);
104
-				} catch (NotFoundException $e) {
105
-					try {
106
-						$this->folder = $appDataRootFolder->newFolder($this->appId);
107
-					} catch (NotPermittedException $e) {
108
-						throw new \RuntimeException('Could not get appdata folder for ' . $this->appId);
109
-					}
110
-				}
111
-			}
112
-		}
113
-
114
-		return $this->folder;
115
-	}
116
-
117
-	public function getFolder(string $name): ISimpleFolder {
118
-		$key = $this->appId . '/' . $name;
119
-		if ($cachedFolder = $this->folders->get($key)) {
120
-			if ($cachedFolder instanceof \Exception) {
121
-				throw $cachedFolder;
122
-			} else {
123
-				return $cachedFolder;
124
-			}
125
-		}
126
-		try {
127
-			// Hardening if somebody wants to retrieve '/'
128
-			if ($name === '/') {
129
-				$node = $this->getAppDataFolder();
130
-			} else {
131
-				$path = $this->getAppDataFolderName() . '/' . $this->appId . '/' . $name;
132
-				$node = $this->rootFolder->get($path);
133
-			}
134
-		} catch (NotFoundException $e) {
135
-			$this->folders->set($key, $e);
136
-			throw $e;
137
-		}
138
-
139
-		/** @var Folder $node */
140
-		$folder = new SimpleFolder($node);
141
-		$this->folders->set($key, $folder);
142
-		return $folder;
143
-	}
144
-
145
-	public function newFolder(string $name): ISimpleFolder {
146
-		$key = $this->appId . '/' . $name;
147
-		$folder = $this->getAppDataFolder()->newFolder($name);
148
-
149
-		$simpleFolder = new SimpleFolder($folder);
150
-		$this->folders->set($key, $simpleFolder);
151
-		return $simpleFolder;
152
-	}
153
-
154
-	public function getDirectoryListing(): array {
155
-		$listing = $this->getAppDataFolder()->getDirectoryListing();
156
-
157
-		$fileListing = array_map(function (Node $folder) {
158
-			if ($folder instanceof Folder) {
159
-				return new SimpleFolder($folder);
160
-			}
161
-			return null;
162
-		}, $listing);
163
-
164
-		$fileListing = array_filter($fileListing);
165
-
166
-		return array_values($fileListing);
167
-	}
168
-
169
-	public function getId(): int {
170
-		return $this->getAppDataFolder()->getId();
171
-	}
41
+    private IRootFolder $rootFolder;
42
+    private SystemConfig $config;
43
+    private string $appId;
44
+    private ?Folder $folder = null;
45
+    /** @var CappedMemoryCache<ISimpleFolder|NotFoundException> */
46
+    private CappedMemoryCache $folders;
47
+
48
+    /**
49
+     * AppData constructor.
50
+     *
51
+     * @param IRootFolder $rootFolder
52
+     * @param SystemConfig $systemConfig
53
+     * @param string $appId
54
+     */
55
+    public function __construct(IRootFolder $rootFolder,
56
+                                SystemConfig $systemConfig,
57
+                                string $appId) {
58
+        $this->rootFolder = $rootFolder;
59
+        $this->config = $systemConfig;
60
+        $this->appId = $appId;
61
+        $this->folders = new CappedMemoryCache();
62
+    }
63
+
64
+    private function getAppDataFolderName() {
65
+        $instanceId = $this->config->getValue('instanceid', null);
66
+        if ($instanceId === null) {
67
+            throw new \RuntimeException('no instance id!');
68
+        }
69
+
70
+        return 'appdata_' . $instanceId;
71
+    }
72
+
73
+    protected function getAppDataRootFolder(): Folder {
74
+        $name = $this->getAppDataFolderName();
75
+
76
+        try {
77
+            /** @var Folder $node */
78
+            $node = $this->rootFolder->get($name);
79
+            return $node;
80
+        } catch (NotFoundException $e) {
81
+            try {
82
+                return $this->rootFolder->newFolder($name);
83
+            } catch (NotPermittedException $e) {
84
+                throw new \RuntimeException('Could not get appdata folder');
85
+            }
86
+        }
87
+    }
88
+
89
+    /**
90
+     * @return Folder
91
+     * @throws \RuntimeException
92
+     */
93
+    private function getAppDataFolder(): Folder {
94
+        if ($this->folder === null) {
95
+            $name = $this->getAppDataFolderName();
96
+
97
+            try {
98
+                $this->folder = $this->rootFolder->get($name . '/' . $this->appId);
99
+            } catch (NotFoundException $e) {
100
+                $appDataRootFolder = $this->getAppDataRootFolder();
101
+
102
+                try {
103
+                    $this->folder = $appDataRootFolder->get($this->appId);
104
+                } catch (NotFoundException $e) {
105
+                    try {
106
+                        $this->folder = $appDataRootFolder->newFolder($this->appId);
107
+                    } catch (NotPermittedException $e) {
108
+                        throw new \RuntimeException('Could not get appdata folder for ' . $this->appId);
109
+                    }
110
+                }
111
+            }
112
+        }
113
+
114
+        return $this->folder;
115
+    }
116
+
117
+    public function getFolder(string $name): ISimpleFolder {
118
+        $key = $this->appId . '/' . $name;
119
+        if ($cachedFolder = $this->folders->get($key)) {
120
+            if ($cachedFolder instanceof \Exception) {
121
+                throw $cachedFolder;
122
+            } else {
123
+                return $cachedFolder;
124
+            }
125
+        }
126
+        try {
127
+            // Hardening if somebody wants to retrieve '/'
128
+            if ($name === '/') {
129
+                $node = $this->getAppDataFolder();
130
+            } else {
131
+                $path = $this->getAppDataFolderName() . '/' . $this->appId . '/' . $name;
132
+                $node = $this->rootFolder->get($path);
133
+            }
134
+        } catch (NotFoundException $e) {
135
+            $this->folders->set($key, $e);
136
+            throw $e;
137
+        }
138
+
139
+        /** @var Folder $node */
140
+        $folder = new SimpleFolder($node);
141
+        $this->folders->set($key, $folder);
142
+        return $folder;
143
+    }
144
+
145
+    public function newFolder(string $name): ISimpleFolder {
146
+        $key = $this->appId . '/' . $name;
147
+        $folder = $this->getAppDataFolder()->newFolder($name);
148
+
149
+        $simpleFolder = new SimpleFolder($folder);
150
+        $this->folders->set($key, $simpleFolder);
151
+        return $simpleFolder;
152
+    }
153
+
154
+    public function getDirectoryListing(): array {
155
+        $listing = $this->getAppDataFolder()->getDirectoryListing();
156
+
157
+        $fileListing = array_map(function (Node $folder) {
158
+            if ($folder instanceof Folder) {
159
+                return new SimpleFolder($folder);
160
+            }
161
+            return null;
162
+        }, $listing);
163
+
164
+        $fileListing = array_filter($fileListing);
165
+
166
+        return array_values($fileListing);
167
+    }
168
+
169
+    public function getId(): int {
170
+        return $this->getAppDataFolder()->getId();
171
+    }
172 172
 }
Please login to merge, or discard this patch.
lib/private/Diagnostics/QueryLogger.php 2 patches
Indentation   +52 added lines, -52 removed lines patch added patch discarded remove patch
@@ -28,64 +28,64 @@
 block discarded – undo
28 28
 use OCP\Diagnostics\IQueryLogger;
29 29
 
30 30
 class QueryLogger implements IQueryLogger {
31
-	protected int $index = 0;
32
-	protected ?Query $activeQuery = null;
33
-	/** @var CappedMemoryCache<Query> */
34
-	protected CappedMemoryCache $queries;
31
+    protected int $index = 0;
32
+    protected ?Query $activeQuery = null;
33
+    /** @var CappedMemoryCache<Query> */
34
+    protected CappedMemoryCache $queries;
35 35
 
36
-	/**
37
-	 * QueryLogger constructor.
38
-	 */
39
-	public function __construct() {
40
-		$this->queries = new CappedMemoryCache(1024);
41
-	}
36
+    /**
37
+     * QueryLogger constructor.
38
+     */
39
+    public function __construct() {
40
+        $this->queries = new CappedMemoryCache(1024);
41
+    }
42 42
 
43 43
 
44
-	/**
45
-	 * @var bool - Module needs to be activated by some app
46
-	 */
47
-	private $activated = false;
44
+    /**
45
+     * @var bool - Module needs to be activated by some app
46
+     */
47
+    private $activated = false;
48 48
 
49
-	/**
50
-	 * @inheritdoc
51
-	 */
52
-	public function startQuery($sql, array $params = null, array $types = null) {
53
-		if ($this->activated) {
54
-			$this->activeQuery = new Query($sql, $params, microtime(true), $this->getStack());
55
-		}
56
-	}
49
+    /**
50
+     * @inheritdoc
51
+     */
52
+    public function startQuery($sql, array $params = null, array $types = null) {
53
+        if ($this->activated) {
54
+            $this->activeQuery = new Query($sql, $params, microtime(true), $this->getStack());
55
+        }
56
+    }
57 57
 
58
-	private function getStack() {
59
-		$stack = debug_backtrace();
60
-		array_shift($stack);
61
-		array_shift($stack);
62
-		array_shift($stack);
63
-		return $stack;
64
-	}
58
+    private function getStack() {
59
+        $stack = debug_backtrace();
60
+        array_shift($stack);
61
+        array_shift($stack);
62
+        array_shift($stack);
63
+        return $stack;
64
+    }
65 65
 
66
-	/**
67
-	 * @inheritdoc
68
-	 */
69
-	public function stopQuery() {
70
-		if ($this->activated && $this->activeQuery) {
71
-			$this->activeQuery->end(microtime(true));
72
-			$this->queries[(string)$this->index] = $this->activeQuery;
73
-			$this->index++;
74
-			$this->activeQuery = null;
75
-		}
76
-	}
66
+    /**
67
+     * @inheritdoc
68
+     */
69
+    public function stopQuery() {
70
+        if ($this->activated && $this->activeQuery) {
71
+            $this->activeQuery->end(microtime(true));
72
+            $this->queries[(string)$this->index] = $this->activeQuery;
73
+            $this->index++;
74
+            $this->activeQuery = null;
75
+        }
76
+    }
77 77
 
78
-	/**
79
-	 * @inheritdoc
80
-	 */
81
-	public function getQueries() {
82
-		return $this->queries->getData();
83
-	}
78
+    /**
79
+     * @inheritdoc
80
+     */
81
+    public function getQueries() {
82
+        return $this->queries->getData();
83
+    }
84 84
 
85
-	/**
86
-	 * @inheritdoc
87
-	 */
88
-	public function activate() {
89
-		$this->activated = true;
90
-	}
85
+    /**
86
+     * @inheritdoc
87
+     */
88
+    public function activate() {
89
+        $this->activated = true;
90
+    }
91 91
 }
Please login to merge, or discard this patch.
Spacing   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -69,7 +69,7 @@
 block discarded – undo
69 69
 	public function stopQuery() {
70 70
 		if ($this->activated && $this->activeQuery) {
71 71
 			$this->activeQuery->end(microtime(true));
72
-			$this->queries[(string)$this->index] = $this->activeQuery;
72
+			$this->queries[(string) $this->index] = $this->activeQuery;
73 73
 			$this->index++;
74 74
 			$this->activeQuery = null;
75 75
 		}
Please login to merge, or discard this patch.
lib/private/Encryption/File.php 1 patch
Indentation   +92 added lines, -92 removed lines patch added patch discarded remove patch
@@ -35,96 +35,96 @@
 block discarded – undo
35 35
 
36 36
 class File implements \OCP\Encryption\IFile {
37 37
 
38
-	/** @var Util */
39
-	protected $util;
40
-
41
-	/** @var IRootFolder */
42
-	private $rootFolder;
43
-
44
-	/** @var IManager */
45
-	private $shareManager;
46
-
47
-	/**
48
-	 * cache results of already checked folders
49
-	 *
50
-	 * @var CappedMemoryCache<array>
51
-	 */
52
-	protected CappedMemoryCache $cache;
53
-
54
-	public function __construct(Util $util,
55
-								IRootFolder $rootFolder,
56
-								IManager $shareManager) {
57
-		$this->util = $util;
58
-		$this->cache = new CappedMemoryCache();
59
-		$this->rootFolder = $rootFolder;
60
-		$this->shareManager = $shareManager;
61
-	}
62
-
63
-
64
-	/**
65
-	 * Get list of users with access to the file
66
-	 *
67
-	 * @param string $path to the file
68
-	 * @return array{users: string[], public: bool}
69
-	 */
70
-	public function getAccessList($path) {
71
-
72
-		// Make sure that a share key is generated for the owner too
73
-		[$owner, $ownerPath] = $this->util->getUidAndFilename($path);
74
-
75
-		// always add owner to the list of users with access to the file
76
-		$userIds = [$owner];
77
-
78
-		if (!$this->util->isFile($owner . '/' . $ownerPath)) {
79
-			return ['users' => $userIds, 'public' => false];
80
-		}
81
-
82
-		$ownerPath = substr($ownerPath, strlen('/files'));
83
-		$userFolder = $this->rootFolder->getUserFolder($owner);
84
-		try {
85
-			$file = $userFolder->get($ownerPath);
86
-		} catch (NotFoundException $e) {
87
-			$file = null;
88
-		}
89
-		$ownerPath = $this->util->stripPartialFileExtension($ownerPath);
90
-
91
-		// first get the shares for the parent and cache the result so that we don't
92
-		// need to check all parents for every file
93
-		$parent = dirname($ownerPath);
94
-		$parentNode = $userFolder->get($parent);
95
-		if (isset($this->cache[$parent])) {
96
-			$resultForParents = $this->cache[$parent];
97
-		} else {
98
-			$resultForParents = $this->shareManager->getAccessList($parentNode);
99
-			$this->cache[$parent] = $resultForParents;
100
-		}
101
-		$userIds = array_merge($userIds, $resultForParents['users']);
102
-		$public = $resultForParents['public'] || $resultForParents['remote'];
103
-
104
-
105
-		// Find out who, if anyone, is sharing the file
106
-		if ($file !== null) {
107
-			$resultForFile = $this->shareManager->getAccessList($file, false);
108
-			$userIds = array_merge($userIds, $resultForFile['users']);
109
-			$public = $resultForFile['public'] || $resultForFile['remote'] || $public;
110
-		}
111
-
112
-		// check if it is a group mount
113
-		if (\OCP\App::isEnabled("files_external")) {
114
-			/** @var GlobalStoragesService $storageService */
115
-			$storageService = \OC::$server->get(GlobalStoragesService::class);
116
-			$storages = $storageService->getAllStorages();
117
-			foreach ($storages as $storage) {
118
-				if ($storage->getMountPoint() == substr($ownerPath, 0, strlen($storage->getMountPoint()))) {
119
-					$mountedFor = $this->util->getUserWithAccessToMountPoint($storage->getApplicableUsers(), $storage->getApplicableGroups());
120
-					$userIds = array_merge($userIds, $mountedFor);
121
-				}
122
-			}
123
-		}
124
-
125
-		// Remove duplicate UIDs
126
-		$uniqueUserIds = array_unique($userIds);
127
-
128
-		return ['users' => $uniqueUserIds, 'public' => $public];
129
-	}
38
+    /** @var Util */
39
+    protected $util;
40
+
41
+    /** @var IRootFolder */
42
+    private $rootFolder;
43
+
44
+    /** @var IManager */
45
+    private $shareManager;
46
+
47
+    /**
48
+     * cache results of already checked folders
49
+     *
50
+     * @var CappedMemoryCache<array>
51
+     */
52
+    protected CappedMemoryCache $cache;
53
+
54
+    public function __construct(Util $util,
55
+                                IRootFolder $rootFolder,
56
+                                IManager $shareManager) {
57
+        $this->util = $util;
58
+        $this->cache = new CappedMemoryCache();
59
+        $this->rootFolder = $rootFolder;
60
+        $this->shareManager = $shareManager;
61
+    }
62
+
63
+
64
+    /**
65
+     * Get list of users with access to the file
66
+     *
67
+     * @param string $path to the file
68
+     * @return array{users: string[], public: bool}
69
+     */
70
+    public function getAccessList($path) {
71
+
72
+        // Make sure that a share key is generated for the owner too
73
+        [$owner, $ownerPath] = $this->util->getUidAndFilename($path);
74
+
75
+        // always add owner to the list of users with access to the file
76
+        $userIds = [$owner];
77
+
78
+        if (!$this->util->isFile($owner . '/' . $ownerPath)) {
79
+            return ['users' => $userIds, 'public' => false];
80
+        }
81
+
82
+        $ownerPath = substr($ownerPath, strlen('/files'));
83
+        $userFolder = $this->rootFolder->getUserFolder($owner);
84
+        try {
85
+            $file = $userFolder->get($ownerPath);
86
+        } catch (NotFoundException $e) {
87
+            $file = null;
88
+        }
89
+        $ownerPath = $this->util->stripPartialFileExtension($ownerPath);
90
+
91
+        // first get the shares for the parent and cache the result so that we don't
92
+        // need to check all parents for every file
93
+        $parent = dirname($ownerPath);
94
+        $parentNode = $userFolder->get($parent);
95
+        if (isset($this->cache[$parent])) {
96
+            $resultForParents = $this->cache[$parent];
97
+        } else {
98
+            $resultForParents = $this->shareManager->getAccessList($parentNode);
99
+            $this->cache[$parent] = $resultForParents;
100
+        }
101
+        $userIds = array_merge($userIds, $resultForParents['users']);
102
+        $public = $resultForParents['public'] || $resultForParents['remote'];
103
+
104
+
105
+        // Find out who, if anyone, is sharing the file
106
+        if ($file !== null) {
107
+            $resultForFile = $this->shareManager->getAccessList($file, false);
108
+            $userIds = array_merge($userIds, $resultForFile['users']);
109
+            $public = $resultForFile['public'] || $resultForFile['remote'] || $public;
110
+        }
111
+
112
+        // check if it is a group mount
113
+        if (\OCP\App::isEnabled("files_external")) {
114
+            /** @var GlobalStoragesService $storageService */
115
+            $storageService = \OC::$server->get(GlobalStoragesService::class);
116
+            $storages = $storageService->getAllStorages();
117
+            foreach ($storages as $storage) {
118
+                if ($storage->getMountPoint() == substr($ownerPath, 0, strlen($storage->getMountPoint()))) {
119
+                    $mountedFor = $this->util->getUserWithAccessToMountPoint($storage->getApplicableUsers(), $storage->getApplicableGroups());
120
+                    $userIds = array_merge($userIds, $mountedFor);
121
+                }
122
+            }
123
+        }
124
+
125
+        // Remove duplicate UIDs
126
+        $uniqueUserIds = array_unique($userIds);
127
+
128
+        return ['users' => $uniqueUserIds, 'public' => $public];
129
+    }
130 130
 }
Please login to merge, or discard this patch.
lib/private/Encryption/Util.php 1 patch
Indentation   +366 added lines, -366 removed lines patch added patch discarded remove patch
@@ -39,370 +39,370 @@
 block discarded – undo
39 39
 use OCP\IUser;
40 40
 
41 41
 class Util {
42
-	public const HEADER_START = 'HBEGIN';
43
-	public const HEADER_END = 'HEND';
44
-	public const HEADER_PADDING_CHAR = '-';
45
-
46
-	public const HEADER_ENCRYPTION_MODULE_KEY = 'oc_encryption_module';
47
-
48
-	/**
49
-	 * block size will always be 8192 for a PHP stream
50
-	 * @see https://bugs.php.net/bug.php?id=21641
51
-	 * @var integer
52
-	 */
53
-	protected $headerSize = 8192;
54
-
55
-	/**
56
-	 * block size will always be 8192 for a PHP stream
57
-	 * @see https://bugs.php.net/bug.php?id=21641
58
-	 * @var integer
59
-	 */
60
-	protected $blockSize = 8192;
61
-
62
-	/** @var View */
63
-	protected $rootView;
64
-
65
-	/** @var array */
66
-	protected $ocHeaderKeys;
67
-
68
-	/** @var \OC\User\Manager */
69
-	protected $userManager;
70
-
71
-	/** @var IConfig */
72
-	protected $config;
73
-
74
-	/** @var array paths excluded from encryption */
75
-	protected $excludedPaths;
76
-
77
-	/** @var \OC\Group\Manager $manager */
78
-	protected $groupManager;
79
-
80
-	/**
81
-	 *
82
-	 * @param View $rootView
83
-	 * @param \OC\User\Manager $userManager
84
-	 * @param \OC\Group\Manager $groupManager
85
-	 * @param IConfig $config
86
-	 */
87
-	public function __construct(
88
-		View $rootView,
89
-		\OC\User\Manager $userManager,
90
-		\OC\Group\Manager $groupManager,
91
-		IConfig $config) {
92
-		$this->ocHeaderKeys = [
93
-			self::HEADER_ENCRYPTION_MODULE_KEY
94
-		];
95
-
96
-		$this->rootView = $rootView;
97
-		$this->userManager = $userManager;
98
-		$this->groupManager = $groupManager;
99
-		$this->config = $config;
100
-
101
-		$this->excludedPaths[] = 'files_encryption';
102
-		$this->excludedPaths[] = 'appdata_' . $config->getSystemValue('instanceid', null);
103
-		$this->excludedPaths[] = 'files_external';
104
-	}
105
-
106
-	/**
107
-	 * read encryption module ID from header
108
-	 *
109
-	 * @param array $header
110
-	 * @return string
111
-	 * @throws ModuleDoesNotExistsException
112
-	 */
113
-	public function getEncryptionModuleId(array $header = null) {
114
-		$id = '';
115
-		$encryptionModuleKey = self::HEADER_ENCRYPTION_MODULE_KEY;
116
-
117
-		if (isset($header[$encryptionModuleKey])) {
118
-			$id = $header[$encryptionModuleKey];
119
-		} elseif (isset($header['cipher'])) {
120
-			if (class_exists('\OCA\Encryption\Crypto\Encryption')) {
121
-				// fall back to default encryption if the user migrated from
122
-				// ownCloud <= 8.0 with the old encryption
123
-				$id = \OCA\Encryption\Crypto\Encryption::ID;
124
-			} else {
125
-				throw new ModuleDoesNotExistsException('Default encryption module missing');
126
-			}
127
-		}
128
-
129
-		return $id;
130
-	}
131
-
132
-	/**
133
-	 * create header for encrypted file
134
-	 *
135
-	 * @param array $headerData
136
-	 * @param IEncryptionModule $encryptionModule
137
-	 * @return string
138
-	 * @throws EncryptionHeaderToLargeException if header has to many arguments
139
-	 * @throws EncryptionHeaderKeyExistsException if header key is already in use
140
-	 */
141
-	public function createHeader(array $headerData, IEncryptionModule $encryptionModule) {
142
-		$header = self::HEADER_START . ':' . self::HEADER_ENCRYPTION_MODULE_KEY . ':' . $encryptionModule->getId() . ':';
143
-		foreach ($headerData as $key => $value) {
144
-			if (in_array($key, $this->ocHeaderKeys)) {
145
-				throw new EncryptionHeaderKeyExistsException($key);
146
-			}
147
-			$header .= $key . ':' . $value . ':';
148
-		}
149
-		$header .= self::HEADER_END;
150
-
151
-		if (strlen($header) > $this->getHeaderSize()) {
152
-			throw new EncryptionHeaderToLargeException();
153
-		}
154
-
155
-		$paddedHeader = str_pad($header, $this->headerSize, self::HEADER_PADDING_CHAR, STR_PAD_RIGHT);
156
-
157
-		return $paddedHeader;
158
-	}
159
-
160
-	/**
161
-	 * go recursively through a dir and collect all files and sub files.
162
-	 *
163
-	 * @param string $dir relative to the users files folder
164
-	 * @return array with list of files relative to the users files folder
165
-	 */
166
-	public function getAllFiles($dir) {
167
-		$result = [];
168
-		$dirList = [$dir];
169
-
170
-		while ($dirList) {
171
-			$dir = array_pop($dirList);
172
-			$content = $this->rootView->getDirectoryContent($dir);
173
-
174
-			foreach ($content as $c) {
175
-				if ($c->getType() === 'dir') {
176
-					$dirList[] = $c->getPath();
177
-				} else {
178
-					$result[] = $c->getPath();
179
-				}
180
-			}
181
-		}
182
-
183
-		return $result;
184
-	}
185
-
186
-	/**
187
-	 * check if it is a file uploaded by the user stored in data/user/files
188
-	 * or a metadata file
189
-	 *
190
-	 * @param string $path relative to the data/ folder
191
-	 * @return boolean
192
-	 */
193
-	public function isFile($path) {
194
-		$parts = explode('/', Filesystem::normalizePath($path), 4);
195
-		if (isset($parts[2]) && $parts[2] === 'files') {
196
-			return true;
197
-		}
198
-		return false;
199
-	}
200
-
201
-	/**
202
-	 * return size of encryption header
203
-	 *
204
-	 * @return integer
205
-	 */
206
-	public function getHeaderSize() {
207
-		return $this->headerSize;
208
-	}
209
-
210
-	/**
211
-	 * return size of block read by a PHP stream
212
-	 *
213
-	 * @return integer
214
-	 */
215
-	public function getBlockSize() {
216
-		return $this->blockSize;
217
-	}
218
-
219
-	/**
220
-	 * get the owner and the path for the file relative to the owners files folder
221
-	 *
222
-	 * @param string $path
223
-	 * @return array{0: string, 1: string}
224
-	 * @throws \BadMethodCallException
225
-	 */
226
-	public function getUidAndFilename($path) {
227
-		$parts = explode('/', $path);
228
-		$uid = '';
229
-		if (count($parts) > 2) {
230
-			$uid = $parts[1];
231
-		}
232
-		if (!$this->userManager->userExists($uid)) {
233
-			throw new \BadMethodCallException(
234
-				'path needs to be relative to the system wide data folder and point to a user specific file'
235
-			);
236
-		}
237
-
238
-		$ownerPath = implode('/', array_slice($parts, 2));
239
-
240
-		return [$uid, Filesystem::normalizePath($ownerPath)];
241
-	}
242
-
243
-	/**
244
-	 * Remove .path extension from a file path
245
-	 * @param string $path Path that may identify a .part file
246
-	 * @return string File path without .part extension
247
-	 * @note this is needed for reusing keys
248
-	 */
249
-	public function stripPartialFileExtension($path) {
250
-		$extension = pathinfo($path, PATHINFO_EXTENSION);
251
-
252
-		if ($extension === 'part') {
253
-			$newLength = strlen($path) - 5; // 5 = strlen(".part")
254
-			$fPath = substr($path, 0, $newLength);
255
-
256
-			// if path also contains a transaction id, we remove it too
257
-			$extension = pathinfo($fPath, PATHINFO_EXTENSION);
258
-			if (substr($extension, 0, 12) === 'ocTransferId') { // 12 = strlen("ocTransferId")
259
-				$newLength = strlen($fPath) - strlen($extension) - 1;
260
-				$fPath = substr($fPath, 0, $newLength);
261
-			}
262
-			return $fPath;
263
-		} else {
264
-			return $path;
265
-		}
266
-	}
267
-
268
-	public function getUserWithAccessToMountPoint($users, $groups) {
269
-		$result = [];
270
-		if ($users === [] && $groups === []) {
271
-			$users = $this->userManager->search('', null, null);
272
-			$result = array_map(function (IUser $user) {
273
-				return $user->getUID();
274
-			}, $users);
275
-		} else {
276
-			$result = array_merge($result, $users);
277
-
278
-			$groupManager = \OC::$server->getGroupManager();
279
-			foreach ($groups as $group) {
280
-				$groupObject = $groupManager->get($group);
281
-				if ($groupObject) {
282
-					$foundUsers = $groupObject->searchUsers('', -1, 0);
283
-					$userIds = [];
284
-					foreach ($foundUsers as $user) {
285
-						$userIds[] = $user->getUID();
286
-					}
287
-					$result = array_merge($result, $userIds);
288
-				}
289
-			}
290
-		}
291
-
292
-		return $result;
293
-	}
294
-
295
-	/**
296
-	 * check if the file is stored on a system wide mount point
297
-	 * @param string $path relative to /data/user with leading '/'
298
-	 * @param string $uid
299
-	 * @return boolean
300
-	 */
301
-	public function isSystemWideMountPoint($path, $uid) {
302
-		if (\OCP\App::isEnabled("files_external")) {
303
-			/** @var GlobalStoragesService $storageService */
304
-			$storageService = \OC::$server->get(GlobalStoragesService::class);
305
-			$storages = $storageService->getAllStorages();
306
-			foreach ($storages as $storage) {
307
-				if (strpos($path, '/files/' . $storage->getMountPoint()) === 0) {
308
-					if ($this->isMountPointApplicableToUser($storage, $uid)) {
309
-						return true;
310
-					}
311
-				}
312
-			}
313
-		}
314
-		return false;
315
-	}
316
-
317
-	/**
318
-	 * check if mount point is applicable to user
319
-	 *
320
-	 * @param StorageConfig $mount
321
-	 * @param string $uid
322
-	 * @return boolean
323
-	 */
324
-	private function isMountPointApplicableToUser(StorageConfig $mount, string $uid) {
325
-		if ($mount->getApplicableUsers() === [] && $mount->getApplicableGroups() === []) {
326
-			// applicable for everyone
327
-			return true;
328
-		}
329
-		// check if mount point is applicable for the user
330
-		if (array_search($uid, $mount->getApplicableUsers()) !== false) {
331
-			return true;
332
-		}
333
-		// check if mount point is applicable for group where the user is a member
334
-		foreach ($mount->getApplicableGroups() as $gid) {
335
-			if ($this->groupManager->isInGroup($uid, $gid)) {
336
-				return true;
337
-			}
338
-		}
339
-		return false;
340
-	}
341
-
342
-	/**
343
-	 * check if it is a path which is excluded by ownCloud from encryption
344
-	 *
345
-	 * @param string $path
346
-	 * @return boolean
347
-	 */
348
-	public function isExcluded($path) {
349
-		$normalizedPath = Filesystem::normalizePath($path);
350
-		$root = explode('/', $normalizedPath, 4);
351
-		if (count($root) > 1) {
352
-
353
-			// detect alternative key storage root
354
-			$rootDir = $this->getKeyStorageRoot();
355
-			if ($rootDir !== '' &&
356
-				0 === strpos(
357
-					Filesystem::normalizePath($path),
358
-					Filesystem::normalizePath($rootDir)
359
-				)
360
-			) {
361
-				return true;
362
-			}
363
-
364
-
365
-			//detect system wide folders
366
-			if (in_array($root[1], $this->excludedPaths)) {
367
-				return true;
368
-			}
369
-
370
-			// detect user specific folders
371
-			if ($this->userManager->userExists($root[1])
372
-				&& in_array($root[2], $this->excludedPaths)) {
373
-				return true;
374
-			}
375
-		}
376
-		return false;
377
-	}
378
-
379
-	/**
380
-	 * check if recovery key is enabled for user
381
-	 *
382
-	 * @param string $uid
383
-	 * @return boolean
384
-	 */
385
-	public function recoveryEnabled($uid) {
386
-		$enabled = $this->config->getUserValue($uid, 'encryption', 'recovery_enabled', '0');
387
-
388
-		return $enabled === '1';
389
-	}
390
-
391
-	/**
392
-	 * set new key storage root
393
-	 *
394
-	 * @param string $root new key store root relative to the data folder
395
-	 */
396
-	public function setKeyStorageRoot($root) {
397
-		$this->config->setAppValue('core', 'encryption_key_storage_root', $root);
398
-	}
399
-
400
-	/**
401
-	 * get key storage root
402
-	 *
403
-	 * @return string key storage root
404
-	 */
405
-	public function getKeyStorageRoot() {
406
-		return $this->config->getAppValue('core', 'encryption_key_storage_root', '');
407
-	}
42
+    public const HEADER_START = 'HBEGIN';
43
+    public const HEADER_END = 'HEND';
44
+    public const HEADER_PADDING_CHAR = '-';
45
+
46
+    public const HEADER_ENCRYPTION_MODULE_KEY = 'oc_encryption_module';
47
+
48
+    /**
49
+     * block size will always be 8192 for a PHP stream
50
+     * @see https://bugs.php.net/bug.php?id=21641
51
+     * @var integer
52
+     */
53
+    protected $headerSize = 8192;
54
+
55
+    /**
56
+     * block size will always be 8192 for a PHP stream
57
+     * @see https://bugs.php.net/bug.php?id=21641
58
+     * @var integer
59
+     */
60
+    protected $blockSize = 8192;
61
+
62
+    /** @var View */
63
+    protected $rootView;
64
+
65
+    /** @var array */
66
+    protected $ocHeaderKeys;
67
+
68
+    /** @var \OC\User\Manager */
69
+    protected $userManager;
70
+
71
+    /** @var IConfig */
72
+    protected $config;
73
+
74
+    /** @var array paths excluded from encryption */
75
+    protected $excludedPaths;
76
+
77
+    /** @var \OC\Group\Manager $manager */
78
+    protected $groupManager;
79
+
80
+    /**
81
+     *
82
+     * @param View $rootView
83
+     * @param \OC\User\Manager $userManager
84
+     * @param \OC\Group\Manager $groupManager
85
+     * @param IConfig $config
86
+     */
87
+    public function __construct(
88
+        View $rootView,
89
+        \OC\User\Manager $userManager,
90
+        \OC\Group\Manager $groupManager,
91
+        IConfig $config) {
92
+        $this->ocHeaderKeys = [
93
+            self::HEADER_ENCRYPTION_MODULE_KEY
94
+        ];
95
+
96
+        $this->rootView = $rootView;
97
+        $this->userManager = $userManager;
98
+        $this->groupManager = $groupManager;
99
+        $this->config = $config;
100
+
101
+        $this->excludedPaths[] = 'files_encryption';
102
+        $this->excludedPaths[] = 'appdata_' . $config->getSystemValue('instanceid', null);
103
+        $this->excludedPaths[] = 'files_external';
104
+    }
105
+
106
+    /**
107
+     * read encryption module ID from header
108
+     *
109
+     * @param array $header
110
+     * @return string
111
+     * @throws ModuleDoesNotExistsException
112
+     */
113
+    public function getEncryptionModuleId(array $header = null) {
114
+        $id = '';
115
+        $encryptionModuleKey = self::HEADER_ENCRYPTION_MODULE_KEY;
116
+
117
+        if (isset($header[$encryptionModuleKey])) {
118
+            $id = $header[$encryptionModuleKey];
119
+        } elseif (isset($header['cipher'])) {
120
+            if (class_exists('\OCA\Encryption\Crypto\Encryption')) {
121
+                // fall back to default encryption if the user migrated from
122
+                // ownCloud <= 8.0 with the old encryption
123
+                $id = \OCA\Encryption\Crypto\Encryption::ID;
124
+            } else {
125
+                throw new ModuleDoesNotExistsException('Default encryption module missing');
126
+            }
127
+        }
128
+
129
+        return $id;
130
+    }
131
+
132
+    /**
133
+     * create header for encrypted file
134
+     *
135
+     * @param array $headerData
136
+     * @param IEncryptionModule $encryptionModule
137
+     * @return string
138
+     * @throws EncryptionHeaderToLargeException if header has to many arguments
139
+     * @throws EncryptionHeaderKeyExistsException if header key is already in use
140
+     */
141
+    public function createHeader(array $headerData, IEncryptionModule $encryptionModule) {
142
+        $header = self::HEADER_START . ':' . self::HEADER_ENCRYPTION_MODULE_KEY . ':' . $encryptionModule->getId() . ':';
143
+        foreach ($headerData as $key => $value) {
144
+            if (in_array($key, $this->ocHeaderKeys)) {
145
+                throw new EncryptionHeaderKeyExistsException($key);
146
+            }
147
+            $header .= $key . ':' . $value . ':';
148
+        }
149
+        $header .= self::HEADER_END;
150
+
151
+        if (strlen($header) > $this->getHeaderSize()) {
152
+            throw new EncryptionHeaderToLargeException();
153
+        }
154
+
155
+        $paddedHeader = str_pad($header, $this->headerSize, self::HEADER_PADDING_CHAR, STR_PAD_RIGHT);
156
+
157
+        return $paddedHeader;
158
+    }
159
+
160
+    /**
161
+     * go recursively through a dir and collect all files and sub files.
162
+     *
163
+     * @param string $dir relative to the users files folder
164
+     * @return array with list of files relative to the users files folder
165
+     */
166
+    public function getAllFiles($dir) {
167
+        $result = [];
168
+        $dirList = [$dir];
169
+
170
+        while ($dirList) {
171
+            $dir = array_pop($dirList);
172
+            $content = $this->rootView->getDirectoryContent($dir);
173
+
174
+            foreach ($content as $c) {
175
+                if ($c->getType() === 'dir') {
176
+                    $dirList[] = $c->getPath();
177
+                } else {
178
+                    $result[] = $c->getPath();
179
+                }
180
+            }
181
+        }
182
+
183
+        return $result;
184
+    }
185
+
186
+    /**
187
+     * check if it is a file uploaded by the user stored in data/user/files
188
+     * or a metadata file
189
+     *
190
+     * @param string $path relative to the data/ folder
191
+     * @return boolean
192
+     */
193
+    public function isFile($path) {
194
+        $parts = explode('/', Filesystem::normalizePath($path), 4);
195
+        if (isset($parts[2]) && $parts[2] === 'files') {
196
+            return true;
197
+        }
198
+        return false;
199
+    }
200
+
201
+    /**
202
+     * return size of encryption header
203
+     *
204
+     * @return integer
205
+     */
206
+    public function getHeaderSize() {
207
+        return $this->headerSize;
208
+    }
209
+
210
+    /**
211
+     * return size of block read by a PHP stream
212
+     *
213
+     * @return integer
214
+     */
215
+    public function getBlockSize() {
216
+        return $this->blockSize;
217
+    }
218
+
219
+    /**
220
+     * get the owner and the path for the file relative to the owners files folder
221
+     *
222
+     * @param string $path
223
+     * @return array{0: string, 1: string}
224
+     * @throws \BadMethodCallException
225
+     */
226
+    public function getUidAndFilename($path) {
227
+        $parts = explode('/', $path);
228
+        $uid = '';
229
+        if (count($parts) > 2) {
230
+            $uid = $parts[1];
231
+        }
232
+        if (!$this->userManager->userExists($uid)) {
233
+            throw new \BadMethodCallException(
234
+                'path needs to be relative to the system wide data folder and point to a user specific file'
235
+            );
236
+        }
237
+
238
+        $ownerPath = implode('/', array_slice($parts, 2));
239
+
240
+        return [$uid, Filesystem::normalizePath($ownerPath)];
241
+    }
242
+
243
+    /**
244
+     * Remove .path extension from a file path
245
+     * @param string $path Path that may identify a .part file
246
+     * @return string File path without .part extension
247
+     * @note this is needed for reusing keys
248
+     */
249
+    public function stripPartialFileExtension($path) {
250
+        $extension = pathinfo($path, PATHINFO_EXTENSION);
251
+
252
+        if ($extension === 'part') {
253
+            $newLength = strlen($path) - 5; // 5 = strlen(".part")
254
+            $fPath = substr($path, 0, $newLength);
255
+
256
+            // if path also contains a transaction id, we remove it too
257
+            $extension = pathinfo($fPath, PATHINFO_EXTENSION);
258
+            if (substr($extension, 0, 12) === 'ocTransferId') { // 12 = strlen("ocTransferId")
259
+                $newLength = strlen($fPath) - strlen($extension) - 1;
260
+                $fPath = substr($fPath, 0, $newLength);
261
+            }
262
+            return $fPath;
263
+        } else {
264
+            return $path;
265
+        }
266
+    }
267
+
268
+    public function getUserWithAccessToMountPoint($users, $groups) {
269
+        $result = [];
270
+        if ($users === [] && $groups === []) {
271
+            $users = $this->userManager->search('', null, null);
272
+            $result = array_map(function (IUser $user) {
273
+                return $user->getUID();
274
+            }, $users);
275
+        } else {
276
+            $result = array_merge($result, $users);
277
+
278
+            $groupManager = \OC::$server->getGroupManager();
279
+            foreach ($groups as $group) {
280
+                $groupObject = $groupManager->get($group);
281
+                if ($groupObject) {
282
+                    $foundUsers = $groupObject->searchUsers('', -1, 0);
283
+                    $userIds = [];
284
+                    foreach ($foundUsers as $user) {
285
+                        $userIds[] = $user->getUID();
286
+                    }
287
+                    $result = array_merge($result, $userIds);
288
+                }
289
+            }
290
+        }
291
+
292
+        return $result;
293
+    }
294
+
295
+    /**
296
+     * check if the file is stored on a system wide mount point
297
+     * @param string $path relative to /data/user with leading '/'
298
+     * @param string $uid
299
+     * @return boolean
300
+     */
301
+    public function isSystemWideMountPoint($path, $uid) {
302
+        if (\OCP\App::isEnabled("files_external")) {
303
+            /** @var GlobalStoragesService $storageService */
304
+            $storageService = \OC::$server->get(GlobalStoragesService::class);
305
+            $storages = $storageService->getAllStorages();
306
+            foreach ($storages as $storage) {
307
+                if (strpos($path, '/files/' . $storage->getMountPoint()) === 0) {
308
+                    if ($this->isMountPointApplicableToUser($storage, $uid)) {
309
+                        return true;
310
+                    }
311
+                }
312
+            }
313
+        }
314
+        return false;
315
+    }
316
+
317
+    /**
318
+     * check if mount point is applicable to user
319
+     *
320
+     * @param StorageConfig $mount
321
+     * @param string $uid
322
+     * @return boolean
323
+     */
324
+    private function isMountPointApplicableToUser(StorageConfig $mount, string $uid) {
325
+        if ($mount->getApplicableUsers() === [] && $mount->getApplicableGroups() === []) {
326
+            // applicable for everyone
327
+            return true;
328
+        }
329
+        // check if mount point is applicable for the user
330
+        if (array_search($uid, $mount->getApplicableUsers()) !== false) {
331
+            return true;
332
+        }
333
+        // check if mount point is applicable for group where the user is a member
334
+        foreach ($mount->getApplicableGroups() as $gid) {
335
+            if ($this->groupManager->isInGroup($uid, $gid)) {
336
+                return true;
337
+            }
338
+        }
339
+        return false;
340
+    }
341
+
342
+    /**
343
+     * check if it is a path which is excluded by ownCloud from encryption
344
+     *
345
+     * @param string $path
346
+     * @return boolean
347
+     */
348
+    public function isExcluded($path) {
349
+        $normalizedPath = Filesystem::normalizePath($path);
350
+        $root = explode('/', $normalizedPath, 4);
351
+        if (count($root) > 1) {
352
+
353
+            // detect alternative key storage root
354
+            $rootDir = $this->getKeyStorageRoot();
355
+            if ($rootDir !== '' &&
356
+                0 === strpos(
357
+                    Filesystem::normalizePath($path),
358
+                    Filesystem::normalizePath($rootDir)
359
+                )
360
+            ) {
361
+                return true;
362
+            }
363
+
364
+
365
+            //detect system wide folders
366
+            if (in_array($root[1], $this->excludedPaths)) {
367
+                return true;
368
+            }
369
+
370
+            // detect user specific folders
371
+            if ($this->userManager->userExists($root[1])
372
+                && in_array($root[2], $this->excludedPaths)) {
373
+                return true;
374
+            }
375
+        }
376
+        return false;
377
+    }
378
+
379
+    /**
380
+     * check if recovery key is enabled for user
381
+     *
382
+     * @param string $uid
383
+     * @return boolean
384
+     */
385
+    public function recoveryEnabled($uid) {
386
+        $enabled = $this->config->getUserValue($uid, 'encryption', 'recovery_enabled', '0');
387
+
388
+        return $enabled === '1';
389
+    }
390
+
391
+    /**
392
+     * set new key storage root
393
+     *
394
+     * @param string $root new key store root relative to the data folder
395
+     */
396
+    public function setKeyStorageRoot($root) {
397
+        $this->config->setAppValue('core', 'encryption_key_storage_root', $root);
398
+    }
399
+
400
+    /**
401
+     * get key storage root
402
+     *
403
+     * @return string key storage root
404
+     */
405
+    public function getKeyStorageRoot() {
406
+        return $this->config->getAppValue('core', 'encryption_key_storage_root', '');
407
+    }
408 408
 }
Please login to merge, or discard this patch.
apps/files_sharing/lib/SharedMount.php 1 patch
Indentation   +243 added lines, -243 removed lines patch added patch discarded remove patch
@@ -45,247 +45,247 @@
 block discarded – undo
45 45
  * Shared mount points can be moved by the user
46 46
  */
47 47
 class SharedMount extends MountPoint implements MoveableMount {
48
-	/**
49
-	 * @var \OCA\Files_Sharing\SharedStorage $storage
50
-	 */
51
-	protected $storage = null;
52
-
53
-	/**
54
-	 * @var \OC\Files\View
55
-	 */
56
-	private $recipientView;
57
-
58
-	private IUser $user;
59
-
60
-	/** @var \OCP\Share\IShare */
61
-	private $superShare;
62
-
63
-	/** @var \OCP\Share\IShare[] */
64
-	private $groupedShares;
65
-
66
-	private IEventDispatcher $eventDispatcher;
67
-
68
-	private ICache $cache;
69
-
70
-	public function __construct(
71
-		$storage,
72
-		array $mountpoints,
73
-		$arguments,
74
-		IStorageFactory $loader,
75
-		View $recipientView,
76
-		CappedMemoryCache $folderExistCache,
77
-		IEventDispatcher $eventDispatcher,
78
-		IUser $user,
79
-		ICache $cache
80
-	) {
81
-		$this->user = $user;
82
-		$this->recipientView = $recipientView;
83
-		$this->eventDispatcher = $eventDispatcher;
84
-		$this->cache = $cache;
85
-
86
-		$this->superShare = $arguments['superShare'];
87
-		$this->groupedShares = $arguments['groupedShares'];
88
-
89
-		$newMountPoint = $this->verifyMountPoint($this->superShare, $mountpoints, $folderExistCache);
90
-		$absMountPoint = '/' . $user->getUID() . '/files' . $newMountPoint;
91
-		parent::__construct($storage, $absMountPoint, $arguments, $loader, null, null, MountProvider::class);
92
-	}
93
-
94
-	/**
95
-	 * check if the parent folder exists otherwise move the mount point up
96
-	 *
97
-	 * @param \OCP\Share\IShare $share
98
-	 * @param SharedMount[] $mountpoints
99
-	 * @param CappedMemoryCache<bool> $folderExistCache
100
-	 * @return string
101
-	 */
102
-	private function verifyMountPoint(
103
-		\OCP\Share\IShare $share,
104
-		array $mountpoints,
105
-		CappedMemoryCache $folderExistCache
106
-	) {
107
-		$cacheKey = $this->user->getUID() . '/' . $share->getTarget();
108
-		$cached = $this->cache->get($cacheKey);
109
-		if ($cached !== null) {
110
-			return $cached;
111
-		}
112
-
113
-		$mountPoint = basename($share->getTarget());
114
-		$parent = dirname($share->getTarget());
115
-
116
-		$event = new VerifyMountPointEvent($share, $this->recipientView, $parent);
117
-		$this->eventDispatcher->dispatchTyped($event);
118
-		$parent = $event->getParent();
119
-
120
-		if ($folderExistCache->hasKey($parent)) {
121
-			$parentExists = $folderExistCache->get($parent);
122
-		} else {
123
-			$parentExists = $this->recipientView->is_dir($parent);
124
-			$folderExistCache->set($parent, $parentExists);
125
-		}
126
-		if (!$parentExists) {
127
-			$parent = Helper::getShareFolder($this->recipientView, $this->user->getUID());
128
-		}
129
-
130
-		$newMountPoint = $this->generateUniqueTarget(
131
-			\OC\Files\Filesystem::normalizePath($parent . '/' . $mountPoint),
132
-			$this->recipientView,
133
-			$mountpoints
134
-		);
135
-
136
-		if ($newMountPoint !== $share->getTarget()) {
137
-			$this->updateFileTarget($newMountPoint, $share);
138
-		}
139
-
140
-		$this->cache->set($cacheKey, $newMountPoint, 60 * 60);
141
-
142
-		return $newMountPoint;
143
-	}
144
-
145
-	/**
146
-	 * update fileTarget in the database if the mount point changed
147
-	 *
148
-	 * @param string $newPath
149
-	 * @param \OCP\Share\IShare $share
150
-	 * @return bool
151
-	 */
152
-	private function updateFileTarget($newPath, &$share) {
153
-		$share->setTarget($newPath);
154
-
155
-		foreach ($this->groupedShares as $tmpShare) {
156
-			$tmpShare->setTarget($newPath);
157
-			\OC::$server->getShareManager()->moveShare($tmpShare, $this->user->getUID());
158
-		}
159
-
160
-		$this->eventDispatcher->dispatchTyped(new InvalidateMountCacheEvent($this->user));
161
-	}
162
-
163
-
164
-	/**
165
-	 * @param string $path
166
-	 * @param View $view
167
-	 * @param SharedMount[] $mountpoints
168
-	 * @return mixed
169
-	 */
170
-	private function generateUniqueTarget($path, $view, array $mountpoints) {
171
-		$pathinfo = pathinfo($path);
172
-		$ext = isset($pathinfo['extension']) ? '.' . $pathinfo['extension'] : '';
173
-		$name = $pathinfo['filename'];
174
-		$dir = $pathinfo['dirname'];
175
-
176
-		$i = 2;
177
-		$absolutePath = $this->recipientView->getAbsolutePath($path) . '/';
178
-		while ($view->file_exists($path) || isset($mountpoints[$absolutePath])) {
179
-			$path = Filesystem::normalizePath($dir . '/' . $name . ' (' . $i . ')' . $ext);
180
-			$absolutePath = $this->recipientView->getAbsolutePath($path) . '/';
181
-			$i++;
182
-		}
183
-
184
-		return $path;
185
-	}
186
-
187
-	/**
188
-	 * Format a path to be relative to the /user/files/ directory
189
-	 *
190
-	 * @param string $path the absolute path
191
-	 * @return string e.g. turns '/admin/files/test.txt' into '/test.txt'
192
-	 * @throws \OCA\Files_Sharing\Exceptions\BrokenPath
193
-	 */
194
-	protected function stripUserFilesPath($path) {
195
-		$trimmed = ltrim($path, '/');
196
-		$split = explode('/', $trimmed);
197
-
198
-		// it is not a file relative to data/user/files
199
-		if (count($split) < 3 || $split[1] !== 'files') {
200
-			\OC::$server->getLogger()->error('Can not strip userid and "files/" from path: ' . $path, ['app' => 'files_sharing']);
201
-			throw new \OCA\Files_Sharing\Exceptions\BrokenPath('Path does not start with /user/files', 10);
202
-		}
203
-
204
-		// skip 'user' and 'files'
205
-		$sliced = array_slice($split, 2);
206
-		$relPath = implode('/', $sliced);
207
-
208
-		return '/' . $relPath;
209
-	}
210
-
211
-	/**
212
-	 * Move the mount point to $target
213
-	 *
214
-	 * @param string $target the target mount point
215
-	 * @return bool
216
-	 */
217
-	public function moveMount($target) {
218
-		$relTargetPath = $this->stripUserFilesPath($target);
219
-		$share = $this->storage->getShare();
220
-
221
-		$result = true;
222
-
223
-		try {
224
-			$this->updateFileTarget($relTargetPath, $share);
225
-			$this->setMountPoint($target);
226
-			$this->storage->setMountPoint($relTargetPath);
227
-		} catch (\Exception $e) {
228
-			\OC::$server->getLogger()->logException($e, ['app' => 'files_sharing', 'message' => 'Could not rename mount point for shared folder "' . $this->getMountPoint() . '" to "' . $target . '"']);
229
-		}
230
-
231
-		return $result;
232
-	}
233
-
234
-	/**
235
-	 * Remove the mount points
236
-	 *
237
-	 * @return bool
238
-	 */
239
-	public function removeMount() {
240
-		$mountManager = \OC\Files\Filesystem::getMountManager();
241
-		/** @var \OCA\Files_Sharing\SharedStorage $storage */
242
-		$storage = $this->getStorage();
243
-		$result = $storage->unshareStorage();
244
-		$mountManager->removeMount($this->mountPoint);
245
-
246
-		return $result;
247
-	}
248
-
249
-	/**
250
-	 * @return \OCP\Share\IShare
251
-	 */
252
-	public function getShare() {
253
-		return $this->superShare;
254
-	}
255
-
256
-	/**
257
-	 * Get the file id of the root of the storage
258
-	 *
259
-	 * @return int
260
-	 */
261
-	public function getStorageRootId() {
262
-		return $this->getShare()->getNodeId();
263
-	}
264
-
265
-	/**
266
-	 * @return int
267
-	 */
268
-	public function getNumericStorageId() {
269
-		if (!is_null($this->getShare()->getNodeCacheEntry())) {
270
-			return $this->getShare()->getNodeCacheEntry()->getStorageId();
271
-		} else {
272
-			$builder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
273
-
274
-			$query = $builder->select('storage')
275
-				->from('filecache')
276
-				->where($builder->expr()->eq('fileid', $builder->createNamedParameter($this->getStorageRootId())));
277
-
278
-			$result = $query->execute();
279
-			$row = $result->fetch();
280
-			$result->closeCursor();
281
-			if ($row) {
282
-				return (int)$row['storage'];
283
-			}
284
-			return -1;
285
-		}
286
-	}
287
-
288
-	public function getMountType() {
289
-		return 'shared';
290
-	}
48
+    /**
49
+     * @var \OCA\Files_Sharing\SharedStorage $storage
50
+     */
51
+    protected $storage = null;
52
+
53
+    /**
54
+     * @var \OC\Files\View
55
+     */
56
+    private $recipientView;
57
+
58
+    private IUser $user;
59
+
60
+    /** @var \OCP\Share\IShare */
61
+    private $superShare;
62
+
63
+    /** @var \OCP\Share\IShare[] */
64
+    private $groupedShares;
65
+
66
+    private IEventDispatcher $eventDispatcher;
67
+
68
+    private ICache $cache;
69
+
70
+    public function __construct(
71
+        $storage,
72
+        array $mountpoints,
73
+        $arguments,
74
+        IStorageFactory $loader,
75
+        View $recipientView,
76
+        CappedMemoryCache $folderExistCache,
77
+        IEventDispatcher $eventDispatcher,
78
+        IUser $user,
79
+        ICache $cache
80
+    ) {
81
+        $this->user = $user;
82
+        $this->recipientView = $recipientView;
83
+        $this->eventDispatcher = $eventDispatcher;
84
+        $this->cache = $cache;
85
+
86
+        $this->superShare = $arguments['superShare'];
87
+        $this->groupedShares = $arguments['groupedShares'];
88
+
89
+        $newMountPoint = $this->verifyMountPoint($this->superShare, $mountpoints, $folderExistCache);
90
+        $absMountPoint = '/' . $user->getUID() . '/files' . $newMountPoint;
91
+        parent::__construct($storage, $absMountPoint, $arguments, $loader, null, null, MountProvider::class);
92
+    }
93
+
94
+    /**
95
+     * check if the parent folder exists otherwise move the mount point up
96
+     *
97
+     * @param \OCP\Share\IShare $share
98
+     * @param SharedMount[] $mountpoints
99
+     * @param CappedMemoryCache<bool> $folderExistCache
100
+     * @return string
101
+     */
102
+    private function verifyMountPoint(
103
+        \OCP\Share\IShare $share,
104
+        array $mountpoints,
105
+        CappedMemoryCache $folderExistCache
106
+    ) {
107
+        $cacheKey = $this->user->getUID() . '/' . $share->getTarget();
108
+        $cached = $this->cache->get($cacheKey);
109
+        if ($cached !== null) {
110
+            return $cached;
111
+        }
112
+
113
+        $mountPoint = basename($share->getTarget());
114
+        $parent = dirname($share->getTarget());
115
+
116
+        $event = new VerifyMountPointEvent($share, $this->recipientView, $parent);
117
+        $this->eventDispatcher->dispatchTyped($event);
118
+        $parent = $event->getParent();
119
+
120
+        if ($folderExistCache->hasKey($parent)) {
121
+            $parentExists = $folderExistCache->get($parent);
122
+        } else {
123
+            $parentExists = $this->recipientView->is_dir($parent);
124
+            $folderExistCache->set($parent, $parentExists);
125
+        }
126
+        if (!$parentExists) {
127
+            $parent = Helper::getShareFolder($this->recipientView, $this->user->getUID());
128
+        }
129
+
130
+        $newMountPoint = $this->generateUniqueTarget(
131
+            \OC\Files\Filesystem::normalizePath($parent . '/' . $mountPoint),
132
+            $this->recipientView,
133
+            $mountpoints
134
+        );
135
+
136
+        if ($newMountPoint !== $share->getTarget()) {
137
+            $this->updateFileTarget($newMountPoint, $share);
138
+        }
139
+
140
+        $this->cache->set($cacheKey, $newMountPoint, 60 * 60);
141
+
142
+        return $newMountPoint;
143
+    }
144
+
145
+    /**
146
+     * update fileTarget in the database if the mount point changed
147
+     *
148
+     * @param string $newPath
149
+     * @param \OCP\Share\IShare $share
150
+     * @return bool
151
+     */
152
+    private function updateFileTarget($newPath, &$share) {
153
+        $share->setTarget($newPath);
154
+
155
+        foreach ($this->groupedShares as $tmpShare) {
156
+            $tmpShare->setTarget($newPath);
157
+            \OC::$server->getShareManager()->moveShare($tmpShare, $this->user->getUID());
158
+        }
159
+
160
+        $this->eventDispatcher->dispatchTyped(new InvalidateMountCacheEvent($this->user));
161
+    }
162
+
163
+
164
+    /**
165
+     * @param string $path
166
+     * @param View $view
167
+     * @param SharedMount[] $mountpoints
168
+     * @return mixed
169
+     */
170
+    private function generateUniqueTarget($path, $view, array $mountpoints) {
171
+        $pathinfo = pathinfo($path);
172
+        $ext = isset($pathinfo['extension']) ? '.' . $pathinfo['extension'] : '';
173
+        $name = $pathinfo['filename'];
174
+        $dir = $pathinfo['dirname'];
175
+
176
+        $i = 2;
177
+        $absolutePath = $this->recipientView->getAbsolutePath($path) . '/';
178
+        while ($view->file_exists($path) || isset($mountpoints[$absolutePath])) {
179
+            $path = Filesystem::normalizePath($dir . '/' . $name . ' (' . $i . ')' . $ext);
180
+            $absolutePath = $this->recipientView->getAbsolutePath($path) . '/';
181
+            $i++;
182
+        }
183
+
184
+        return $path;
185
+    }
186
+
187
+    /**
188
+     * Format a path to be relative to the /user/files/ directory
189
+     *
190
+     * @param string $path the absolute path
191
+     * @return string e.g. turns '/admin/files/test.txt' into '/test.txt'
192
+     * @throws \OCA\Files_Sharing\Exceptions\BrokenPath
193
+     */
194
+    protected function stripUserFilesPath($path) {
195
+        $trimmed = ltrim($path, '/');
196
+        $split = explode('/', $trimmed);
197
+
198
+        // it is not a file relative to data/user/files
199
+        if (count($split) < 3 || $split[1] !== 'files') {
200
+            \OC::$server->getLogger()->error('Can not strip userid and "files/" from path: ' . $path, ['app' => 'files_sharing']);
201
+            throw new \OCA\Files_Sharing\Exceptions\BrokenPath('Path does not start with /user/files', 10);
202
+        }
203
+
204
+        // skip 'user' and 'files'
205
+        $sliced = array_slice($split, 2);
206
+        $relPath = implode('/', $sliced);
207
+
208
+        return '/' . $relPath;
209
+    }
210
+
211
+    /**
212
+     * Move the mount point to $target
213
+     *
214
+     * @param string $target the target mount point
215
+     * @return bool
216
+     */
217
+    public function moveMount($target) {
218
+        $relTargetPath = $this->stripUserFilesPath($target);
219
+        $share = $this->storage->getShare();
220
+
221
+        $result = true;
222
+
223
+        try {
224
+            $this->updateFileTarget($relTargetPath, $share);
225
+            $this->setMountPoint($target);
226
+            $this->storage->setMountPoint($relTargetPath);
227
+        } catch (\Exception $e) {
228
+            \OC::$server->getLogger()->logException($e, ['app' => 'files_sharing', 'message' => 'Could not rename mount point for shared folder "' . $this->getMountPoint() . '" to "' . $target . '"']);
229
+        }
230
+
231
+        return $result;
232
+    }
233
+
234
+    /**
235
+     * Remove the mount points
236
+     *
237
+     * @return bool
238
+     */
239
+    public function removeMount() {
240
+        $mountManager = \OC\Files\Filesystem::getMountManager();
241
+        /** @var \OCA\Files_Sharing\SharedStorage $storage */
242
+        $storage = $this->getStorage();
243
+        $result = $storage->unshareStorage();
244
+        $mountManager->removeMount($this->mountPoint);
245
+
246
+        return $result;
247
+    }
248
+
249
+    /**
250
+     * @return \OCP\Share\IShare
251
+     */
252
+    public function getShare() {
253
+        return $this->superShare;
254
+    }
255
+
256
+    /**
257
+     * Get the file id of the root of the storage
258
+     *
259
+     * @return int
260
+     */
261
+    public function getStorageRootId() {
262
+        return $this->getShare()->getNodeId();
263
+    }
264
+
265
+    /**
266
+     * @return int
267
+     */
268
+    public function getNumericStorageId() {
269
+        if (!is_null($this->getShare()->getNodeCacheEntry())) {
270
+            return $this->getShare()->getNodeCacheEntry()->getStorageId();
271
+        } else {
272
+            $builder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
273
+
274
+            $query = $builder->select('storage')
275
+                ->from('filecache')
276
+                ->where($builder->expr()->eq('fileid', $builder->createNamedParameter($this->getStorageRootId())));
277
+
278
+            $result = $query->execute();
279
+            $row = $result->fetch();
280
+            $result->closeCursor();
281
+            if ($row) {
282
+                return (int)$row['storage'];
283
+            }
284
+            return -1;
285
+        }
286
+    }
287
+
288
+    public function getMountType() {
289
+        return 'shared';
290
+    }
291 291
 }
Please login to merge, or discard this patch.
apps/files_sharing/lib/MountProvider.php 2 patches
Indentation   +227 added lines, -227 removed lines patch added patch discarded remove patch
@@ -42,231 +42,231 @@
 block discarded – undo
42 42
 use OCP\Share\IShare;
43 43
 
44 44
 class MountProvider implements IMountProvider {
45
-	/**
46
-	 * @var \OCP\IConfig
47
-	 */
48
-	protected $config;
49
-
50
-	/**
51
-	 * @var IManager
52
-	 */
53
-	protected $shareManager;
54
-
55
-	/**
56
-	 * @var ILogger
57
-	 */
58
-	protected $logger;
59
-
60
-	/** @var IEventDispatcher */
61
-	protected $eventDispatcher;
62
-
63
-	/** @var ICacheFactory */
64
-	protected $cacheFactory;
65
-
66
-	/**
67
-	 * @param \OCP\IConfig $config
68
-	 * @param IManager $shareManager
69
-	 * @param ILogger $logger
70
-	 */
71
-	public function __construct(
72
-		IConfig $config,
73
-		IManager $shareManager,
74
-		ILogger $logger,
75
-		IEventDispatcher $eventDispatcher,
76
-		ICacheFactory $cacheFactory
77
-	) {
78
-		$this->config = $config;
79
-		$this->shareManager = $shareManager;
80
-		$this->logger = $logger;
81
-		$this->eventDispatcher = $eventDispatcher;
82
-		$this->cacheFactory = $cacheFactory;
83
-	}
84
-
85
-
86
-	/**
87
-	 * Get all mountpoints applicable for the user and check for shares where we need to update the etags
88
-	 *
89
-	 * @param \OCP\IUser $user
90
-	 * @param \OCP\Files\Storage\IStorageFactory $loader
91
-	 * @return \OCP\Files\Mount\IMountPoint[]
92
-	 */
93
-	public function getMountsForUser(IUser $user, IStorageFactory $loader) {
94
-		$shares = $this->shareManager->getSharedWith($user->getUID(), IShare::TYPE_USER, null, -1);
95
-		$shares = array_merge($shares, $this->shareManager->getSharedWith($user->getUID(), IShare::TYPE_GROUP, null, -1));
96
-		$shares = array_merge($shares, $this->shareManager->getSharedWith($user->getUID(), IShare::TYPE_CIRCLE, null, -1));
97
-		$shares = array_merge($shares, $this->shareManager->getSharedWith($user->getUID(), IShare::TYPE_ROOM, null, -1));
98
-		$shares = array_merge($shares, $this->shareManager->getSharedWith($user->getUID(), IShare::TYPE_DECK, null, -1));
99
-
100
-
101
-		// filter out excluded shares and group shares that includes self
102
-		$shares = array_filter($shares, function (\OCP\Share\IShare $share) use ($user) {
103
-			return $share->getPermissions() > 0 && $share->getShareOwner() !== $user->getUID();
104
-		});
105
-
106
-		$superShares = $this->buildSuperShares($shares, $user);
107
-
108
-		$mounts = [];
109
-		$view = new View('/' . $user->getUID() . '/files');
110
-		$ownerViews = [];
111
-		$sharingDisabledForUser = $this->shareManager->sharingDisabledForUser($user->getUID());
112
-		/** @var CappedMemoryCache<bool> $folderExistCache */
113
-		$foldersExistCache = new CappedMemoryCache();
114
-		foreach ($superShares as $share) {
115
-			try {
116
-				/** @var \OCP\Share\IShare $parentShare */
117
-				$parentShare = $share[0];
118
-
119
-				if ($parentShare->getStatus() !== IShare::STATUS_ACCEPTED &&
120
-					($parentShare->getShareType() === IShare::TYPE_GROUP ||
121
-						$parentShare->getShareType() === IShare::TYPE_USERGROUP ||
122
-						$parentShare->getShareType() === IShare::TYPE_USER)) {
123
-					continue;
124
-				}
125
-
126
-				$owner = $parentShare->getShareOwner();
127
-				if (!isset($ownerViews[$owner])) {
128
-					$ownerViews[$owner] = new View('/' . $parentShare->getShareOwner() . '/files');
129
-				}
130
-				$mount = new SharedMount(
131
-					'\OCA\Files_Sharing\SharedStorage',
132
-					$mounts,
133
-					[
134
-						'user' => $user->getUID(),
135
-						// parent share
136
-						'superShare' => $parentShare,
137
-						// children/component of the superShare
138
-						'groupedShares' => $share[1],
139
-						'ownerView' => $ownerViews[$owner],
140
-						'sharingDisabledForUser' => $sharingDisabledForUser
141
-					],
142
-					$loader,
143
-					$view,
144
-					$foldersExistCache,
145
-					$this->eventDispatcher,
146
-					$user,
147
-					$this->cacheFactory->createLocal('share-valid-mountpoint')
148
-				);
149
-
150
-				$event = new ShareMountedEvent($mount);
151
-				$this->eventDispatcher->dispatchTyped($event);
152
-
153
-				$mounts[$mount->getMountPoint()] = $mount;
154
-				foreach ($event->getAdditionalMounts() as $additionalMount) {
155
-					$mounts[$additionalMount->getMountPoint()] = $additionalMount;
156
-				}
157
-			} catch (\Exception $e) {
158
-				$this->logger->logException($e);
159
-				$this->logger->error('Error while trying to create shared mount');
160
-			}
161
-		}
162
-
163
-		// array_filter removes the null values from the array
164
-		return array_values(array_filter($mounts));
165
-	}
166
-
167
-	/**
168
-	 * Groups shares by path (nodeId) and target path
169
-	 *
170
-	 * @param \OCP\Share\IShare[] $shares
171
-	 * @return \OCP\Share\IShare[][] array of grouped shares, each element in the
172
-	 * array is a group which itself is an array of shares
173
-	 */
174
-	private function groupShares(array $shares) {
175
-		$tmp = [];
176
-
177
-		foreach ($shares as $share) {
178
-			if (!isset($tmp[$share->getNodeId()])) {
179
-				$tmp[$share->getNodeId()] = [];
180
-			}
181
-			$tmp[$share->getNodeId()][] = $share;
182
-		}
183
-
184
-		$result = [];
185
-		// sort by stime, the super share will be based on the least recent share
186
-		foreach ($tmp as &$tmp2) {
187
-			@usort($tmp2, function ($a, $b) {
188
-				$aTime = $a->getShareTime()->getTimestamp();
189
-				$bTime = $b->getShareTime()->getTimestamp();
190
-				if ($aTime === $bTime) {
191
-					return $a->getId() < $b->getId() ? -1 : 1;
192
-				}
193
-				return $aTime < $bTime ? -1 : 1;
194
-			});
195
-			$result[] = $tmp2;
196
-		}
197
-
198
-		return array_values($result);
199
-	}
200
-
201
-	/**
202
-	 * Build super shares (virtual share) by grouping them by node id and target,
203
-	 * then for each group compute the super share and return it along with the matching
204
-	 * grouped shares. The most permissive permissions are used based on the permissions
205
-	 * of all shares within the group.
206
-	 *
207
-	 * @param \OCP\Share\IShare[] $allShares
208
-	 * @param \OCP\IUser $user user
209
-	 * @return array Tuple of [superShare, groupedShares]
210
-	 */
211
-	private function buildSuperShares(array $allShares, \OCP\IUser $user) {
212
-		$result = [];
213
-
214
-		$groupedShares = $this->groupShares($allShares);
215
-
216
-		/** @var \OCP\Share\IShare[] $shares */
217
-		foreach ($groupedShares as $shares) {
218
-			if (count($shares) === 0) {
219
-				continue;
220
-			}
221
-
222
-			$superShare = $this->shareManager->newShare();
223
-
224
-			// compute super share based on first entry of the group
225
-			$superShare->setId($shares[0]->getId())
226
-				->setShareOwner($shares[0]->getShareOwner())
227
-				->setNodeId($shares[0]->getNodeId())
228
-				->setShareType($shares[0]->getShareType())
229
-				->setTarget($shares[0]->getTarget());
230
-
231
-			// use most permissive permissions
232
-			$permissions = 0;
233
-			$status = IShare::STATUS_PENDING;
234
-			foreach ($shares as $share) {
235
-				$permissions |= $share->getPermissions();
236
-				$status = max($status, $share->getStatus());
237
-
238
-				if ($share->getTarget() !== $superShare->getTarget()) {
239
-					// adjust target, for database consistency
240
-					$share->setTarget($superShare->getTarget());
241
-					try {
242
-						$this->shareManager->moveShare($share, $user->getUID());
243
-					} catch (\InvalidArgumentException $e) {
244
-						// ignore as it is not important and we don't want to
245
-						// block FS setup
246
-
247
-						// the subsequent code anyway only uses the target of the
248
-						// super share
249
-
250
-						// such issue can usually happen when dealing with
251
-						// null groups which usually appear with group backend
252
-						// caching inconsistencies
253
-						$this->logger->debug(
254
-							'Could not adjust share target for share ' . $share->getId() . ' to make it consistent: ' . $e->getMessage(),
255
-							['app' => 'files_sharing']
256
-						);
257
-					}
258
-				}
259
-				if (!is_null($share->getNodeCacheEntry())) {
260
-					$superShare->setNodeCacheEntry($share->getNodeCacheEntry());
261
-				}
262
-			}
263
-
264
-			$superShare->setPermissions($permissions)
265
-				->setStatus($status);
266
-
267
-			$result[] = [$superShare, $shares];
268
-		}
269
-
270
-		return $result;
271
-	}
45
+    /**
46
+     * @var \OCP\IConfig
47
+     */
48
+    protected $config;
49
+
50
+    /**
51
+     * @var IManager
52
+     */
53
+    protected $shareManager;
54
+
55
+    /**
56
+     * @var ILogger
57
+     */
58
+    protected $logger;
59
+
60
+    /** @var IEventDispatcher */
61
+    protected $eventDispatcher;
62
+
63
+    /** @var ICacheFactory */
64
+    protected $cacheFactory;
65
+
66
+    /**
67
+     * @param \OCP\IConfig $config
68
+     * @param IManager $shareManager
69
+     * @param ILogger $logger
70
+     */
71
+    public function __construct(
72
+        IConfig $config,
73
+        IManager $shareManager,
74
+        ILogger $logger,
75
+        IEventDispatcher $eventDispatcher,
76
+        ICacheFactory $cacheFactory
77
+    ) {
78
+        $this->config = $config;
79
+        $this->shareManager = $shareManager;
80
+        $this->logger = $logger;
81
+        $this->eventDispatcher = $eventDispatcher;
82
+        $this->cacheFactory = $cacheFactory;
83
+    }
84
+
85
+
86
+    /**
87
+     * Get all mountpoints applicable for the user and check for shares where we need to update the etags
88
+     *
89
+     * @param \OCP\IUser $user
90
+     * @param \OCP\Files\Storage\IStorageFactory $loader
91
+     * @return \OCP\Files\Mount\IMountPoint[]
92
+     */
93
+    public function getMountsForUser(IUser $user, IStorageFactory $loader) {
94
+        $shares = $this->shareManager->getSharedWith($user->getUID(), IShare::TYPE_USER, null, -1);
95
+        $shares = array_merge($shares, $this->shareManager->getSharedWith($user->getUID(), IShare::TYPE_GROUP, null, -1));
96
+        $shares = array_merge($shares, $this->shareManager->getSharedWith($user->getUID(), IShare::TYPE_CIRCLE, null, -1));
97
+        $shares = array_merge($shares, $this->shareManager->getSharedWith($user->getUID(), IShare::TYPE_ROOM, null, -1));
98
+        $shares = array_merge($shares, $this->shareManager->getSharedWith($user->getUID(), IShare::TYPE_DECK, null, -1));
99
+
100
+
101
+        // filter out excluded shares and group shares that includes self
102
+        $shares = array_filter($shares, function (\OCP\Share\IShare $share) use ($user) {
103
+            return $share->getPermissions() > 0 && $share->getShareOwner() !== $user->getUID();
104
+        });
105
+
106
+        $superShares = $this->buildSuperShares($shares, $user);
107
+
108
+        $mounts = [];
109
+        $view = new View('/' . $user->getUID() . '/files');
110
+        $ownerViews = [];
111
+        $sharingDisabledForUser = $this->shareManager->sharingDisabledForUser($user->getUID());
112
+        /** @var CappedMemoryCache<bool> $folderExistCache */
113
+        $foldersExistCache = new CappedMemoryCache();
114
+        foreach ($superShares as $share) {
115
+            try {
116
+                /** @var \OCP\Share\IShare $parentShare */
117
+                $parentShare = $share[0];
118
+
119
+                if ($parentShare->getStatus() !== IShare::STATUS_ACCEPTED &&
120
+                    ($parentShare->getShareType() === IShare::TYPE_GROUP ||
121
+                        $parentShare->getShareType() === IShare::TYPE_USERGROUP ||
122
+                        $parentShare->getShareType() === IShare::TYPE_USER)) {
123
+                    continue;
124
+                }
125
+
126
+                $owner = $parentShare->getShareOwner();
127
+                if (!isset($ownerViews[$owner])) {
128
+                    $ownerViews[$owner] = new View('/' . $parentShare->getShareOwner() . '/files');
129
+                }
130
+                $mount = new SharedMount(
131
+                    '\OCA\Files_Sharing\SharedStorage',
132
+                    $mounts,
133
+                    [
134
+                        'user' => $user->getUID(),
135
+                        // parent share
136
+                        'superShare' => $parentShare,
137
+                        // children/component of the superShare
138
+                        'groupedShares' => $share[1],
139
+                        'ownerView' => $ownerViews[$owner],
140
+                        'sharingDisabledForUser' => $sharingDisabledForUser
141
+                    ],
142
+                    $loader,
143
+                    $view,
144
+                    $foldersExistCache,
145
+                    $this->eventDispatcher,
146
+                    $user,
147
+                    $this->cacheFactory->createLocal('share-valid-mountpoint')
148
+                );
149
+
150
+                $event = new ShareMountedEvent($mount);
151
+                $this->eventDispatcher->dispatchTyped($event);
152
+
153
+                $mounts[$mount->getMountPoint()] = $mount;
154
+                foreach ($event->getAdditionalMounts() as $additionalMount) {
155
+                    $mounts[$additionalMount->getMountPoint()] = $additionalMount;
156
+                }
157
+            } catch (\Exception $e) {
158
+                $this->logger->logException($e);
159
+                $this->logger->error('Error while trying to create shared mount');
160
+            }
161
+        }
162
+
163
+        // array_filter removes the null values from the array
164
+        return array_values(array_filter($mounts));
165
+    }
166
+
167
+    /**
168
+     * Groups shares by path (nodeId) and target path
169
+     *
170
+     * @param \OCP\Share\IShare[] $shares
171
+     * @return \OCP\Share\IShare[][] array of grouped shares, each element in the
172
+     * array is a group which itself is an array of shares
173
+     */
174
+    private function groupShares(array $shares) {
175
+        $tmp = [];
176
+
177
+        foreach ($shares as $share) {
178
+            if (!isset($tmp[$share->getNodeId()])) {
179
+                $tmp[$share->getNodeId()] = [];
180
+            }
181
+            $tmp[$share->getNodeId()][] = $share;
182
+        }
183
+
184
+        $result = [];
185
+        // sort by stime, the super share will be based on the least recent share
186
+        foreach ($tmp as &$tmp2) {
187
+            @usort($tmp2, function ($a, $b) {
188
+                $aTime = $a->getShareTime()->getTimestamp();
189
+                $bTime = $b->getShareTime()->getTimestamp();
190
+                if ($aTime === $bTime) {
191
+                    return $a->getId() < $b->getId() ? -1 : 1;
192
+                }
193
+                return $aTime < $bTime ? -1 : 1;
194
+            });
195
+            $result[] = $tmp2;
196
+        }
197
+
198
+        return array_values($result);
199
+    }
200
+
201
+    /**
202
+     * Build super shares (virtual share) by grouping them by node id and target,
203
+     * then for each group compute the super share and return it along with the matching
204
+     * grouped shares. The most permissive permissions are used based on the permissions
205
+     * of all shares within the group.
206
+     *
207
+     * @param \OCP\Share\IShare[] $allShares
208
+     * @param \OCP\IUser $user user
209
+     * @return array Tuple of [superShare, groupedShares]
210
+     */
211
+    private function buildSuperShares(array $allShares, \OCP\IUser $user) {
212
+        $result = [];
213
+
214
+        $groupedShares = $this->groupShares($allShares);
215
+
216
+        /** @var \OCP\Share\IShare[] $shares */
217
+        foreach ($groupedShares as $shares) {
218
+            if (count($shares) === 0) {
219
+                continue;
220
+            }
221
+
222
+            $superShare = $this->shareManager->newShare();
223
+
224
+            // compute super share based on first entry of the group
225
+            $superShare->setId($shares[0]->getId())
226
+                ->setShareOwner($shares[0]->getShareOwner())
227
+                ->setNodeId($shares[0]->getNodeId())
228
+                ->setShareType($shares[0]->getShareType())
229
+                ->setTarget($shares[0]->getTarget());
230
+
231
+            // use most permissive permissions
232
+            $permissions = 0;
233
+            $status = IShare::STATUS_PENDING;
234
+            foreach ($shares as $share) {
235
+                $permissions |= $share->getPermissions();
236
+                $status = max($status, $share->getStatus());
237
+
238
+                if ($share->getTarget() !== $superShare->getTarget()) {
239
+                    // adjust target, for database consistency
240
+                    $share->setTarget($superShare->getTarget());
241
+                    try {
242
+                        $this->shareManager->moveShare($share, $user->getUID());
243
+                    } catch (\InvalidArgumentException $e) {
244
+                        // ignore as it is not important and we don't want to
245
+                        // block FS setup
246
+
247
+                        // the subsequent code anyway only uses the target of the
248
+                        // super share
249
+
250
+                        // such issue can usually happen when dealing with
251
+                        // null groups which usually appear with group backend
252
+                        // caching inconsistencies
253
+                        $this->logger->debug(
254
+                            'Could not adjust share target for share ' . $share->getId() . ' to make it consistent: ' . $e->getMessage(),
255
+                            ['app' => 'files_sharing']
256
+                        );
257
+                    }
258
+                }
259
+                if (!is_null($share->getNodeCacheEntry())) {
260
+                    $superShare->setNodeCacheEntry($share->getNodeCacheEntry());
261
+                }
262
+            }
263
+
264
+            $superShare->setPermissions($permissions)
265
+                ->setStatus($status);
266
+
267
+            $result[] = [$superShare, $shares];
268
+        }
269
+
270
+        return $result;
271
+    }
272 272
 }
Please login to merge, or discard this patch.
Spacing   +5 added lines, -5 removed lines patch added patch discarded remove patch
@@ -99,14 +99,14 @@  discard block
 block discarded – undo
99 99
 
100 100
 
101 101
 		// filter out excluded shares and group shares that includes self
102
-		$shares = array_filter($shares, function (\OCP\Share\IShare $share) use ($user) {
102
+		$shares = array_filter($shares, function(\OCP\Share\IShare $share) use ($user) {
103 103
 			return $share->getPermissions() > 0 && $share->getShareOwner() !== $user->getUID();
104 104
 		});
105 105
 
106 106
 		$superShares = $this->buildSuperShares($shares, $user);
107 107
 
108 108
 		$mounts = [];
109
-		$view = new View('/' . $user->getUID() . '/files');
109
+		$view = new View('/'.$user->getUID().'/files');
110 110
 		$ownerViews = [];
111 111
 		$sharingDisabledForUser = $this->shareManager->sharingDisabledForUser($user->getUID());
112 112
 		/** @var CappedMemoryCache<bool> $folderExistCache */
@@ -125,7 +125,7 @@  discard block
 block discarded – undo
125 125
 
126 126
 				$owner = $parentShare->getShareOwner();
127 127
 				if (!isset($ownerViews[$owner])) {
128
-					$ownerViews[$owner] = new View('/' . $parentShare->getShareOwner() . '/files');
128
+					$ownerViews[$owner] = new View('/'.$parentShare->getShareOwner().'/files');
129 129
 				}
130 130
 				$mount = new SharedMount(
131 131
 					'\OCA\Files_Sharing\SharedStorage',
@@ -184,7 +184,7 @@  discard block
 block discarded – undo
184 184
 		$result = [];
185 185
 		// sort by stime, the super share will be based on the least recent share
186 186
 		foreach ($tmp as &$tmp2) {
187
-			@usort($tmp2, function ($a, $b) {
187
+			@usort($tmp2, function($a, $b) {
188 188
 				$aTime = $a->getShareTime()->getTimestamp();
189 189
 				$bTime = $b->getShareTime()->getTimestamp();
190 190
 				if ($aTime === $bTime) {
@@ -251,7 +251,7 @@  discard block
 block discarded – undo
251 251
 						// null groups which usually appear with group backend
252 252
 						// caching inconsistencies
253 253
 						$this->logger->debug(
254
-							'Could not adjust share target for share ' . $share->getId() . ' to make it consistent: ' . $e->getMessage(),
254
+							'Could not adjust share target for share '.$share->getId().' to make it consistent: '.$e->getMessage(),
255 255
 							['app' => 'files_sharing']
256 256
 						);
257 257
 					}
Please login to merge, or discard this patch.
apps/workflowengine/lib/Manager.php 2 patches
Indentation   +668 added lines, -668 removed lines patch added patch discarded remove patch
@@ -70,672 +70,672 @@
 block discarded – undo
70 70
 
71 71
 class Manager implements IManager {
72 72
 
73
-	/** @var IStorage */
74
-	protected $storage;
75
-
76
-	/** @var string */
77
-	protected $path;
78
-
79
-	/** @var object */
80
-	protected $entity;
81
-
82
-	/** @var array[] */
83
-	protected $operations = [];
84
-
85
-	/** @var array[] */
86
-	protected $checks = [];
87
-
88
-	/** @var IDBConnection */
89
-	protected $connection;
90
-
91
-	/** @var IServerContainer|\OC\Server */
92
-	protected $container;
93
-
94
-	/** @var IL10N */
95
-	protected $l;
96
-
97
-	/** @var LegacyDispatcher */
98
-	protected $legacyEventDispatcher;
99
-
100
-	/** @var IEntity[] */
101
-	protected $registeredEntities = [];
102
-
103
-	/** @var IOperation[] */
104
-	protected $registeredOperators = [];
105
-
106
-	/** @var ICheck[] */
107
-	protected $registeredChecks = [];
108
-
109
-	/** @var ILogger */
110
-	protected $logger;
111
-
112
-	/** @var CappedMemoryCache<int[]> */
113
-	protected CappedMemoryCache $operationsByScope;
114
-
115
-	/** @var IUserSession */
116
-	protected $session;
117
-
118
-	/** @var IEventDispatcher */
119
-	private $dispatcher;
120
-
121
-	/** @var IConfig */
122
-	private $config;
123
-
124
-	public function __construct(
125
-		IDBConnection $connection,
126
-		IServerContainer $container,
127
-		IL10N $l,
128
-		LegacyDispatcher $eventDispatcher,
129
-		ILogger $logger,
130
-		IUserSession $session,
131
-		IEventDispatcher $dispatcher,
132
-		IConfig $config
133
-	) {
134
-		$this->connection = $connection;
135
-		$this->container = $container;
136
-		$this->l = $l;
137
-		$this->legacyEventDispatcher = $eventDispatcher;
138
-		$this->logger = $logger;
139
-		$this->operationsByScope = new CappedMemoryCache(64);
140
-		$this->session = $session;
141
-		$this->dispatcher = $dispatcher;
142
-		$this->config = $config;
143
-	}
144
-
145
-	public function getRuleMatcher(): IRuleMatcher {
146
-		return new RuleMatcher(
147
-			$this->session,
148
-			$this->container,
149
-			$this->l,
150
-			$this,
151
-			$this->container->query(Logger::class)
152
-		);
153
-	}
154
-
155
-	public function getAllConfiguredEvents() {
156
-		$query = $this->connection->getQueryBuilder();
157
-
158
-		$query->select('class', 'entity')
159
-			->selectAlias($query->expr()->castColumn('events', IQueryBuilder::PARAM_STR), 'events')
160
-			->from('flow_operations')
161
-			->where($query->expr()->neq('events', $query->createNamedParameter('[]'), IQueryBuilder::PARAM_STR))
162
-			->groupBy('class', 'entity', $query->expr()->castColumn('events', IQueryBuilder::PARAM_STR));
163
-
164
-		$result = $query->execute();
165
-		$operations = [];
166
-		while ($row = $result->fetch()) {
167
-			$eventNames = \json_decode($row['events']);
168
-
169
-			$operation = $row['class'];
170
-			$entity = $row['entity'];
171
-
172
-			$operations[$operation] = $operations[$row['class']] ?? [];
173
-			$operations[$operation][$entity] = $operations[$operation][$entity] ?? [];
174
-
175
-			$operations[$operation][$entity] = array_unique(array_merge($operations[$operation][$entity], $eventNames ?? []));
176
-		}
177
-		$result->closeCursor();
178
-
179
-		return $operations;
180
-	}
181
-
182
-	/**
183
-	 * @param string $operationClass
184
-	 * @return ScopeContext[]
185
-	 */
186
-	public function getAllConfiguredScopesForOperation(string $operationClass): array {
187
-		static $scopesByOperation = [];
188
-		if (isset($scopesByOperation[$operationClass])) {
189
-			return $scopesByOperation[$operationClass];
190
-		}
191
-
192
-		$query = $this->connection->getQueryBuilder();
193
-
194
-		$query->selectDistinct('s.type')
195
-			->addSelect('s.value')
196
-			->from('flow_operations', 'o')
197
-			->leftJoin('o', 'flow_operations_scope', 's', $query->expr()->eq('o.id', 's.operation_id'))
198
-			->where($query->expr()->eq('o.class', $query->createParameter('operationClass')));
199
-
200
-		$query->setParameters(['operationClass' => $operationClass]);
201
-		$result = $query->execute();
202
-
203
-		$scopesByOperation[$operationClass] = [];
204
-		while ($row = $result->fetch()) {
205
-			$scope = new ScopeContext($row['type'], $row['value']);
206
-			$scopesByOperation[$operationClass][$scope->getHash()] = $scope;
207
-		}
208
-
209
-		return $scopesByOperation[$operationClass];
210
-	}
211
-
212
-	public function getAllOperations(ScopeContext $scopeContext): array {
213
-		if (isset($this->operations[$scopeContext->getHash()])) {
214
-			return $this->operations[$scopeContext->getHash()];
215
-		}
216
-
217
-		$query = $this->connection->getQueryBuilder();
218
-
219
-		$query->select('o.*')
220
-			->selectAlias('s.type', 'scope_type')
221
-			->selectAlias('s.value', 'scope_actor_id')
222
-			->from('flow_operations', 'o')
223
-			->leftJoin('o', 'flow_operations_scope', 's', $query->expr()->eq('o.id', 's.operation_id'))
224
-			->where($query->expr()->eq('s.type', $query->createParameter('scope')));
225
-
226
-		if ($scopeContext->getScope() === IManager::SCOPE_USER) {
227
-			$query->andWhere($query->expr()->eq('s.value', $query->createParameter('scopeId')));
228
-		}
229
-
230
-		$query->setParameters(['scope' => $scopeContext->getScope(), 'scopeId' => $scopeContext->getScopeId()]);
231
-		$result = $query->execute();
232
-
233
-		$this->operations[$scopeContext->getHash()] = [];
234
-		while ($row = $result->fetch()) {
235
-			if (!isset($this->operations[$scopeContext->getHash()][$row['class']])) {
236
-				$this->operations[$scopeContext->getHash()][$row['class']] = [];
237
-			}
238
-			$this->operations[$scopeContext->getHash()][$row['class']][] = $row;
239
-		}
240
-
241
-		return $this->operations[$scopeContext->getHash()];
242
-	}
243
-
244
-	public function getOperations(string $class, ScopeContext $scopeContext): array {
245
-		if (!isset($this->operations[$scopeContext->getHash()])) {
246
-			$this->getAllOperations($scopeContext);
247
-		}
248
-		return $this->operations[$scopeContext->getHash()][$class] ?? [];
249
-	}
250
-
251
-	/**
252
-	 * @param int $id
253
-	 * @return array
254
-	 * @throws \UnexpectedValueException
255
-	 */
256
-	protected function getOperation($id) {
257
-		$query = $this->connection->getQueryBuilder();
258
-		$query->select('*')
259
-			->from('flow_operations')
260
-			->where($query->expr()->eq('id', $query->createNamedParameter($id)));
261
-		$result = $query->execute();
262
-		$row = $result->fetch();
263
-		$result->closeCursor();
264
-
265
-		if ($row) {
266
-			return $row;
267
-		}
268
-
269
-		throw new \UnexpectedValueException($this->l->t('Operation #%s does not exist', [$id]));
270
-	}
271
-
272
-	protected function insertOperation(
273
-		string $class,
274
-		string $name,
275
-		array $checkIds,
276
-		string $operation,
277
-		string $entity,
278
-		array $events
279
-	): int {
280
-		$query = $this->connection->getQueryBuilder();
281
-		$query->insert('flow_operations')
282
-			->values([
283
-				'class' => $query->createNamedParameter($class),
284
-				'name' => $query->createNamedParameter($name),
285
-				'checks' => $query->createNamedParameter(json_encode(array_unique($checkIds))),
286
-				'operation' => $query->createNamedParameter($operation),
287
-				'entity' => $query->createNamedParameter($entity),
288
-				'events' => $query->createNamedParameter(json_encode($events))
289
-			]);
290
-		$query->execute();
291
-
292
-		return $query->getLastInsertId();
293
-	}
294
-
295
-	/**
296
-	 * @param string $class
297
-	 * @param string $name
298
-	 * @param array[] $checks
299
-	 * @param string $operation
300
-	 * @return array The added operation
301
-	 * @throws \UnexpectedValueException
302
-	 * @throw Exception
303
-	 */
304
-	public function addOperation(
305
-		string $class,
306
-		string $name,
307
-		array $checks,
308
-		string $operation,
309
-		ScopeContext $scope,
310
-		string $entity,
311
-		array $events
312
-	) {
313
-		$this->validateOperation($class, $name, $checks, $operation, $entity, $events);
314
-
315
-		$this->connection->beginTransaction();
316
-
317
-		try {
318
-			$checkIds = [];
319
-			foreach ($checks as $check) {
320
-				$checkIds[] = $this->addCheck($check['class'], $check['operator'], $check['value']);
321
-			}
322
-
323
-			$id = $this->insertOperation($class, $name, $checkIds, $operation, $entity, $events);
324
-			$this->addScope($id, $scope);
325
-
326
-			$this->connection->commit();
327
-		} catch (Exception $e) {
328
-			$this->connection->rollBack();
329
-			throw $e;
330
-		}
331
-
332
-		return $this->getOperation($id);
333
-	}
334
-
335
-	protected function canModify(int $id, ScopeContext $scopeContext):bool {
336
-		if (isset($this->operationsByScope[$scopeContext->getHash()])) {
337
-			return in_array($id, $this->operationsByScope[$scopeContext->getHash()], true);
338
-		}
339
-
340
-		$qb = $this->connection->getQueryBuilder();
341
-		$qb = $qb->select('o.id')
342
-			->from('flow_operations', 'o')
343
-			->leftJoin('o', 'flow_operations_scope', 's', $qb->expr()->eq('o.id', 's.operation_id'))
344
-			->where($qb->expr()->eq('s.type', $qb->createParameter('scope')));
345
-
346
-		if ($scopeContext->getScope() !== IManager::SCOPE_ADMIN) {
347
-			$qb->where($qb->expr()->eq('s.value', $qb->createParameter('scopeId')));
348
-		}
349
-
350
-		$qb->setParameters(['scope' => $scopeContext->getScope(), 'scopeId' => $scopeContext->getScopeId()]);
351
-		$result = $qb->execute();
352
-
353
-		$operations = [];
354
-		while (($opId = $result->fetchOne()) !== false) {
355
-			$operations[] = (int)$opId;
356
-		}
357
-		$this->operationsByScope[$scopeContext->getHash()] = $operations;
358
-		$result->closeCursor();
359
-
360
-		return in_array($id, $this->operationsByScope[$scopeContext->getHash()], true);
361
-	}
362
-
363
-	/**
364
-	 * @param int $id
365
-	 * @param string $name
366
-	 * @param array[] $checks
367
-	 * @param string $operation
368
-	 * @return array The updated operation
369
-	 * @throws \UnexpectedValueException
370
-	 * @throws \DomainException
371
-	 * @throws Exception
372
-	 */
373
-	public function updateOperation(
374
-		int $id,
375
-		string $name,
376
-		array $checks,
377
-		string $operation,
378
-		ScopeContext $scopeContext,
379
-		string $entity,
380
-		array $events
381
-	): array {
382
-		if (!$this->canModify($id, $scopeContext)) {
383
-			throw new \DomainException('Target operation not within scope');
384
-		};
385
-		$row = $this->getOperation($id);
386
-		$this->validateOperation($row['class'], $name, $checks, $operation, $entity, $events);
387
-
388
-		$checkIds = [];
389
-		try {
390
-			$this->connection->beginTransaction();
391
-			foreach ($checks as $check) {
392
-				$checkIds[] = $this->addCheck($check['class'], $check['operator'], $check['value']);
393
-			}
394
-
395
-			$query = $this->connection->getQueryBuilder();
396
-			$query->update('flow_operations')
397
-				->set('name', $query->createNamedParameter($name))
398
-				->set('checks', $query->createNamedParameter(json_encode(array_unique($checkIds))))
399
-				->set('operation', $query->createNamedParameter($operation))
400
-				->set('entity', $query->createNamedParameter($entity))
401
-				->set('events', $query->createNamedParameter(json_encode($events)))
402
-				->where($query->expr()->eq('id', $query->createNamedParameter($id)));
403
-			$query->execute();
404
-			$this->connection->commit();
405
-		} catch (Exception $e) {
406
-			$this->connection->rollBack();
407
-			throw $e;
408
-		}
409
-		unset($this->operations[$scopeContext->getHash()]);
410
-
411
-		return $this->getOperation($id);
412
-	}
413
-
414
-	/**
415
-	 * @param int $id
416
-	 * @return bool
417
-	 * @throws \UnexpectedValueException
418
-	 * @throws Exception
419
-	 * @throws \DomainException
420
-	 */
421
-	public function deleteOperation($id, ScopeContext $scopeContext) {
422
-		if (!$this->canModify($id, $scopeContext)) {
423
-			throw new \DomainException('Target operation not within scope');
424
-		};
425
-		$query = $this->connection->getQueryBuilder();
426
-		try {
427
-			$this->connection->beginTransaction();
428
-			$result = (bool)$query->delete('flow_operations')
429
-				->where($query->expr()->eq('id', $query->createNamedParameter($id)))
430
-				->execute();
431
-			if ($result) {
432
-				$qb = $this->connection->getQueryBuilder();
433
-				$result &= (bool)$qb->delete('flow_operations_scope')
434
-					->where($qb->expr()->eq('operation_id', $qb->createNamedParameter($id)))
435
-					->execute();
436
-			}
437
-			$this->connection->commit();
438
-		} catch (Exception $e) {
439
-			$this->connection->rollBack();
440
-			throw $e;
441
-		}
442
-
443
-		if (isset($this->operations[$scopeContext->getHash()])) {
444
-			unset($this->operations[$scopeContext->getHash()]);
445
-		}
446
-
447
-		return $result;
448
-	}
449
-
450
-	protected function validateEvents(string $entity, array $events, IOperation $operation) {
451
-		try {
452
-			/** @var IEntity $instance */
453
-			$instance = $this->container->query($entity);
454
-		} catch (QueryException $e) {
455
-			throw new \UnexpectedValueException($this->l->t('Entity %s does not exist', [$entity]));
456
-		}
457
-
458
-		if (!$instance instanceof IEntity) {
459
-			throw new \UnexpectedValueException($this->l->t('Entity %s is invalid', [$entity]));
460
-		}
461
-
462
-		if (empty($events)) {
463
-			if (!$operation instanceof IComplexOperation) {
464
-				throw new \UnexpectedValueException($this->l->t('No events are chosen.'));
465
-			}
466
-			return;
467
-		}
468
-
469
-		$availableEvents = [];
470
-		foreach ($instance->getEvents() as $event) {
471
-			/** @var IEntityEvent $event */
472
-			$availableEvents[] = $event->getEventName();
473
-		}
474
-
475
-		$diff = array_diff($events, $availableEvents);
476
-		if (!empty($diff)) {
477
-			throw new \UnexpectedValueException($this->l->t('Entity %s has no event %s', [$entity, array_shift($diff)]));
478
-		}
479
-	}
480
-
481
-	/**
482
-	 * @param string $class
483
-	 * @param string $name
484
-	 * @param array[] $checks
485
-	 * @param string $operation
486
-	 * @throws \UnexpectedValueException
487
-	 */
488
-	public function validateOperation($class, $name, array $checks, $operation, string $entity, array $events) {
489
-		try {
490
-			/** @var IOperation $instance */
491
-			$instance = $this->container->query($class);
492
-		} catch (QueryException $e) {
493
-			throw new \UnexpectedValueException($this->l->t('Operation %s does not exist', [$class]));
494
-		}
495
-
496
-		if (!($instance instanceof IOperation)) {
497
-			throw new \UnexpectedValueException($this->l->t('Operation %s is invalid', [$class]));
498
-		}
499
-
500
-		$this->validateEvents($entity, $events, $instance);
501
-
502
-		if (count($checks) === 0) {
503
-			throw new \UnexpectedValueException($this->l->t('At least one check needs to be provided'));
504
-		}
505
-
506
-		if (strlen((string)$operation) > IManager::MAX_OPERATION_VALUE_BYTES) {
507
-			throw new \UnexpectedValueException($this->l->t('The provided operation data is too long'));
508
-		}
509
-
510
-		$instance->validateOperation($name, $checks, $operation);
511
-
512
-		foreach ($checks as $check) {
513
-			if (!is_string($check['class'])) {
514
-				throw new \UnexpectedValueException($this->l->t('Invalid check provided'));
515
-			}
516
-
517
-			try {
518
-				/** @var ICheck $instance */
519
-				$instance = $this->container->query($check['class']);
520
-			} catch (QueryException $e) {
521
-				throw new \UnexpectedValueException($this->l->t('Check %s does not exist', [$class]));
522
-			}
523
-
524
-			if (!($instance instanceof ICheck)) {
525
-				throw new \UnexpectedValueException($this->l->t('Check %s is invalid', [$class]));
526
-			}
527
-
528
-			if (!empty($instance->supportedEntities())
529
-				&& !in_array($entity, $instance->supportedEntities())
530
-			) {
531
-				throw new \UnexpectedValueException($this->l->t('Check %s is not allowed with this entity', [$class]));
532
-			}
533
-
534
-			if (strlen((string)$check['value']) > IManager::MAX_CHECK_VALUE_BYTES) {
535
-				throw new \UnexpectedValueException($this->l->t('The provided check value is too long'));
536
-			}
537
-
538
-			$instance->validateCheck($check['operator'], $check['value']);
539
-		}
540
-	}
541
-
542
-	/**
543
-	 * @param int[] $checkIds
544
-	 * @return array[]
545
-	 */
546
-	public function getChecks(array $checkIds) {
547
-		$checkIds = array_map('intval', $checkIds);
548
-
549
-		$checks = [];
550
-		foreach ($checkIds as $i => $checkId) {
551
-			if (isset($this->checks[$checkId])) {
552
-				$checks[$checkId] = $this->checks[$checkId];
553
-				unset($checkIds[$i]);
554
-			}
555
-		}
556
-
557
-		if (empty($checkIds)) {
558
-			return $checks;
559
-		}
560
-
561
-		$query = $this->connection->getQueryBuilder();
562
-		$query->select('*')
563
-			->from('flow_checks')
564
-			->where($query->expr()->in('id', $query->createNamedParameter($checkIds, IQueryBuilder::PARAM_INT_ARRAY)));
565
-		$result = $query->execute();
566
-
567
-		while ($row = $result->fetch()) {
568
-			$this->checks[(int) $row['id']] = $row;
569
-			$checks[(int) $row['id']] = $row;
570
-		}
571
-		$result->closeCursor();
572
-
573
-		$checkIds = array_diff($checkIds, array_keys($checks));
574
-
575
-		if (!empty($checkIds)) {
576
-			$missingCheck = array_pop($checkIds);
577
-			throw new \UnexpectedValueException($this->l->t('Check #%s does not exist', $missingCheck));
578
-		}
579
-
580
-		return $checks;
581
-	}
582
-
583
-	/**
584
-	 * @param string $class
585
-	 * @param string $operator
586
-	 * @param string $value
587
-	 * @return int Check unique ID
588
-	 */
589
-	protected function addCheck($class, $operator, $value) {
590
-		$hash = md5($class . '::' . $operator . '::' . $value);
591
-
592
-		$query = $this->connection->getQueryBuilder();
593
-		$query->select('id')
594
-			->from('flow_checks')
595
-			->where($query->expr()->eq('hash', $query->createNamedParameter($hash)));
596
-		$result = $query->execute();
597
-
598
-		if ($row = $result->fetch()) {
599
-			$result->closeCursor();
600
-			return (int) $row['id'];
601
-		}
602
-
603
-		$query = $this->connection->getQueryBuilder();
604
-		$query->insert('flow_checks')
605
-			->values([
606
-				'class' => $query->createNamedParameter($class),
607
-				'operator' => $query->createNamedParameter($operator),
608
-				'value' => $query->createNamedParameter($value),
609
-				'hash' => $query->createNamedParameter($hash),
610
-			]);
611
-		$query->execute();
612
-
613
-		return $query->getLastInsertId();
614
-	}
615
-
616
-	protected function addScope(int $operationId, ScopeContext $scope): void {
617
-		$query = $this->connection->getQueryBuilder();
618
-
619
-		$insertQuery = $query->insert('flow_operations_scope');
620
-		$insertQuery->values([
621
-			'operation_id' => $query->createNamedParameter($operationId),
622
-			'type' => $query->createNamedParameter($scope->getScope()),
623
-			'value' => $query->createNamedParameter($scope->getScopeId()),
624
-		]);
625
-		$insertQuery->execute();
626
-	}
627
-
628
-	public function formatOperation(array $operation): array {
629
-		$checkIds = json_decode($operation['checks'], true);
630
-		$checks = $this->getChecks($checkIds);
631
-
632
-		$operation['checks'] = [];
633
-		foreach ($checks as $check) {
634
-			// Remove internal values
635
-			unset($check['id']);
636
-			unset($check['hash']);
637
-
638
-			$operation['checks'][] = $check;
639
-		}
640
-		$operation['events'] = json_decode($operation['events'], true) ?? [];
641
-
642
-
643
-		return $operation;
644
-	}
645
-
646
-	/**
647
-	 * @return IEntity[]
648
-	 */
649
-	public function getEntitiesList(): array {
650
-		$this->dispatcher->dispatchTyped(new RegisterEntitiesEvent($this));
651
-		$this->legacyEventDispatcher->dispatch(IManager::EVENT_NAME_REG_ENTITY, new GenericEvent($this));
652
-
653
-		return array_values(array_merge($this->getBuildInEntities(), $this->registeredEntities));
654
-	}
655
-
656
-	/**
657
-	 * @return IOperation[]
658
-	 */
659
-	public function getOperatorList(): array {
660
-		$this->dispatcher->dispatchTyped(new RegisterOperationsEvent($this));
661
-		$this->legacyEventDispatcher->dispatch(IManager::EVENT_NAME_REG_OPERATION, new GenericEvent($this));
662
-
663
-		return array_merge($this->getBuildInOperators(), $this->registeredOperators);
664
-	}
665
-
666
-	/**
667
-	 * @return ICheck[]
668
-	 */
669
-	public function getCheckList(): array {
670
-		$this->dispatcher->dispatchTyped(new RegisterChecksEvent($this));
671
-		$this->legacyEventDispatcher->dispatch(IManager::EVENT_NAME_REG_CHECK, new GenericEvent($this));
672
-
673
-		return array_merge($this->getBuildInChecks(), $this->registeredChecks);
674
-	}
675
-
676
-	public function registerEntity(IEntity $entity): void {
677
-		$this->registeredEntities[get_class($entity)] = $entity;
678
-	}
679
-
680
-	public function registerOperation(IOperation $operator): void {
681
-		$this->registeredOperators[get_class($operator)] = $operator;
682
-	}
683
-
684
-	public function registerCheck(ICheck $check): void {
685
-		$this->registeredChecks[get_class($check)] = $check;
686
-	}
687
-
688
-	/**
689
-	 * @return IEntity[]
690
-	 */
691
-	protected function getBuildInEntities(): array {
692
-		try {
693
-			return [
694
-				File::class => $this->container->query(File::class),
695
-			];
696
-		} catch (QueryException $e) {
697
-			$this->logger->logException($e);
698
-			return [];
699
-		}
700
-	}
701
-
702
-	/**
703
-	 * @return IOperation[]
704
-	 */
705
-	protected function getBuildInOperators(): array {
706
-		try {
707
-			return [
708
-				// None yet
709
-			];
710
-		} catch (QueryException $e) {
711
-			$this->logger->logException($e);
712
-			return [];
713
-		}
714
-	}
715
-
716
-	/**
717
-	 * @return ICheck[]
718
-	 */
719
-	protected function getBuildInChecks(): array {
720
-		try {
721
-			return [
722
-				$this->container->query(FileMimeType::class),
723
-				$this->container->query(FileName::class),
724
-				$this->container->query(FileSize::class),
725
-				$this->container->query(FileSystemTags::class),
726
-				$this->container->query(RequestRemoteAddress::class),
727
-				$this->container->query(RequestTime::class),
728
-				$this->container->query(RequestURL::class),
729
-				$this->container->query(RequestUserAgent::class),
730
-				$this->container->query(UserGroupMembership::class),
731
-			];
732
-		} catch (QueryException $e) {
733
-			$this->logger->logException($e);
734
-			return [];
735
-		}
736
-	}
737
-
738
-	public function isUserScopeEnabled(): bool {
739
-		return $this->config->getAppValue(Application::APP_ID, 'user_scope_disabled', 'no') === 'no';
740
-	}
73
+    /** @var IStorage */
74
+    protected $storage;
75
+
76
+    /** @var string */
77
+    protected $path;
78
+
79
+    /** @var object */
80
+    protected $entity;
81
+
82
+    /** @var array[] */
83
+    protected $operations = [];
84
+
85
+    /** @var array[] */
86
+    protected $checks = [];
87
+
88
+    /** @var IDBConnection */
89
+    protected $connection;
90
+
91
+    /** @var IServerContainer|\OC\Server */
92
+    protected $container;
93
+
94
+    /** @var IL10N */
95
+    protected $l;
96
+
97
+    /** @var LegacyDispatcher */
98
+    protected $legacyEventDispatcher;
99
+
100
+    /** @var IEntity[] */
101
+    protected $registeredEntities = [];
102
+
103
+    /** @var IOperation[] */
104
+    protected $registeredOperators = [];
105
+
106
+    /** @var ICheck[] */
107
+    protected $registeredChecks = [];
108
+
109
+    /** @var ILogger */
110
+    protected $logger;
111
+
112
+    /** @var CappedMemoryCache<int[]> */
113
+    protected CappedMemoryCache $operationsByScope;
114
+
115
+    /** @var IUserSession */
116
+    protected $session;
117
+
118
+    /** @var IEventDispatcher */
119
+    private $dispatcher;
120
+
121
+    /** @var IConfig */
122
+    private $config;
123
+
124
+    public function __construct(
125
+        IDBConnection $connection,
126
+        IServerContainer $container,
127
+        IL10N $l,
128
+        LegacyDispatcher $eventDispatcher,
129
+        ILogger $logger,
130
+        IUserSession $session,
131
+        IEventDispatcher $dispatcher,
132
+        IConfig $config
133
+    ) {
134
+        $this->connection = $connection;
135
+        $this->container = $container;
136
+        $this->l = $l;
137
+        $this->legacyEventDispatcher = $eventDispatcher;
138
+        $this->logger = $logger;
139
+        $this->operationsByScope = new CappedMemoryCache(64);
140
+        $this->session = $session;
141
+        $this->dispatcher = $dispatcher;
142
+        $this->config = $config;
143
+    }
144
+
145
+    public function getRuleMatcher(): IRuleMatcher {
146
+        return new RuleMatcher(
147
+            $this->session,
148
+            $this->container,
149
+            $this->l,
150
+            $this,
151
+            $this->container->query(Logger::class)
152
+        );
153
+    }
154
+
155
+    public function getAllConfiguredEvents() {
156
+        $query = $this->connection->getQueryBuilder();
157
+
158
+        $query->select('class', 'entity')
159
+            ->selectAlias($query->expr()->castColumn('events', IQueryBuilder::PARAM_STR), 'events')
160
+            ->from('flow_operations')
161
+            ->where($query->expr()->neq('events', $query->createNamedParameter('[]'), IQueryBuilder::PARAM_STR))
162
+            ->groupBy('class', 'entity', $query->expr()->castColumn('events', IQueryBuilder::PARAM_STR));
163
+
164
+        $result = $query->execute();
165
+        $operations = [];
166
+        while ($row = $result->fetch()) {
167
+            $eventNames = \json_decode($row['events']);
168
+
169
+            $operation = $row['class'];
170
+            $entity = $row['entity'];
171
+
172
+            $operations[$operation] = $operations[$row['class']] ?? [];
173
+            $operations[$operation][$entity] = $operations[$operation][$entity] ?? [];
174
+
175
+            $operations[$operation][$entity] = array_unique(array_merge($operations[$operation][$entity], $eventNames ?? []));
176
+        }
177
+        $result->closeCursor();
178
+
179
+        return $operations;
180
+    }
181
+
182
+    /**
183
+     * @param string $operationClass
184
+     * @return ScopeContext[]
185
+     */
186
+    public function getAllConfiguredScopesForOperation(string $operationClass): array {
187
+        static $scopesByOperation = [];
188
+        if (isset($scopesByOperation[$operationClass])) {
189
+            return $scopesByOperation[$operationClass];
190
+        }
191
+
192
+        $query = $this->connection->getQueryBuilder();
193
+
194
+        $query->selectDistinct('s.type')
195
+            ->addSelect('s.value')
196
+            ->from('flow_operations', 'o')
197
+            ->leftJoin('o', 'flow_operations_scope', 's', $query->expr()->eq('o.id', 's.operation_id'))
198
+            ->where($query->expr()->eq('o.class', $query->createParameter('operationClass')));
199
+
200
+        $query->setParameters(['operationClass' => $operationClass]);
201
+        $result = $query->execute();
202
+
203
+        $scopesByOperation[$operationClass] = [];
204
+        while ($row = $result->fetch()) {
205
+            $scope = new ScopeContext($row['type'], $row['value']);
206
+            $scopesByOperation[$operationClass][$scope->getHash()] = $scope;
207
+        }
208
+
209
+        return $scopesByOperation[$operationClass];
210
+    }
211
+
212
+    public function getAllOperations(ScopeContext $scopeContext): array {
213
+        if (isset($this->operations[$scopeContext->getHash()])) {
214
+            return $this->operations[$scopeContext->getHash()];
215
+        }
216
+
217
+        $query = $this->connection->getQueryBuilder();
218
+
219
+        $query->select('o.*')
220
+            ->selectAlias('s.type', 'scope_type')
221
+            ->selectAlias('s.value', 'scope_actor_id')
222
+            ->from('flow_operations', 'o')
223
+            ->leftJoin('o', 'flow_operations_scope', 's', $query->expr()->eq('o.id', 's.operation_id'))
224
+            ->where($query->expr()->eq('s.type', $query->createParameter('scope')));
225
+
226
+        if ($scopeContext->getScope() === IManager::SCOPE_USER) {
227
+            $query->andWhere($query->expr()->eq('s.value', $query->createParameter('scopeId')));
228
+        }
229
+
230
+        $query->setParameters(['scope' => $scopeContext->getScope(), 'scopeId' => $scopeContext->getScopeId()]);
231
+        $result = $query->execute();
232
+
233
+        $this->operations[$scopeContext->getHash()] = [];
234
+        while ($row = $result->fetch()) {
235
+            if (!isset($this->operations[$scopeContext->getHash()][$row['class']])) {
236
+                $this->operations[$scopeContext->getHash()][$row['class']] = [];
237
+            }
238
+            $this->operations[$scopeContext->getHash()][$row['class']][] = $row;
239
+        }
240
+
241
+        return $this->operations[$scopeContext->getHash()];
242
+    }
243
+
244
+    public function getOperations(string $class, ScopeContext $scopeContext): array {
245
+        if (!isset($this->operations[$scopeContext->getHash()])) {
246
+            $this->getAllOperations($scopeContext);
247
+        }
248
+        return $this->operations[$scopeContext->getHash()][$class] ?? [];
249
+    }
250
+
251
+    /**
252
+     * @param int $id
253
+     * @return array
254
+     * @throws \UnexpectedValueException
255
+     */
256
+    protected function getOperation($id) {
257
+        $query = $this->connection->getQueryBuilder();
258
+        $query->select('*')
259
+            ->from('flow_operations')
260
+            ->where($query->expr()->eq('id', $query->createNamedParameter($id)));
261
+        $result = $query->execute();
262
+        $row = $result->fetch();
263
+        $result->closeCursor();
264
+
265
+        if ($row) {
266
+            return $row;
267
+        }
268
+
269
+        throw new \UnexpectedValueException($this->l->t('Operation #%s does not exist', [$id]));
270
+    }
271
+
272
+    protected function insertOperation(
273
+        string $class,
274
+        string $name,
275
+        array $checkIds,
276
+        string $operation,
277
+        string $entity,
278
+        array $events
279
+    ): int {
280
+        $query = $this->connection->getQueryBuilder();
281
+        $query->insert('flow_operations')
282
+            ->values([
283
+                'class' => $query->createNamedParameter($class),
284
+                'name' => $query->createNamedParameter($name),
285
+                'checks' => $query->createNamedParameter(json_encode(array_unique($checkIds))),
286
+                'operation' => $query->createNamedParameter($operation),
287
+                'entity' => $query->createNamedParameter($entity),
288
+                'events' => $query->createNamedParameter(json_encode($events))
289
+            ]);
290
+        $query->execute();
291
+
292
+        return $query->getLastInsertId();
293
+    }
294
+
295
+    /**
296
+     * @param string $class
297
+     * @param string $name
298
+     * @param array[] $checks
299
+     * @param string $operation
300
+     * @return array The added operation
301
+     * @throws \UnexpectedValueException
302
+     * @throw Exception
303
+     */
304
+    public function addOperation(
305
+        string $class,
306
+        string $name,
307
+        array $checks,
308
+        string $operation,
309
+        ScopeContext $scope,
310
+        string $entity,
311
+        array $events
312
+    ) {
313
+        $this->validateOperation($class, $name, $checks, $operation, $entity, $events);
314
+
315
+        $this->connection->beginTransaction();
316
+
317
+        try {
318
+            $checkIds = [];
319
+            foreach ($checks as $check) {
320
+                $checkIds[] = $this->addCheck($check['class'], $check['operator'], $check['value']);
321
+            }
322
+
323
+            $id = $this->insertOperation($class, $name, $checkIds, $operation, $entity, $events);
324
+            $this->addScope($id, $scope);
325
+
326
+            $this->connection->commit();
327
+        } catch (Exception $e) {
328
+            $this->connection->rollBack();
329
+            throw $e;
330
+        }
331
+
332
+        return $this->getOperation($id);
333
+    }
334
+
335
+    protected function canModify(int $id, ScopeContext $scopeContext):bool {
336
+        if (isset($this->operationsByScope[$scopeContext->getHash()])) {
337
+            return in_array($id, $this->operationsByScope[$scopeContext->getHash()], true);
338
+        }
339
+
340
+        $qb = $this->connection->getQueryBuilder();
341
+        $qb = $qb->select('o.id')
342
+            ->from('flow_operations', 'o')
343
+            ->leftJoin('o', 'flow_operations_scope', 's', $qb->expr()->eq('o.id', 's.operation_id'))
344
+            ->where($qb->expr()->eq('s.type', $qb->createParameter('scope')));
345
+
346
+        if ($scopeContext->getScope() !== IManager::SCOPE_ADMIN) {
347
+            $qb->where($qb->expr()->eq('s.value', $qb->createParameter('scopeId')));
348
+        }
349
+
350
+        $qb->setParameters(['scope' => $scopeContext->getScope(), 'scopeId' => $scopeContext->getScopeId()]);
351
+        $result = $qb->execute();
352
+
353
+        $operations = [];
354
+        while (($opId = $result->fetchOne()) !== false) {
355
+            $operations[] = (int)$opId;
356
+        }
357
+        $this->operationsByScope[$scopeContext->getHash()] = $operations;
358
+        $result->closeCursor();
359
+
360
+        return in_array($id, $this->operationsByScope[$scopeContext->getHash()], true);
361
+    }
362
+
363
+    /**
364
+     * @param int $id
365
+     * @param string $name
366
+     * @param array[] $checks
367
+     * @param string $operation
368
+     * @return array The updated operation
369
+     * @throws \UnexpectedValueException
370
+     * @throws \DomainException
371
+     * @throws Exception
372
+     */
373
+    public function updateOperation(
374
+        int $id,
375
+        string $name,
376
+        array $checks,
377
+        string $operation,
378
+        ScopeContext $scopeContext,
379
+        string $entity,
380
+        array $events
381
+    ): array {
382
+        if (!$this->canModify($id, $scopeContext)) {
383
+            throw new \DomainException('Target operation not within scope');
384
+        };
385
+        $row = $this->getOperation($id);
386
+        $this->validateOperation($row['class'], $name, $checks, $operation, $entity, $events);
387
+
388
+        $checkIds = [];
389
+        try {
390
+            $this->connection->beginTransaction();
391
+            foreach ($checks as $check) {
392
+                $checkIds[] = $this->addCheck($check['class'], $check['operator'], $check['value']);
393
+            }
394
+
395
+            $query = $this->connection->getQueryBuilder();
396
+            $query->update('flow_operations')
397
+                ->set('name', $query->createNamedParameter($name))
398
+                ->set('checks', $query->createNamedParameter(json_encode(array_unique($checkIds))))
399
+                ->set('operation', $query->createNamedParameter($operation))
400
+                ->set('entity', $query->createNamedParameter($entity))
401
+                ->set('events', $query->createNamedParameter(json_encode($events)))
402
+                ->where($query->expr()->eq('id', $query->createNamedParameter($id)));
403
+            $query->execute();
404
+            $this->connection->commit();
405
+        } catch (Exception $e) {
406
+            $this->connection->rollBack();
407
+            throw $e;
408
+        }
409
+        unset($this->operations[$scopeContext->getHash()]);
410
+
411
+        return $this->getOperation($id);
412
+    }
413
+
414
+    /**
415
+     * @param int $id
416
+     * @return bool
417
+     * @throws \UnexpectedValueException
418
+     * @throws Exception
419
+     * @throws \DomainException
420
+     */
421
+    public function deleteOperation($id, ScopeContext $scopeContext) {
422
+        if (!$this->canModify($id, $scopeContext)) {
423
+            throw new \DomainException('Target operation not within scope');
424
+        };
425
+        $query = $this->connection->getQueryBuilder();
426
+        try {
427
+            $this->connection->beginTransaction();
428
+            $result = (bool)$query->delete('flow_operations')
429
+                ->where($query->expr()->eq('id', $query->createNamedParameter($id)))
430
+                ->execute();
431
+            if ($result) {
432
+                $qb = $this->connection->getQueryBuilder();
433
+                $result &= (bool)$qb->delete('flow_operations_scope')
434
+                    ->where($qb->expr()->eq('operation_id', $qb->createNamedParameter($id)))
435
+                    ->execute();
436
+            }
437
+            $this->connection->commit();
438
+        } catch (Exception $e) {
439
+            $this->connection->rollBack();
440
+            throw $e;
441
+        }
442
+
443
+        if (isset($this->operations[$scopeContext->getHash()])) {
444
+            unset($this->operations[$scopeContext->getHash()]);
445
+        }
446
+
447
+        return $result;
448
+    }
449
+
450
+    protected function validateEvents(string $entity, array $events, IOperation $operation) {
451
+        try {
452
+            /** @var IEntity $instance */
453
+            $instance = $this->container->query($entity);
454
+        } catch (QueryException $e) {
455
+            throw new \UnexpectedValueException($this->l->t('Entity %s does not exist', [$entity]));
456
+        }
457
+
458
+        if (!$instance instanceof IEntity) {
459
+            throw new \UnexpectedValueException($this->l->t('Entity %s is invalid', [$entity]));
460
+        }
461
+
462
+        if (empty($events)) {
463
+            if (!$operation instanceof IComplexOperation) {
464
+                throw new \UnexpectedValueException($this->l->t('No events are chosen.'));
465
+            }
466
+            return;
467
+        }
468
+
469
+        $availableEvents = [];
470
+        foreach ($instance->getEvents() as $event) {
471
+            /** @var IEntityEvent $event */
472
+            $availableEvents[] = $event->getEventName();
473
+        }
474
+
475
+        $diff = array_diff($events, $availableEvents);
476
+        if (!empty($diff)) {
477
+            throw new \UnexpectedValueException($this->l->t('Entity %s has no event %s', [$entity, array_shift($diff)]));
478
+        }
479
+    }
480
+
481
+    /**
482
+     * @param string $class
483
+     * @param string $name
484
+     * @param array[] $checks
485
+     * @param string $operation
486
+     * @throws \UnexpectedValueException
487
+     */
488
+    public function validateOperation($class, $name, array $checks, $operation, string $entity, array $events) {
489
+        try {
490
+            /** @var IOperation $instance */
491
+            $instance = $this->container->query($class);
492
+        } catch (QueryException $e) {
493
+            throw new \UnexpectedValueException($this->l->t('Operation %s does not exist', [$class]));
494
+        }
495
+
496
+        if (!($instance instanceof IOperation)) {
497
+            throw new \UnexpectedValueException($this->l->t('Operation %s is invalid', [$class]));
498
+        }
499
+
500
+        $this->validateEvents($entity, $events, $instance);
501
+
502
+        if (count($checks) === 0) {
503
+            throw new \UnexpectedValueException($this->l->t('At least one check needs to be provided'));
504
+        }
505
+
506
+        if (strlen((string)$operation) > IManager::MAX_OPERATION_VALUE_BYTES) {
507
+            throw new \UnexpectedValueException($this->l->t('The provided operation data is too long'));
508
+        }
509
+
510
+        $instance->validateOperation($name, $checks, $operation);
511
+
512
+        foreach ($checks as $check) {
513
+            if (!is_string($check['class'])) {
514
+                throw new \UnexpectedValueException($this->l->t('Invalid check provided'));
515
+            }
516
+
517
+            try {
518
+                /** @var ICheck $instance */
519
+                $instance = $this->container->query($check['class']);
520
+            } catch (QueryException $e) {
521
+                throw new \UnexpectedValueException($this->l->t('Check %s does not exist', [$class]));
522
+            }
523
+
524
+            if (!($instance instanceof ICheck)) {
525
+                throw new \UnexpectedValueException($this->l->t('Check %s is invalid', [$class]));
526
+            }
527
+
528
+            if (!empty($instance->supportedEntities())
529
+                && !in_array($entity, $instance->supportedEntities())
530
+            ) {
531
+                throw new \UnexpectedValueException($this->l->t('Check %s is not allowed with this entity', [$class]));
532
+            }
533
+
534
+            if (strlen((string)$check['value']) > IManager::MAX_CHECK_VALUE_BYTES) {
535
+                throw new \UnexpectedValueException($this->l->t('The provided check value is too long'));
536
+            }
537
+
538
+            $instance->validateCheck($check['operator'], $check['value']);
539
+        }
540
+    }
541
+
542
+    /**
543
+     * @param int[] $checkIds
544
+     * @return array[]
545
+     */
546
+    public function getChecks(array $checkIds) {
547
+        $checkIds = array_map('intval', $checkIds);
548
+
549
+        $checks = [];
550
+        foreach ($checkIds as $i => $checkId) {
551
+            if (isset($this->checks[$checkId])) {
552
+                $checks[$checkId] = $this->checks[$checkId];
553
+                unset($checkIds[$i]);
554
+            }
555
+        }
556
+
557
+        if (empty($checkIds)) {
558
+            return $checks;
559
+        }
560
+
561
+        $query = $this->connection->getQueryBuilder();
562
+        $query->select('*')
563
+            ->from('flow_checks')
564
+            ->where($query->expr()->in('id', $query->createNamedParameter($checkIds, IQueryBuilder::PARAM_INT_ARRAY)));
565
+        $result = $query->execute();
566
+
567
+        while ($row = $result->fetch()) {
568
+            $this->checks[(int) $row['id']] = $row;
569
+            $checks[(int) $row['id']] = $row;
570
+        }
571
+        $result->closeCursor();
572
+
573
+        $checkIds = array_diff($checkIds, array_keys($checks));
574
+
575
+        if (!empty($checkIds)) {
576
+            $missingCheck = array_pop($checkIds);
577
+            throw new \UnexpectedValueException($this->l->t('Check #%s does not exist', $missingCheck));
578
+        }
579
+
580
+        return $checks;
581
+    }
582
+
583
+    /**
584
+     * @param string $class
585
+     * @param string $operator
586
+     * @param string $value
587
+     * @return int Check unique ID
588
+     */
589
+    protected function addCheck($class, $operator, $value) {
590
+        $hash = md5($class . '::' . $operator . '::' . $value);
591
+
592
+        $query = $this->connection->getQueryBuilder();
593
+        $query->select('id')
594
+            ->from('flow_checks')
595
+            ->where($query->expr()->eq('hash', $query->createNamedParameter($hash)));
596
+        $result = $query->execute();
597
+
598
+        if ($row = $result->fetch()) {
599
+            $result->closeCursor();
600
+            return (int) $row['id'];
601
+        }
602
+
603
+        $query = $this->connection->getQueryBuilder();
604
+        $query->insert('flow_checks')
605
+            ->values([
606
+                'class' => $query->createNamedParameter($class),
607
+                'operator' => $query->createNamedParameter($operator),
608
+                'value' => $query->createNamedParameter($value),
609
+                'hash' => $query->createNamedParameter($hash),
610
+            ]);
611
+        $query->execute();
612
+
613
+        return $query->getLastInsertId();
614
+    }
615
+
616
+    protected function addScope(int $operationId, ScopeContext $scope): void {
617
+        $query = $this->connection->getQueryBuilder();
618
+
619
+        $insertQuery = $query->insert('flow_operations_scope');
620
+        $insertQuery->values([
621
+            'operation_id' => $query->createNamedParameter($operationId),
622
+            'type' => $query->createNamedParameter($scope->getScope()),
623
+            'value' => $query->createNamedParameter($scope->getScopeId()),
624
+        ]);
625
+        $insertQuery->execute();
626
+    }
627
+
628
+    public function formatOperation(array $operation): array {
629
+        $checkIds = json_decode($operation['checks'], true);
630
+        $checks = $this->getChecks($checkIds);
631
+
632
+        $operation['checks'] = [];
633
+        foreach ($checks as $check) {
634
+            // Remove internal values
635
+            unset($check['id']);
636
+            unset($check['hash']);
637
+
638
+            $operation['checks'][] = $check;
639
+        }
640
+        $operation['events'] = json_decode($operation['events'], true) ?? [];
641
+
642
+
643
+        return $operation;
644
+    }
645
+
646
+    /**
647
+     * @return IEntity[]
648
+     */
649
+    public function getEntitiesList(): array {
650
+        $this->dispatcher->dispatchTyped(new RegisterEntitiesEvent($this));
651
+        $this->legacyEventDispatcher->dispatch(IManager::EVENT_NAME_REG_ENTITY, new GenericEvent($this));
652
+
653
+        return array_values(array_merge($this->getBuildInEntities(), $this->registeredEntities));
654
+    }
655
+
656
+    /**
657
+     * @return IOperation[]
658
+     */
659
+    public function getOperatorList(): array {
660
+        $this->dispatcher->dispatchTyped(new RegisterOperationsEvent($this));
661
+        $this->legacyEventDispatcher->dispatch(IManager::EVENT_NAME_REG_OPERATION, new GenericEvent($this));
662
+
663
+        return array_merge($this->getBuildInOperators(), $this->registeredOperators);
664
+    }
665
+
666
+    /**
667
+     * @return ICheck[]
668
+     */
669
+    public function getCheckList(): array {
670
+        $this->dispatcher->dispatchTyped(new RegisterChecksEvent($this));
671
+        $this->legacyEventDispatcher->dispatch(IManager::EVENT_NAME_REG_CHECK, new GenericEvent($this));
672
+
673
+        return array_merge($this->getBuildInChecks(), $this->registeredChecks);
674
+    }
675
+
676
+    public function registerEntity(IEntity $entity): void {
677
+        $this->registeredEntities[get_class($entity)] = $entity;
678
+    }
679
+
680
+    public function registerOperation(IOperation $operator): void {
681
+        $this->registeredOperators[get_class($operator)] = $operator;
682
+    }
683
+
684
+    public function registerCheck(ICheck $check): void {
685
+        $this->registeredChecks[get_class($check)] = $check;
686
+    }
687
+
688
+    /**
689
+     * @return IEntity[]
690
+     */
691
+    protected function getBuildInEntities(): array {
692
+        try {
693
+            return [
694
+                File::class => $this->container->query(File::class),
695
+            ];
696
+        } catch (QueryException $e) {
697
+            $this->logger->logException($e);
698
+            return [];
699
+        }
700
+    }
701
+
702
+    /**
703
+     * @return IOperation[]
704
+     */
705
+    protected function getBuildInOperators(): array {
706
+        try {
707
+            return [
708
+                // None yet
709
+            ];
710
+        } catch (QueryException $e) {
711
+            $this->logger->logException($e);
712
+            return [];
713
+        }
714
+    }
715
+
716
+    /**
717
+     * @return ICheck[]
718
+     */
719
+    protected function getBuildInChecks(): array {
720
+        try {
721
+            return [
722
+                $this->container->query(FileMimeType::class),
723
+                $this->container->query(FileName::class),
724
+                $this->container->query(FileSize::class),
725
+                $this->container->query(FileSystemTags::class),
726
+                $this->container->query(RequestRemoteAddress::class),
727
+                $this->container->query(RequestTime::class),
728
+                $this->container->query(RequestURL::class),
729
+                $this->container->query(RequestUserAgent::class),
730
+                $this->container->query(UserGroupMembership::class),
731
+            ];
732
+        } catch (QueryException $e) {
733
+            $this->logger->logException($e);
734
+            return [];
735
+        }
736
+    }
737
+
738
+    public function isUserScopeEnabled(): bool {
739
+        return $this->config->getAppValue(Application::APP_ID, 'user_scope_disabled', 'no') === 'no';
740
+    }
741 741
 }
Please login to merge, or discard this patch.
Spacing   +6 added lines, -6 removed lines patch added patch discarded remove patch
@@ -352,7 +352,7 @@  discard block
 block discarded – undo
352 352
 
353 353
 		$operations = [];
354 354
 		while (($opId = $result->fetchOne()) !== false) {
355
-			$operations[] = (int)$opId;
355
+			$operations[] = (int) $opId;
356 356
 		}
357 357
 		$this->operationsByScope[$scopeContext->getHash()] = $operations;
358 358
 		$result->closeCursor();
@@ -425,12 +425,12 @@  discard block
 block discarded – undo
425 425
 		$query = $this->connection->getQueryBuilder();
426 426
 		try {
427 427
 			$this->connection->beginTransaction();
428
-			$result = (bool)$query->delete('flow_operations')
428
+			$result = (bool) $query->delete('flow_operations')
429 429
 				->where($query->expr()->eq('id', $query->createNamedParameter($id)))
430 430
 				->execute();
431 431
 			if ($result) {
432 432
 				$qb = $this->connection->getQueryBuilder();
433
-				$result &= (bool)$qb->delete('flow_operations_scope')
433
+				$result &= (bool) $qb->delete('flow_operations_scope')
434 434
 					->where($qb->expr()->eq('operation_id', $qb->createNamedParameter($id)))
435 435
 					->execute();
436 436
 			}
@@ -503,7 +503,7 @@  discard block
 block discarded – undo
503 503
 			throw new \UnexpectedValueException($this->l->t('At least one check needs to be provided'));
504 504
 		}
505 505
 
506
-		if (strlen((string)$operation) > IManager::MAX_OPERATION_VALUE_BYTES) {
506
+		if (strlen((string) $operation) > IManager::MAX_OPERATION_VALUE_BYTES) {
507 507
 			throw new \UnexpectedValueException($this->l->t('The provided operation data is too long'));
508 508
 		}
509 509
 
@@ -531,7 +531,7 @@  discard block
 block discarded – undo
531 531
 				throw new \UnexpectedValueException($this->l->t('Check %s is not allowed with this entity', [$class]));
532 532
 			}
533 533
 
534
-			if (strlen((string)$check['value']) > IManager::MAX_CHECK_VALUE_BYTES) {
534
+			if (strlen((string) $check['value']) > IManager::MAX_CHECK_VALUE_BYTES) {
535 535
 				throw new \UnexpectedValueException($this->l->t('The provided check value is too long'));
536 536
 			}
537 537
 
@@ -587,7 +587,7 @@  discard block
 block discarded – undo
587 587
 	 * @return int Check unique ID
588 588
 	 */
589 589
 	protected function addCheck($class, $operator, $value) {
590
-		$hash = md5($class . '::' . $operator . '::' . $value);
590
+		$hash = md5($class.'::'.$operator.'::'.$value);
591 591
 
592 592
 		$query = $this->connection->getQueryBuilder();
593 593
 		$query->select('id')
Please login to merge, or discard this patch.