Passed
Push — master ( 0c0e41...d48cc0 )
by Roeland
11:06 queued 10s
created

ShareesAPIController::getAllSharees()   B

Complexity

Conditions 8
Paths 20

Size

Total Lines 42
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 26
nc 20
nop 2
dl 0
loc 42
rs 8.4444
c 0
b 0
f 0
1
<?php
2
declare(strict_types=1);
3
/**
4
 * @copyright Copyright (c) 2016, ownCloud, Inc.
5
 *
6
 * @author Arthur Schiwon <[email protected]>
7
 * @author Bjoern Schiessle <[email protected]>
8
 * @author Björn Schießle <[email protected]>
9
 * @author Joas Schilling <[email protected]>
10
 * @author Maxence Lange <[email protected]>
11
 * @author Morris Jobke <[email protected]>
12
 * @author Robin Appelman <[email protected]>
13
 * @author Roeland Jago Douma <[email protected]>
14
 *
15
 * @license AGPL-3.0
16
 *
17
 * This code is free software: you can redistribute it and/or modify
18
 * it under the terms of the GNU Affero General Public License, version 3,
19
 * as published by the Free Software Foundation.
20
 *
21
 * This program is distributed in the hope that it will be useful,
22
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24
 * GNU Affero General Public License for more details.
25
 *
26
 * You should have received a copy of the GNU Affero General Public License, version 3,
27
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
28
 *
29
 */
