Completed
Push — master ( 63adda...00bb92 )
by Lukas
09:13
created

ShareesAPIController::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 21
Code Lines 19

Duplication

Lines 21
Ratio 100 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 19
c 1
b 0
f 0
nc 1
nop 10
dl 21
loc 21
rs 9.3142

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Björn Schießle <[email protected]>
6
 * @author Joas Schilling <[email protected]>
7
 * @author Roeland Jago Douma <[email protected]>
8
 * @author Thomas Müller <[email protected]>
9
 *
10
 * @license AGPL-3.0
11
 *
12
 * This code is free software: you can redistribute it and/or modify
13
 * it under the terms of the GNU Affero General Public License, version 3,
14
 * as published by the Free Software Foundation.
15
 *
16
 * This program is distributed in the hope that it will be useful,
17
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19
 * GNU Affero General Public License for more details.
20
 *
21
 * You should have received a copy of the GNU Affero General Public License, version 3,
22
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
23
 *
24
 */
25
namespace OCA\Files_Sharing\Controller;
26
27
use OCP\AppFramework\Http;
28
use OCP\AppFramework\OCS\OCSBadRequestException;
29
use OCP\AppFramework\OCSController;
30
use OCP\Contacts\IManager;
31
use OCP\IGroup;
32
use OCP\IGroupManager;
33
use OCP\ILogger;
34
use OCP\IRequest;
35
use OCP\IUser;
36
use OCP\IUserManager;
37
use OCP\IConfig;
38
use OCP\IUserSession;
39
use OCP\IURLGenerator;
40
use OCP\Share;
41
42
class ShareesAPIController extends OCSController {
43
44
	/** @var IGroupManager */
45
	protected $groupManager;
46
47
	/** @var IUserManager */
48
	protected $userManager;
49
50
	/** @var IManager */
51
	protected $contactsManager;
52
53
	/** @var IConfig */
54
	protected $config;
55
56
	/** @var IUserSession */
57
	protected $userSession;
58
59
	/** @var IURLGenerator */
60
	protected $urlGenerator;
61
62
	/** @var ILogger */
63
	protected $logger;
64
65
	/** @var \OCP\Share\IManager */
66
	protected $shareManager;
67
68
	/** @var bool */
69
	protected $shareWithGroupOnly = false;
70
71
	/** @var bool */
72
	protected $shareeEnumeration = true;
73
74
	/** @var int */
75
	protected $offset = 0;
76
77
	/** @var int */
78
	protected $limit = 10;
79
80
	/** @var array */
81
	protected $result = [
82
		'exact' => [
83
			'users' => [],
84
			'groups' => [],
85
			'remotes' => [],
86
		],
87
		'users' => [],
88
		'groups' => [],
89
		'remotes' => [],
90
	];
91
92
	protected $reachedEndFor = [];
93
94
	/**
95
	 * @param string $appName
96
	 * @param IRequest $request
97
	 * @param IGroupManager $groupManager
98
	 * @param IUserManager $userManager
99
	 * @param IManager $contactsManager
100
	 * @param IConfig $config
101
	 * @param IUserSession $userSession
102
	 * @param IURLGenerator $urlGenerator
103
	 * @param ILogger $logger
104
	 * @param \OCP\Share\IManager $shareManager
105
	 */
106 View Code Duplication
	public function __construct($appName,
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
107
								IRequest $request,
108
								IGroupManager $groupManager,
109
								IUserManager $userManager,
110
								IManager $contactsManager,
111
								IConfig $config,
112
								IUserSession $userSession,
113
								IURLGenerator $urlGenerator,
114
								ILogger $logger,
115
								\OCP\Share\IManager $shareManager) {
116
		parent::__construct($appName, $request);
117
118
		$this->groupManager = $groupManager;
119
		$this->userManager = $userManager;
120
		$this->contactsManager = $contactsManager;
121
		$this->config = $config;
122
		$this->userSession = $userSession;
123
		$this->urlGenerator = $urlGenerator;
124
		$this->logger = $logger;
125
		$this->shareManager = $shareManager;
126
	}
127
128
	/**
129
	 * @param string $search
130
	 */
131
	protected function getUsers($search) {
132
		$this->result['users'] = $this->result['exact']['users'] = $users = [];
133
134
		$userGroups = [];
135
		if ($this->shareWithGroupOnly) {
136
			// Search in all the groups this user is part of
137
			$userGroups = $this->groupManager->getUserGroupIds($this->userSession->getUser());
0 ignored issues
show
Bug introduced by
It seems like $this->userSession->getUser() can be null; however, getUserGroupIds() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
138
			foreach ($userGroups as $userGroup) {
139
				$usersTmp = $this->groupManager->displayNamesInGroup($userGroup, $search, $this->limit, $this->offset);
140
				foreach ($usersTmp as $uid => $userDisplayName) {
141
					$users[$uid] = $userDisplayName;
142
				}
143
			}
144
		} else {
145
			// Search in all users
146
			$usersTmp = $this->userManager->searchDisplayName($search, $this->limit, $this->offset);
147
148
			foreach ($usersTmp as $user) {
149
				$users[$user->getUID()] = $user->getDisplayName();
150
			}
151
		}
152
153 View Code Duplication
		if (!$this->shareeEnumeration || sizeof($users) < $this->limit) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
154
			$this->reachedEndFor[] = 'users';
155
		}
156
157
		$foundUserById = false;
158
		foreach ($users as $uid => $userDisplayName) {
159
			if (strtolower($uid) === strtolower($search) || strtolower($userDisplayName) === strtolower($search)) {
160
				if (strtolower($uid) === strtolower($search)) {
161
					$foundUserById = true;
162
				}
163
				$this->result['exact']['users'][] = [
164
					'label' => $userDisplayName,
165
					'value' => [
166
						'shareType' => Share::SHARE_TYPE_USER,
167
						'shareWith' => $uid,
168
					],
169
				];
170 View Code Duplication
			} else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
171
				$this->result['users'][] = [
172
					'label' => $userDisplayName,
173
					'value' => [
174
						'shareType' => Share::SHARE_TYPE_USER,
175
						'shareWith' => $uid,
176
					],
177
				];
178
			}
179
		}
180
181
		if ($this->offset === 0 && !$foundUserById) {
182
			// On page one we try if the search result has a direct hit on the
183
			// user id and if so, we add that to the exact match list
184
			$user = $this->userManager->get($search);
185
			if ($user instanceof IUser) {
186
				$addUser = true;
187
188
				if ($this->shareWithGroupOnly) {
189
					// Only add, if we have a common group
190
					$commonGroups = array_intersect($userGroups, $this->groupManager->getUserGroupIds($user));
191
					$addUser = !empty($commonGroups);
192
				}
193
194
				if ($addUser) {
195
					array_push($this->result['exact']['users'], [
196
						'label' => $user->getDisplayName(),
197
						'value' => [
198
							'shareType' => Share::SHARE_TYPE_USER,
199
							'shareWith' => $user->getUID(),
200
						],
201
					]);
202
				}
203
			}
204
		}
205
206
		if (!$this->shareeEnumeration) {
207
			$this->result['users'] = [];
208
		}
209
	}
210
211
	/**
212
	 * @param string $search
213
	 */
214
	protected function getGroups($search) {
215
		$this->result['groups'] = $this->result['exact']['groups'] = [];
216
217
		$groups = $this->groupManager->search($search, $this->limit, $this->offset);
218
		$groups = array_map(function (IGroup $group) { return $group->getGID(); }, $groups);
219
220 View Code Duplication
		if (!$this->shareeEnumeration || sizeof($groups) < $this->limit) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
221
			$this->reachedEndFor[] = 'groups';
222
		}