30
namespace OCA\Files_Sharing\Controller;
31
32
use function array_filter;
33
use function array_slice;
34
use function array_values;
35
use Generator;
36
use OC\Collaboration\Collaborators\SearchResult;
37
use OCP\AppFramework\Http\DataResponse;
38
use OCP\AppFramework\OCS\OCSBadRequestException;
39
use OCP\AppFramework\OCSController;
40
use OCP\Collaboration\Collaborators\ISearch;
41
use OCP\Collaboration\Collaborators\ISearchResult;
42
use OCP\Collaboration\Collaborators\SearchResultType;
43
use OCP\IRequest;
44
use OCP\IConfig;
45
use OCP\IURLGenerator;
46
use OCP\Share;
47
use OCP\Share\IManager;
48
use function usort;
49
50
class ShareesAPIController extends OCSController {
51
52
	/** @var userId */
0 ignored issues
show
Bug introduced by
The type OCA\Files_Sharing\Controller\userId was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
53
	protected $userId;
54
55
	/** @var IConfig */
56
	protected $config;
57
58
	/** @var IURLGenerator */
59
	protected $urlGenerator;
60
61
	/** @var IManager */
62
	protected $shareManager;
63
64
	/** @var bool */
65
	protected $shareWithGroupOnly = false;
66
67
	/** @var bool */
68
	protected $shareeEnumeration = true;
69
70
	/** @var int */
71
	protected $offset = 0;
72
73
	/** @var int */
74
	protected $limit = 10;
75
76
	/** @var array */
77
	protected $result = [
78
		'exact' => [
79
			'users' => [],
80
			'groups' => [],
81
			'remotes' => [],
82
			'remote_groups' => [],
83
			'emails' => [],
84
			'circles' => [],
85
			'rooms' => [],
86
		],
87
		'users' => [],
88
		'groups' => [],
89
		'remotes' => [],
90
		'remote_groups' => [],
91
		'emails' => [],
92
		'lookup' => [],
93
		'circles' => [],
94
		'rooms' => [],
95
	];
96
97
	protected $reachedEndFor = [];
98
	/** @var ISearch */
99
	private $collaboratorSearch;
100
101
	/**
102
	 * @param string $UserId
103
	 * @param string $appName
104
	 * @param IRequest $request
105
	 * @param IConfig $config
106
	 * @param IURLGenerator $urlGenerator
107
	 * @param IManager $shareManager
108
	 * @param ISearch $collaboratorSearch
109
	 */
110
	public function __construct(
111
		$UserId,
112
		string $appName,
113
		IRequest $request,
114
		IConfig $config,
115
		IURLGenerator $urlGenerator,
116
		IManager $shareManager,
117
		ISearch $collaboratorSearch
118
	) {
119
		parent::__construct($appName, $request);
120
		$this->userId = $UserId;
0 ignored issues
show
Documentation Bug introduced by
It seems like $UserId of type string is incompatible with the declared type OCA\Files_Sharing\Controller\userId of property $userId.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
121
		$this->config = $config;
122
		$this->urlGenerator = $urlGenerator;
123
		$this->shareManager = $shareManager;
124
		$this->collaboratorSearch = $collaboratorSearch;
125
	}
126
127
	/**
128
	 * @NoAdminRequired
129
	 *
130
	 * @param string $search
131
	 * @param string $itemType
132
	 * @param int $page
133
	 * @param int $perPage
134
	 * @param int|int[] $shareType
135
	 * @param bool $lookup
136
	 * @return DataResponse
137
	 * @throws OCSBadRequestException
138
	 */
139
	public function search(string $search = '', string $itemType = null, int $page = 1, int $perPage = 200, $shareType = null, bool $lookup = true): DataResponse {
140
141
		// only search for string larger than a given threshold
142
		$threshold = (int)$this->config->getSystemValue('sharing.minSearchStringLength', 0);
143
		if (strlen($search) < $threshold) {
144
			return new DataResponse($this->result);
145
		}
146
147
		// never return more than the max. number of results configured in the config.php
148
		$maxResults = (int)$this->config->getSystemValue('sharing.maxAutocompleteResults', 0);
149
		if ($maxResults > 0) {
150
			$perPage = min($perPage, $maxResults);
151
		}
152
		if ($perPage <= 0) {
153
			throw new OCSBadRequestException('Invalid perPage argument');
154
		}
155
		if ($page <= 0) {
156
			throw new OCSBadRequestException('Invalid page');
157
		}
158
159
		$shareTypes = [
160
			Share::SHARE_TYPE_USER,
161
		];
162
163
		if ($itemType === null) {
164
			throw new OCSBadRequestException('Missing itemType');
165
		} elseif ($itemType === 'file' || $itemType === 'folder') {
166
			if ($this->shareManager->allowGroupSharing()) {
167
				$shareTypes[] = Share::SHARE_TYPE_GROUP;
168
			}
169
170
			if ($this->isRemoteSharingAllowed($itemType)) {
171
				$shareTypes[] = Share::SHARE_TYPE_REMOTE;
172
			}
173
174
			if ($this->isRemoteGroupSharingAllowed($itemType)) {
175
				$shareTypes[] = Share::SHARE_TYPE_REMOTE_GROUP;
176
			}
177
178
			if ($this->shareManager->shareProviderExists(Share::SHARE_TYPE_EMAIL)) {
179
				$shareTypes[] = Share::SHARE_TYPE_EMAIL;
180
			}
181
182
			if ($this->shareManager->shareProviderExists(Share::SHARE_TYPE_ROOM)) {
183
				$shareTypes[] = Share::SHARE_TYPE_ROOM;
184
			}
185
		} else {
186
			$shareTypes[] = Share::SHARE_TYPE_GROUP;
187
			$shareTypes[] = Share::SHARE_TYPE_EMAIL;
188
		}
189
190
		// FIXME: DI
191
		if (\OC::$server->getAppManager()->isEnabledForUser('circles') && class_exists('\OCA\Circles\ShareByCircleProvider')) {
192
			$shareTypes[] = Share::SHARE_TYPE_CIRCLE;
193
		}
194
195
		if (isset($_GET['shareType']) && is_array($_GET['shareType'])) {
196
			$shareTypes = array_intersect($shareTypes, $_GET['shareType']);
197
			sort($shareTypes);
198
		} else if (is_numeric($shareType)) {
199
			$shareTypes = array_intersect($shareTypes, [(int) $shareType]);
200
			sort($shareTypes);
201
		}
202
203
		$this->shareWithGroupOnly = $this->config->getAppValue('core', 'shareapi_only_share_with_group_members', 'no') === 'yes';
204
		$this->shareeEnumeration = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes';
205
		$this->limit = (int) $perPage;
206
		$this->offset = $perPage * ($page - 1);
207
208
		list($result, $hasMoreResults) = $this->collaboratorSearch->search($search, $shareTypes, $lookup, $this->limit, $this->offset);
209
210
		// extra treatment for 'exact' subarray, with a single merge expected keys might be lost
211
		if(isset($result['exact'])) {
212
			$result['exact'] = array_merge($this->result['exact'], $result['exact']);
213
		}
214
		$this->result = array_merge($this->result, $result);
215
		$response = new DataResponse($this->result);
216
217
		if ($hasMoreResults) {
218
			$response->addHeader('Link', $this->getPaginationLink($page, [
219
				'search' => $search,
220
				'itemType' => $itemType,
221
				'shareType' => $shareTypes,
222
				'perPage' => $perPage,
223
			]));
224
		}
225
226
		return $response;
227
	}
228
229
	/**
230
	 * @param string $user
231
	 * @param int $shareType
232
	 *
233
	 * @return Generator<array<string>>
234
	 */
235
	private function getAllShareesByType(string $user, int $shareType): Generator {
236
		$offset = 0;
237
		$pageSize = 50;
238
239
		while (count($page = $this->shareManager->getSharesBy(
240
			$user,
241
			$shareType,
242
			null,
243
			false,
244
			$pageSize,
245
			$offset
246
		))) {
247
			foreach ($page as $share) {
248
				yield [$share->getSharedWith(), $share->getSharedWithDisplayName() ?? $share->getSharedWith()];
249
			}
250
251
			$offset += $pageSize;
252
		}
253
	}
254
255
	private function sortShareesByFrequency(array $sharees): array {
256
		usort($sharees, function(array $s1, array $s2) {
257
			return $s2['count'] - $s1['count'];
258
		});
259
		return $sharees;
260
	}
261
262
	private $searchResultTypeMap = [
263
		Share::SHARE_TYPE_USER => 'users',
264
		Share::SHARE_TYPE_GROUP => 'groups',
265
		Share::SHARE_TYPE_REMOTE => 'remotes',
266
		Share::SHARE_TYPE_REMOTE_GROUP => 'remote_groups',
267
		Share::SHARE_TYPE_EMAIL => 'emails',
268
	];
269
270
	private function getAllSharees(string $user, array $shareTypes): ISearchResult {
271
		$result = [];
272
		foreach ($shareTypes as $shareType) {
273
			$sharees = $this->getAllShareesByType($user, $shareType);
274
			$shareTypeResults = [];
275
			foreach ($sharees as list($sharee, $displayname)) {
276
				if (!isset($this->searchResultTypeMap[$shareType])) {
277
					continue;
278
				}
279
280
				if (!isset($shareTypeResults[$sharee])) {
281
					$shareTypeResults[$sharee] = [
282
						'count' => 1,
283
						'label' => $displayname,
284
						'value' => [
285
							'shareType' => $shareType,
286
							'shareWith' => $sharee,
287
						],
288
					];
289
				} else {
290
					$shareTypeResults[$sharee]['count']++;
291
				}
292
			}
293
			$result = array_merge($result, array_values($shareTypeResults));
294
		}
295
296
		$top5 = array_slice(
297
			$this->sortShareesByFrequency($result),
298
			0,
299
			5
300
		);
301
302
		$searchResult = new SearchResult();
303
		foreach ($this->searchResultTypeMap as $int => $str) {
304
			$searchResult->addResultSet(new SearchResultType($str), [], []);
305
			foreach ($top5 as $x) {
306
				if ($x['value']['shareType'] === $int) {
307
					$searchResult->addResultSet(new SearchResultType($str), [], [$x]);
308
				}
309
			}
310
		}
311
		return $searchResult;
312
	}
313
314
	/**
315
	 * @NoAdminRequired
316
	 *
317
	 * @param string $itemType
318
	 * @return DataResponse
319
	 * @throws OCSBadRequestException
320
	 */
321
	public function findRecommended(string $itemType = null, $shareType = null): DataResponse {
322
		$shareTypes = [
323
			Share::SHARE_TYPE_USER,
324
		];
325
326
		if ($itemType === null) {
327
			throw new OCSBadRequestException('Missing itemType');
328
		} elseif ($itemType === 'file' || $itemType === 'folder') {
329
			if ($this->shareManager->allowGroupSharing()) {
330
				$shareTypes[] = Share::SHARE_TYPE_GROUP;
331
			}
332
333
			if ($this->isRemoteSharingAllowed($itemType)) {
334
				$shareTypes[] = Share::SHARE_TYPE_REMOTE;
335
			}
336
337
			if ($this->isRemoteGroupSharingAllowed($itemType)) {
338
				$shareTypes[] = Share::SHARE_TYPE_REMOTE_GROUP;
339
			}
340
341
			if ($this->shareManager->shareProviderExists(Share::SHARE_TYPE_EMAIL)) {
342
				$shareTypes[] = Share::SHARE_TYPE_EMAIL;
343
			}
344
345
			if ($this->shareManager->shareProviderExists(Share::SHARE_TYPE_ROOM)) {
346
				$shareTypes[] = Share::SHARE_TYPE_ROOM;
347
			}
348
		} else {
349
			$shareTypes[] = Share::SHARE_TYPE_GROUP;
350
			$shareTypes[] = Share::SHARE_TYPE_EMAIL;
351
		}
352
353
		// FIXME: DI
354
		if (\OC::$server->getAppManager()->isEnabledForUser('circles') && class_exists('\OCA\Circles\ShareByCircleProvider')) {
355
			$shareTypes[] = Share::SHARE_TYPE_CIRCLE;
356
		}
357
358
		if (isset($_GET['shareType']) && is_array($_GET['shareType'])) {
359
			$shareTypes = array_intersect($shareTypes, $_GET['shareType']);
360
			sort($shareTypes);
361
		} else if (is_numeric($shareType)) {
362
			$shareTypes = array_intersect($shareTypes, [(int) $shareType]);
363
			sort($shareTypes);
364
		}
365
366
		return new DataResponse(
367
			$this->getAllSharees($this->userId, $shareTypes)->asArray()
368
		);
369
	}
370
371
	/**
372
	 * Method to get out the static call for better testing
373
	 *
374
	 * @param string $itemType
375
	 * @return bool
376
	 */
377
	protected function isRemoteSharingAllowed(string $itemType): bool {
378
		try {
379
			// FIXME: static foo makes unit testing unnecessarily difficult
380
			$backend = \OC\Share\Share::getBackend($itemType);
381
			return $backend->isShareTypeAllowed(Share::SHARE_TYPE_REMOTE);
382
		} catch (\Exception $e) {
383
			return false;
384
		}
385
	}
386
387
	protected function isRemoteGroupSharingAllowed(string $itemType): bool {
388
		try {
389
			// FIXME: static foo makes unit testing unnecessarily difficult
390
			$backend = \OC\Share\Share::getBackend($itemType);
391
			return $backend->isShareTypeAllowed(Share::SHARE_TYPE_REMOTE_GROUP);
392
		} catch (\Exception $e) {
393
			return false;
394
		}
395
	}
396
397
398
	/**
399
	 * Generates a bunch of pagination links for the current page
400
	 *
401
	 * @param int $page Current page
402
	 * @param array $params Parameters for the URL
403
	 * @return string
404
	 */
405
	protected function getPaginationLink(int $page, array $params): string {
406
		if ($this->isV2()) {
407
			$url = $this->urlGenerator->getAbsoluteURL('/ocs/v2.php/apps/files_sharing/api/v1/sharees') . '?';
408
		} else {
409
			$url = $this->urlGenerator->getAbsoluteURL('/ocs/v1.php/apps/files_sharing/api/v1/sharees') . '?';
410
		}
411
		$params['page'] = $page + 1;
412
		return '<' . $url . http_build_query($params) . '>; rel="next"';
413
	}
414
415
	/**
416
	 * @return bool
417
	 */
418
	protected function isV2(): bool {
419
		return $this->request->getScriptName() === '/ocs/v2.php';
420
	}
421
}
422