223
224
		$userGroups =  [];
225
		if (!empty($groups) && $this->shareWithGroupOnly) {
226
			// Intersect all the groups that match with the groups this user is a member of
227
			$userGroups = $this->groupManager->getUserGroups($this->userSession->getUser());
228
			$userGroups = array_map(function (IGroup $group) { return $group->getGID(); }, $userGroups);
229
			$groups = array_intersect($groups, $userGroups);
230
		}
231
232
		foreach ($groups as $gid) {
233
			if (strtolower($gid) === strtolower($search)) {
234
				$this->result['exact']['groups'][] = [
235
					'label' => $gid,
236
					'value' => [
237
						'shareType' => Share::SHARE_TYPE_GROUP,
238
						'shareWith' => $gid,
239
					],
240
				];
241 View Code Duplication
			} else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
242
				$this->result['groups'][] = [
243
					'label' => $gid,
244
					'value' => [
245
						'shareType' => Share::SHARE_TYPE_GROUP,
246
						'shareWith' => $gid,
247
					],
248
				];
249
			}
250
		}
251
252
		if ($this->offset === 0 && empty($this->result['exact']['groups'])) {
253
			// On page one we try if the search result has a direct hit on the
254
			// user id and if so, we add that to the exact match list
255
			$group = $this->groupManager->get($search);
256
			if ($group instanceof IGroup && (!$this->shareWithGroupOnly || in_array($group->getGID(), $userGroups))) {
257
				array_push($this->result['exact']['groups'], [
258
					'label' => $group->getGID(),
259
					'value' => [
260
						'shareType' => Share::SHARE_TYPE_GROUP,
261
						'shareWith' => $group->getGID(),
262
					],
263
				]);
264
			}
265
		}
266
267
		if (!$this->shareeEnumeration) {
268
			$this->result['groups'] = [];
269
		}
270
	}
271
272
	/**
273
	 * @param string $search
274
	 * @return array possible sharees
275
	 */
276
	protected function getRemote($search) {
277
		$this->result['remotes'] = [];
278
279
		// Search in contacts
280
		//@todo Pagination missing
281
		$addressBookContacts = $this->contactsManager->search($search, ['CLOUD', 'FN']);
282
		$foundRemoteById = false;
283
		foreach ($addressBookContacts as $contact) {
284
			if (isset($contact['isLocalSystemBook'])) {
285
				continue;
286
			}
287
			if (isset($contact['CLOUD'])) {
288
				$cloudIds = $contact['CLOUD'];
289
				if (!is_array($cloudIds)) {
290
					$cloudIds = [$cloudIds];
291
				}
292
				foreach ($cloudIds as $cloudId) {
293
					list(, $serverUrl) = $this->splitUserRemote($cloudId);
294
					if (strtolower($contact['FN']) === strtolower($search) || strtolower($cloudId) === strtolower($search)) {
295
						if (strtolower($cloudId) === strtolower($search)) {
296
							$foundRemoteById = true;
297
						}
298
						$this->result['exact']['remotes'][] = [
299
							'label' => $contact['FN'],
300
							'value' => [
301
								'shareType' => Share::SHARE_TYPE_REMOTE,
302
								'shareWith' => $cloudId,
303
								'server' => $serverUrl,
304
							],
305
						];
306 View Code Duplication
					} else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
307
						$this->result['remotes'][] = [
308
							'label' => $contact['FN'],
309
							'value' => [
310
								'shareType' => Share::SHARE_TYPE_REMOTE,
311
								'shareWith' => $cloudId,
312
								'server' => $serverUrl,
313
							],
314
						];
315
					}
316
				}
317
			}
318
		}
319
320
		if (!$this->shareeEnumeration) {
321
			$this->result['remotes'] = [];
322
		}
323
324
		if (!$foundRemoteById && substr_count($search, '@') >= 1 && substr_count($search, ' ') === 0 && $this->offset === 0) {
325
			$this->result['exact']['remotes'][] = [
326
				'label' => $search,
327
				'value' => [
328
					'shareType' => Share::SHARE_TYPE_REMOTE,
329
					'shareWith' => $search,
330
				],
331
			];
332
		}
333
334
		$this->reachedEndFor[] = 'remotes';
335
	}
336
337
	/**
338
	 * split user and remote from federated cloud id
339
	 *
340
	 * @param string $address federated share address
341
	 * @return array [user, remoteURL]
342
	 * @throws \Exception
343
	 */
344
	public function splitUserRemote($address) {
345
		if (strpos($address, '@') === false) {
346
			throw new \Exception('Invalid Federated Cloud ID');
347
		}
348
349
		// Find the first character that is not allowed in user names
350
		$id = str_replace('\\', '/', $address);
351
		$posSlash = strpos($id, '/');
352
		$posColon = strpos($id, ':');
353
354 View Code Duplication
		if ($posSlash === false && $posColon === false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
355
			$invalidPos = strlen($id);
356
		} else if ($posSlash === false) {
357
			$invalidPos = $posColon;
358
		} else if ($posColon === false) {
359
			$invalidPos = $posSlash;
360
		} else {
361
			$invalidPos = min($posSlash, $posColon);
362
		}
363
364
		// Find the last @ before $invalidPos
365
		$pos = $lastAtPos = 0;
366 View Code Duplication
		while ($lastAtPos !== false && $lastAtPos <= $invalidPos) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
367
			$pos = $lastAtPos;
368
			$lastAtPos = strpos($id, '@', $pos + 1);
369
		}
370
371 View Code Duplication
		if ($pos !== false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
372
			$user = substr($id, 0, $pos);
373
			$remote = substr($id, $pos + 1);
374
			$remote = $this->fixRemoteURL($remote);
375
			if (!empty($user) && !empty($remote)) {
376
				return array($user, $remote);
377
			}
378
		}
379
380
		throw new \Exception('Invalid Federated Cloud ID');
381
	}
382
383
	/**
384
	 * Strips away a potential file names and trailing slashes:
385
	 * - http://localhost
386
	 * - http://localhost/
387
	 * - http://localhost/index.php
388
	 * - http://localhost/index.php/s/{shareToken}
389
	 *
390
	 * all return: http://localhost
391
	 *
392
	 * @param string $remote
393
	 * @return string
394
	 */
395 View Code Duplication
	protected function fixRemoteURL($remote) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
396
		$remote = str_replace('\\', '/', $remote);
397
		if ($fileNamePosition = strpos($remote, '/index.php')) {
398
			$remote = substr($remote, 0, $fileNamePosition);
399
		}
400
		$remote = rtrim($remote, '/');
401
402
		return $remote;
403
	}
404
405
	/**
406
	 * @NoAdminRequired
407
	 *
408
	 * @param string $search
409
	 * @param string $itemType
410
	 * @param int $page
411
	 * @param int $perPage
412
	 * @param int|int[] $shareType
413
	 * @return Http\DataResponse
414
	 * @throws OCSBadRequestException
415
	 */
416
	public function search($search = '', $itemType = null, $page = 1, $perPage = 200, $shareType = null) {
417
		if ($perPage <= 0) {
418
			throw new OCSBadRequestException('Invalid perPage argument');
419
		}
420
		if ($page <= 0) {
421
			throw new OCSBadRequestException('Invalid page');
422
		}
423
424
		$shareTypes = [
425
			Share::SHARE_TYPE_USER,
426
		];
427
428
		if ($this->shareManager->allowGroupSharing()) {
429
			$shareTypes[] = Share::SHARE_TYPE_GROUP;
430
		}
431
432
		$shareTypes[] = Share::SHARE_TYPE_REMOTE;
433
434
		if (is_array($shareType)) {
435
			$shareTypes = array_intersect($shareTypes, $shareType);
436
			sort($shareTypes);
437
		} else if (is_numeric($shareType)) {
438
			$shareTypes = array_intersect($shareTypes, [(int) $shareType]);
439
			sort($shareTypes);
440
		}
441
442
		if (in_array(Share::SHARE_TYPE_REMOTE, $shareTypes) && !$this->isRemoteSharingAllowed($itemType)) {
443
			// Remove remote shares from type array, because it is not allowed.
444
			$shareTypes = array_diff($shareTypes, [Share::SHARE_TYPE_REMOTE]);
445
		}
446
447
		$this->shareWithGroupOnly = $this->config->getAppValue('core', 'shareapi_only_share_with_group_members', 'no') === 'yes';
448
		$this->shareeEnumeration = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes';
449
		$this->limit = (int) $perPage;
450
		$this->offset = $perPage * ($page - 1);
451
452
		return $this->searchSharees($search, $itemType, $shareTypes, $page, $perPage);
453
	}
454
455
	/**
456
	 * Method to get out the static call for better testing
457
	 *
458
	 * @param string $itemType
459
	 * @return bool
460
	 */
461
	protected function isRemoteSharingAllowed($itemType) {
462
		try {
463
			$backend = Share::getBackend($itemType);
464
			return $backend->isShareTypeAllowed(Share::SHARE_TYPE_REMOTE);
465
		} catch (\Exception $e) {
466
			return false;
467
		}
468
	}
469
470
	/**
471
	 * Testable search function that does not need globals
472
	 *
473
	 * @param string $search
474
	 * @param string $itemType
475
	 * @param array $shareTypes
476
	 * @param int $page
477
	 * @param int $perPage
478
	 * @return Http\DataResponse
479
	 * @throws OCSBadRequestException
480
	 */
481
	protected function searchSharees($search, $itemType, array $shareTypes, $page, $perPage) {
482
		// Verify arguments
483
		if ($itemType === null) {
484
			throw new OCSBadRequestException('Missing itemType');
485
		}
486
487
		// Get users
488
		if (in_array(Share::SHARE_TYPE_USER, $shareTypes)) {
489
			$this->getUsers($search);
490
		}
491
492
		// Get groups
493
		if (in_array(Share::SHARE_TYPE_GROUP, $shareTypes)) {
494
			$this->getGroups($search);
495
		}
496
497
		// Get remote
498
		if (in_array(Share::SHARE_TYPE_REMOTE, $shareTypes)) {
499
			$this->getRemote($search);
500
		}
501
502
		$response = new Http\DataResponse($this->result);
503
504
		if (sizeof($this->reachedEndFor) < 3) {
505
			$response->addHeader('Link', $this->getPaginationLink($page, [
506
				'search' => $search,
507
				'itemType' => $itemType,
508
				'shareType' => $shareTypes,
509
				'perPage' => $perPage,
510
			]));
511
		}
512
513
		return $response;
514
	}
515
516
	/**
517
	 * Generates a bunch of pagination links for the current page
518
	 *
519
	 * @param int $page Current page
520
	 * @param array $params Parameters for the URL
521
	 * @return string
522
	 */
523
	protected function getPaginationLink($page, array $params) {
524
		if ($this->isV2()) {
525
			$url = $this->urlGenerator->getAbsoluteURL('/ocs/v2.php/apps/files_sharing/api/v1/sharees') . '?';
526
		} else {
527
			$url = $this->urlGenerator->getAbsoluteURL('/ocs/v1.php/apps/files_sharing/api/v1/sharees') . '?';
528
		}
529
		$params['page'] = $page + 1;
530
		$link = '<' . $url . http_build_query($params) . '>; rel="next"';
531
532
		return $link;
533
	}
534
535
	/**
536
	 * @return bool
537
	 */
538
	protected function isV2() {
539
		return $this->request->getScriptName() === '/ocs/v2.php';
540
	}
541
}
542