Completed
Pull Request — master (#9293)
by Blizzz
38:09 queued 17:25
created
apps/files_trashbin/ajax/undelete.php 1 patch
Indentation   +43 added lines, -43 removed lines patch added patch discarded remove patch
@@ -35,25 +35,25 @@  discard block
 block discarded – undo
35 35
 
36 36
 $dir = '/';
37 37
 if (isset($_POST['dir'])) {
38
-	$dir = rtrim((string)$_POST['dir'], '/'). '/';
38
+    $dir = rtrim((string)$_POST['dir'], '/'). '/';
39 39
 }
40 40
 $allFiles = false;
41 41
 if (isset($_POST['allfiles']) && (string)$_POST['allfiles'] === 'true') {
42
-	$allFiles = true;
43
-	$list = array();
44
-	$dirListing = true;
45
-	if ($dir === '' || $dir === '/') {
46
-		$dirListing = false;
47
-	}
48
-	foreach (OCA\Files_Trashbin\Helper::getTrashFiles($dir, \OCP\User::getUser()) as $file) {
49
-		$fileName = $file['name'];
50
-		if (!$dirListing) {
51
-			$fileName .= '.d' . $file['mtime'];
52
-		}
53
-		$list[] = $fileName;
54
-	}
42
+    $allFiles = true;
43
+    $list = array();
44
+    $dirListing = true;
45
+    if ($dir === '' || $dir === '/') {
46
+        $dirListing = false;
47
+    }
48
+    foreach (OCA\Files_Trashbin\Helper::getTrashFiles($dir, \OCP\User::getUser()) as $file) {
49
+        $fileName = $file['name'];
50
+        if (!$dirListing) {
51
+            $fileName .= '.d' . $file['mtime'];
52
+        }
53
+        $list[] = $fileName;
54
+    }
55 55
 } else {
56
-	$list = json_decode($_POST['files']);
56
+    $list = json_decode($_POST['files']);
57 57
 }
58 58
 
59 59
 $error = array();
@@ -61,38 +61,38 @@  discard block
 block discarded – undo
61 61
 
62 62
 $i = 0;
63 63
 foreach ($list as $file) {
64
-	$path = $dir . '/' . $file;
65
-	if ($dir === '/') {
66
-		$file = ltrim($file, '/');
67
-		$delimiter = strrpos($file, '.d');
68
-		$filename = substr($file, 0, $delimiter);
69
-		$timestamp =  substr($file, $delimiter+2);
70
-	} else {
71
-		$path_parts = pathinfo($file);
72
-		$filename = $path_parts['basename'];
73
-		$timestamp = null;
74
-	}
64
+    $path = $dir . '/' . $file;
65
+    if ($dir === '/') {
66
+        $file = ltrim($file, '/');
67
+        $delimiter = strrpos($file, '.d');
68
+        $filename = substr($file, 0, $delimiter);
69
+        $timestamp =  substr($file, $delimiter+2);
70
+    } else {
71
+        $path_parts = pathinfo($file);
72
+        $filename = $path_parts['basename'];
73
+        $timestamp = null;
74
+    }
75 75
 
76
-	if ( !OCA\Files_Trashbin\Trashbin::restore($path, $filename, $timestamp) ) {
77
-		$error[] = $filename;
78
-		\OCP\Util::writeLog('trashbin', 'can\'t restore ' . $filename, ILogger::ERROR);
79
-	} else {
80
-		$success[$i]['filename'] = $file;
81
-		$success[$i]['timestamp'] = $timestamp;
82
-		$i++;
83
-	}
76
+    if ( !OCA\Files_Trashbin\Trashbin::restore($path, $filename, $timestamp) ) {
77
+        $error[] = $filename;
78
+        \OCP\Util::writeLog('trashbin', 'can\'t restore ' . $filename, ILogger::ERROR);
79
+    } else {
80
+        $success[$i]['filename'] = $file;
81
+        $success[$i]['timestamp'] = $timestamp;
82
+        $i++;
83
+    }
84 84
 
85 85
 }
86 86
 
87 87
 if ( $error ) {
88
-	$filelist = '';
89
-	foreach ( $error as $e ) {
90
-		$filelist .= $e.', ';
91
-	}
92
-	$l = OC::$server->getL10N('files_trashbin');
93
-	$message = $l->t("Couldn't restore %s", array(rtrim($filelist, ', ')));
94
-	\OC_JSON::error(array("data" => array("message" => $message,
95
-										  "success" => $success, "error" => $error)));
88
+    $filelist = '';
89
+    foreach ( $error as $e ) {
90
+        $filelist .= $e.', ';
91
+    }
92
+    $l = OC::$server->getL10N('files_trashbin');
93
+    $message = $l->t("Couldn't restore %s", array(rtrim($filelist, ', ')));
94
+    \OC_JSON::error(array("data" => array("message" => $message,
95
+                                            "success" => $success, "error" => $error)));
96 96
 } else {
97
-	\OC_JSON::success(array("data" => array("success" => $success)));
97
+    \OC_JSON::success(array("data" => array("success" => $success)));
98 98
 }
Please login to merge, or discard this patch.
apps/provisioning_api/lib/Controller/UsersController.php 1 patch
Indentation   +841 added lines, -841 removed lines patch added patch discarded remove patch
@@ -52,845 +52,845 @@
 block discarded – undo
52 52
 
53 53
 class UsersController extends AUserData {
54 54
 
55
-	/** @var IAppManager */
56
-	private $appManager;
57
-	/** @var ILogger */
58
-	private $logger;
59
-	/** @var IFactory */
60
-	private $l10nFactory;
61
-	/** @var NewUserMailHelper */
62
-	private $newUserMailHelper;
63
-	/** @var FederatedFileSharingFactory */
64
-	private $federatedFileSharingFactory;
65
-	/** @var ISecureRandom */
66
-	private $secureRandom;
67
-
68
-	/**
69
-	 * @param string $appName
70
-	 * @param IRequest $request
71
-	 * @param IUserManager $userManager
72
-	 * @param IConfig $config
73
-	 * @param IAppManager $appManager
74
-	 * @param IGroupManager $groupManager
75
-	 * @param IUserSession $userSession
76
-	 * @param AccountManager $accountManager
77
-	 * @param ILogger $logger
78
-	 * @param IFactory $l10nFactory
79
-	 * @param NewUserMailHelper $newUserMailHelper
80
-	 * @param FederatedFileSharingFactory $federatedFileSharingFactory
81
-	 * @param ISecureRandom $secureRandom
82
-	 */
83
-	public function __construct(string $appName,
84
-								IRequest $request,
85
-								IUserManager $userManager,
86
-								IConfig $config,
87
-								IAppManager $appManager,
88
-								IGroupManager $groupManager,
89
-								IUserSession $userSession,
90
-								AccountManager $accountManager,
91
-								ILogger $logger,
92
-								IFactory $l10nFactory,
93
-								NewUserMailHelper $newUserMailHelper,
94
-								FederatedFileSharingFactory $federatedFileSharingFactory,
95
-								ISecureRandom $secureRandom) {
96
-		parent::__construct($appName,
97
-							$request,
98
-							$userManager,
99
-							$config,
100
-							$groupManager,
101
-							$userSession,
102
-							$accountManager);
103
-
104
-		$this->appManager = $appManager;
105
-		$this->logger = $logger;
106
-		$this->l10nFactory = $l10nFactory;
107
-		$this->newUserMailHelper = $newUserMailHelper;
108
-		$this->federatedFileSharingFactory = $federatedFileSharingFactory;
109
-		$this->secureRandom = $secureRandom;
110
-	}
111
-
112
-	/**
113
-	 * @NoAdminRequired
114
-	 *
115
-	 * returns a list of users
116
-	 *
117
-	 * @param string $search
118
-	 * @param int $limit
119
-	 * @param int $offset
120
-	 * @return DataResponse
121
-	 */
122
-	public function getUsers(string $search = '', $limit = null, $offset = 0): DataResponse {
123
-		$user = $this->userSession->getUser();
124
-		$users = [];
125
-
126
-		// Admin? Or SubAdmin?
127
-		$uid = $user->getUID();
128
-		$subAdminManager = $this->groupManager->getSubAdmin();
129
-		if ($this->groupManager->isAdmin($uid)){
130
-			$users = $this->userManager->search($search, $limit, $offset);
131
-		} else if ($subAdminManager->isSubAdmin($user)) {
132
-			$subAdminOfGroups = $subAdminManager->getSubAdminsGroups($user);
133
-			foreach ($subAdminOfGroups as $key => $group) {
134
-				$subAdminOfGroups[$key] = $group->getGID();
135
-			}
136
-
137
-			$users = [];
138
-			foreach ($subAdminOfGroups as $group) {
139
-				$users = array_merge($users, $this->groupManager->displayNamesInGroup($group, $search, $limit, $offset));
140
-			}
141
-		}
142
-
143
-		$users = array_keys($users);
144
-
145
-		return new DataResponse([
146
-			'users' => $users
147
-		]);
148
-	}
149
-
150
-	/**
151
-	 * @NoAdminRequired
152
-	 *
153
-	 * returns a list of users and their data
154
-	 */
155
-	public function getUsersDetails(string $search = '', $limit = null, $offset = 0): DataResponse {
156
-		$user = $this->userSession->getUser();
157
-		$users = [];
158
-
159
-		// Admin? Or SubAdmin?
160
-		$uid = $user->getUID();
161
-		$subAdminManager = $this->groupManager->getSubAdmin();
162
-		if ($this->groupManager->isAdmin($uid)){
163
-			$users = $this->userManager->search($search, $limit, $offset);
164
-		} else if ($subAdminManager->isSubAdmin($user)) {
165
-			$subAdminOfGroups = $subAdminManager->getSubAdminsGroups($user);
166
-			foreach ($subAdminOfGroups as $key => $group) {
167
-				$subAdminOfGroups[$key] = $group->getGID();
168
-			}
169
-
170
-			$users = [];
171
-			foreach ($subAdminOfGroups as $group) {
172
-				$users = array_merge($users, $this->groupManager->displayNamesInGroup($group, $search, $limit, $offset));
173
-			}
174
-		}
175
-
176
-		$users = array_keys($users);
177
-		$usersDetails = [];
178
-		foreach ($users as $key => $userId) {
179
-			$userData = $this->getUserData($userId);
180
-			// Do not insert empty entry
181
-			if (!empty($userData)) {
182
-				$usersDetails[$userId] = $userData;
183
-			}
184
-		}
185
-
186
-		return new DataResponse([
187
-			'users' => $usersDetails
188
-		]);
189
-	}
190
-
191
-	/**
192
-	 * @PasswordConfirmationRequired
193
-	 * @NoAdminRequired
194
-	 *
195
-	 * @param string $userid
196
-	 * @param string $password
197
-	 * @param string $email
198
-	 * @param array $groups
199
-	 * @param array $subadmins
200
-	 * @param string $quota
201
-	 * @param string $language
202
-	 * @return DataResponse
203
-	 * @throws OCSException
204
-	 */
205
-	public function addUser(string $userid,
206
-							string $password = '',
207
-							string $email = '',
208
-							array $groups = [],
209
-							array $subadmin = [],
210
-							string $quota = '',
211
-							string $language = ''): DataResponse {
212
-		$user = $this->userSession->getUser();
213
-		$isAdmin = $this->groupManager->isAdmin($user->getUID());
214
-		$subAdminManager = $this->groupManager->getSubAdmin();
215
-
216
-		if ($this->userManager->userExists($userid)) {
217
-			$this->logger->error('Failed addUser attempt: User already exists.', ['app' => 'ocs_api']);
218
-			throw new OCSException('User already exists', 102);
219
-		}
220
-
221
-		if ($groups !== []) {
222
-			foreach ($groups as $group) {
223
-				if (!$this->groupManager->groupExists($group)) {
224
-					throw new OCSException('group '.$group.' does not exist', 104);
225
-				}
226
-				if (!$isAdmin && !$subAdminManager->isSubAdminOfGroup($user, $this->groupManager->get($group))) {
227
-					throw new OCSException('insufficient privileges for group '. $group, 105);
228
-				}
229
-			}
230
-		} else {
231
-			if (!$isAdmin) {
232
-				throw new OCSException('no group specified (required for subadmins)', 106);
233
-			}
234
-		}
235
-
236
-		$subadminGroups = [];
237
-		if ($subadmin !== []) {
238
-			foreach ($subadmin as $groupid) {
239
-				$group = $this->groupManager->get($groupid);
240
-				// Check if group exists
241
-				if ($group === null) {
242
-					throw new OCSException('Subadmin group does not exist',  102);
243
-				}
244
-				// Check if trying to make subadmin of admin group
245
-				if ($group->getGID() === 'admin') {
246
-					throw new OCSException('Cannot create subadmins for admin group', 103);
247
-				}
248
-				// Check if has permission to promote subadmins
249
-				if (!$subAdminManager->isSubAdminOfGroup($user, $group) && !$isAdmin) {
250
-					throw new OCSForbiddenException('No permissions to promote subadmins');
251
-				}
252
-				$subadminGroups[] = $group;
253
-			}
254
-		}
255
-
256
-		$generatePasswordResetToken = false;
257
-		if ($password === '') {
258
-			if ($email === '') {
259
-				throw new OCSException('To send a password link to the user an email address is required.', 108);
260
-			}
261
-
262
-			$password = $this->secureRandom->generate(10);
263
-			// Make sure we pass the password_policy
264
-			$password .= $this->secureRandom->generate(2, '$!.,;:-~+*[]{}()');
265
-			$generatePasswordResetToken = true;
266
-		}
267
-
268
-		try {
269
-			$newUser = $this->userManager->createUser($userid, $password);
270
-			$this->logger->info('Successful addUser call with userid: ' . $userid, ['app' => 'ocs_api']);
271
-
272
-			foreach ($groups as $group) {
273
-				$this->groupManager->get($group)->addUser($newUser);
274
-				$this->logger->info('Added userid ' . $userid . ' to group ' . $group, ['app' => 'ocs_api']);
275
-			}
276
-			foreach ($subadminGroups as $group) {
277
-				$subAdminManager->createSubAdmin($newUser, $group);
278
-			}
279
-
280
-			if ($quota !== '') {
281
-				$this->editUser($userid, 'quota', $quota);
282
-			}
283
-
284
-			if ($language !== '') {
285
-				$this->editUser($userid, 'language', $language);
286
-			}
287
-
288
-			// Send new user mail only if a mail is set
289
-			if ($email !== '') {
290
-				$newUser->setEMailAddress($email);
291
-				try {
292
-					$emailTemplate = $this->newUserMailHelper->generateTemplate($newUser, $generatePasswordResetToken);
293
-					$this->newUserMailHelper->sendMail($newUser, $emailTemplate);
294
-				} catch (\Exception $e) {
295
-					$this->logger->logException($e, [
296
-						'message' => "Can't send new user mail to $email",
297
-						'level' => ILogger::ERROR,
298
-						'app' => 'ocs_api',
299
-					]);
300
-					throw new OCSException('Unable to send the invitation mail', 109);
301
-				}
302
-			}
303
-
304
-			return new DataResponse();
305
-
306
-		} catch (HintException $e ) {
307
-			$this->logger->logException($e, [
308
-				'message' => 'Failed addUser attempt with hint exception.',
309
-				'level' => ILogger::WARN,
310
-				'app' => 'ocs_api',
311
-			]);
312
-			throw new OCSException($e->getHint(), 107);
313
-		} catch (\Exception $e) {
314
-			$this->logger->logException($e, [
315
-				'message' => 'Failed addUser attempt with exception.',
316
-				'level' => ILogger::ERROR,
317
-				'app' => 'ocs_api',
318
-			]);
319
-			throw new OCSException('Bad request', 101);
320
-		}
321
-	}
322
-
323
-	/**
324
-	 * @NoAdminRequired
325
-	 * @NoSubAdminRequired
326
-	 *
327
-	 * gets user info
328
-	 *
329
-	 * @param string $userId
330
-	 * @return DataResponse
331
-	 * @throws OCSException
332
-	 */
333
-	public function getUser(string $userId): DataResponse {
334
-		$data = $this->getUserData($userId);
335
-		// getUserData returns empty array if not enough permissions
336
-		if (empty($data)) {
337
-			throw new OCSException('', \OCP\API::RESPOND_UNAUTHORISED);
338
-		}
339
-		return new DataResponse($data);
340
-	}
341
-
342
-	/**
343
-	 * @NoAdminRequired
344
-	 * @NoSubAdminRequired
345
-	 *
346
-	 * gets user info from the currently logged in user
347
-	 *
348
-	 * @return DataResponse
349
-	 * @throws OCSException
350
-	 */
351
-	public function getCurrentUser(): DataResponse {
352
-		$user = $this->userSession->getUser();
353
-		if ($user) {
354
-			$data =  $this->getUserData($user->getUID());
355
-			// rename "displayname" to "display-name" only for this call to keep
356
-			// the API stable.
357
-			$data['display-name'] = $data['displayname'];
358
-			unset($data['displayname']);
359
-			return new DataResponse($data);
360
-
361
-		}
362
-
363
-		throw new OCSException('', \OCP\API::RESPOND_UNAUTHORISED);
364
-	}
365
-
366
-	/**
367
-	 * @NoAdminRequired
368
-	 * @NoSubAdminRequired
369
-	 */
370
-	public function getEditableFields(): DataResponse {
371
-		$permittedFields = [];
372
-
373
-		// Editing self (display, email)
374
-		if ($this->config->getSystemValue('allow_user_to_change_display_name', true) !== false) {
375
-			$permittedFields[] = AccountManager::PROPERTY_DISPLAYNAME;
376
-			$permittedFields[] = AccountManager::PROPERTY_EMAIL;
377
-		}
378
-
379
-		if ($this->appManager->isEnabledForUser('federatedfilesharing')) {
380
-			$federatedFileSharing = $this->federatedFileSharingFactory->get();
381
-			$shareProvider = $federatedFileSharing->getFederatedShareProvider();
382
-			if ($shareProvider->isLookupServerUploadEnabled()) {
383
-				$permittedFields[] = AccountManager::PROPERTY_PHONE;
384
-				$permittedFields[] = AccountManager::PROPERTY_ADDRESS;
385
-				$permittedFields[] = AccountManager::PROPERTY_WEBSITE;
386
-				$permittedFields[] = AccountManager::PROPERTY_TWITTER;
387
-			}
388
-		}
389
-
390
-		return new DataResponse($permittedFields);
391
-	}
392
-
393
-	/**
394
-	 * @NoAdminRequired
395
-	 * @NoSubAdminRequired
396
-	 * @PasswordConfirmationRequired
397
-	 *
398
-	 * edit users
399
-	 *
400
-	 * @param string $userId
401
-	 * @param string $key
402
-	 * @param string $value
403
-	 * @return DataResponse
404
-	 * @throws OCSException
405
-	 */
406
-	public function editUser(string $userId, string $key, string $value): DataResponse {
407
-		$currentLoggedInUser = $this->userSession->getUser();
408
-
409
-		$targetUser = $this->userManager->get($userId);
410
-		if ($targetUser === null) {
411
-			throw new OCSException('', \OCP\API::RESPOND_UNAUTHORISED);
412
-		}
413
-
414
-		$permittedFields = [];
415
-		if ($targetUser->getUID() === $currentLoggedInUser->getUID()) {
416
-			// Editing self (display, email)
417
-			if ($this->config->getSystemValue('allow_user_to_change_display_name', true) !== false) {
418
-				$permittedFields[] = 'display';
419
-				$permittedFields[] = AccountManager::PROPERTY_DISPLAYNAME;
420
-				$permittedFields[] = AccountManager::PROPERTY_EMAIL;
421
-			}
422
-
423
-			$permittedFields[] = 'password';
424
-			if ($this->config->getSystemValue('force_language', false) === false ||
425
-				$this->groupManager->isAdmin($currentLoggedInUser->getUID())) {
426
-				$permittedFields[] = 'language';
427
-			}
428
-
429
-			if ($this->appManager->isEnabledForUser('federatedfilesharing')) {
430
-				$federatedFileSharing = new \OCA\FederatedFileSharing\AppInfo\Application();
431
-				$shareProvider = $federatedFileSharing->getFederatedShareProvider();
432
-				if ($shareProvider->isLookupServerUploadEnabled()) {
433
-					$permittedFields[] = AccountManager::PROPERTY_PHONE;
434
-					$permittedFields[] = AccountManager::PROPERTY_ADDRESS;
435
-					$permittedFields[] = AccountManager::PROPERTY_WEBSITE;
436
-					$permittedFields[] = AccountManager::PROPERTY_TWITTER;
437
-				}
438
-			}
439
-
440
-			// If admin they can edit their own quota
441
-			if ($this->groupManager->isAdmin($currentLoggedInUser->getUID())) {
442
-				$permittedFields[] = 'quota';
443
-			}
444
-		} else {
445
-			// Check if admin / subadmin
446
-			$subAdminManager = $this->groupManager->getSubAdmin();
447
-			if ($subAdminManager->isUserAccessible($currentLoggedInUser, $targetUser)
448
-			|| $this->groupManager->isAdmin($currentLoggedInUser->getUID())) {
449
-				// They have permissions over the user
450
-				$permittedFields[] = 'display';
451
-				$permittedFields[] = AccountManager::PROPERTY_DISPLAYNAME;
452
-				$permittedFields[] = AccountManager::PROPERTY_EMAIL;
453
-				$permittedFields[] = 'password';
454
-				$permittedFields[] = 'language';
455
-				$permittedFields[] = AccountManager::PROPERTY_PHONE;
456
-				$permittedFields[] = AccountManager::PROPERTY_ADDRESS;
457
-				$permittedFields[] = AccountManager::PROPERTY_WEBSITE;
458
-				$permittedFields[] = AccountManager::PROPERTY_TWITTER;
459
-				$permittedFields[] = 'quota';
460
-			} else {
461
-				// No rights
462
-				throw new OCSException('', \OCP\API::RESPOND_UNAUTHORISED);
463
-			}
464
-		}
465
-		// Check if permitted to edit this field
466
-		if (!in_array($key, $permittedFields)) {
467
-			throw new OCSException('', \OCP\API::RESPOND_UNAUTHORISED);
468
-		}
469
-		// Process the edit
470
-		switch($key) {
471
-			case 'display':
472
-			case AccountManager::PROPERTY_DISPLAYNAME:
473
-				$targetUser->setDisplayName($value);
474
-				break;
475
-			case 'quota':
476
-				$quota = $value;
477
-				if ($quota !== 'none' && $quota !== 'default') {
478
-					if (is_numeric($quota)) {
479
-						$quota = (float) $quota;
480
-					} else {
481
-						$quota = \OCP\Util::computerFileSize($quota);
482
-					}
483
-					if ($quota === false) {
484
-						throw new OCSException('Invalid quota value '.$value, 103);
485
-					}
486
-					if ($quota === 0) {
487
-						$quota = 'default';
488
-					}else if ($quota === -1) {
489
-						$quota = 'none';
490
-					} else {
491
-						$quota = \OCP\Util::humanFileSize($quota);
492
-					}
493
-				}
494
-				$targetUser->setQuota($quota);
495
-				break;
496
-			case 'password':
497
-				$targetUser->setPassword($value);
498
-				break;
499
-			case 'language':
500
-				$languagesCodes = $this->l10nFactory->findAvailableLanguages();
501
-				if (!in_array($value, $languagesCodes, true) && $value !== 'en') {
502
-					throw new OCSException('Invalid language', 102);
503
-				}
504
-				$this->config->setUserValue($targetUser->getUID(), 'core', 'lang', $value);
505
-				break;
506
-			case AccountManager::PROPERTY_EMAIL:
507
-				if (filter_var($value, FILTER_VALIDATE_EMAIL) || $value === '') {
508
-					$targetUser->setEMailAddress($value);
509
-				} else {
510
-					throw new OCSException('', 102);
511
-				}
512
-				break;
513
-			case AccountManager::PROPERTY_PHONE:
514
-			case AccountManager::PROPERTY_ADDRESS:
515
-			case AccountManager::PROPERTY_WEBSITE:
516
-			case AccountManager::PROPERTY_TWITTER:
517
-				$userAccount = $this->accountManager->getUser($targetUser);
518
-				if ($userAccount[$key]['value'] !== $value) {
519
-					$userAccount[$key]['value'] = $value;
520
-					$this->accountManager->updateUser($targetUser, $userAccount);
521
-				}
522
-				break;
523
-			default:
524
-				throw new OCSException('', 103);
525
-		}
526
-		return new DataResponse();
527
-	}
528
-
529
-	/**
530
-	 * @PasswordConfirmationRequired
531
-	 * @NoAdminRequired
532
-	 *
533
-	 * @param string $userId
534
-	 * @return DataResponse
535
-	 * @throws OCSException
536
-	 */
537
-	public function deleteUser(string $userId): DataResponse {
538
-		$currentLoggedInUser = $this->userSession->getUser();
539
-
540
-		$targetUser = $this->userManager->get($userId);
541
-
542
-		if ($targetUser === null || $targetUser->getUID() === $currentLoggedInUser->getUID()) {
543
-			throw new OCSException('', 101);
544
-		}
545
-
546
-		// If not permitted
547
-		$subAdminManager = $this->groupManager->getSubAdmin();
548
-		if (!$this->groupManager->isAdmin($currentLoggedInUser->getUID()) && !$subAdminManager->isUserAccessible($currentLoggedInUser, $targetUser)) {
549
-			throw new OCSException('', \OCP\API::RESPOND_UNAUTHORISED);
550
-		}
551
-
552
-		// Go ahead with the delete
553
-		if ($targetUser->delete()) {
554
-			return new DataResponse();
555
-		} else {
556
-			throw new OCSException('', 101);
557
-		}
558
-	}
559
-
560
-	/**
561
-	 * @PasswordConfirmationRequired
562
-	 * @NoAdminRequired
563
-	 *
564
-	 * @param string $userId
565
-	 * @return DataResponse
566
-	 * @throws OCSException
567
-	 * @throws OCSForbiddenException
568
-	 */
569
-	public function disableUser(string $userId): DataResponse {
570
-		return $this->setEnabled($userId, false);
571
-	}
572
-
573
-	/**
574
-	 * @PasswordConfirmationRequired
575
-	 * @NoAdminRequired
576
-	 *
577
-	 * @param string $userId
578
-	 * @return DataResponse
579
-	 * @throws OCSException
580
-	 * @throws OCSForbiddenException
581
-	 */
582
-	public function enableUser(string $userId): DataResponse {
583
-		return $this->setEnabled($userId, true);
584
-	}
585
-
586
-	/**
587
-	 * @param string $userId
588
-	 * @param bool $value
589
-	 * @return DataResponse
590
-	 * @throws OCSException
591
-	 */
592
-	private function setEnabled(string $userId, bool $value): DataResponse {
593
-		$currentLoggedInUser = $this->userSession->getUser();
594
-
595
-		$targetUser = $this->userManager->get($userId);
596
-		if ($targetUser === null || $targetUser->getUID() === $currentLoggedInUser->getUID()) {
597
-			throw new OCSException('', 101);
598
-		}
599
-
600
-		// If not permitted
601
-		$subAdminManager = $this->groupManager->getSubAdmin();
602
-		if (!$this->groupManager->isAdmin($currentLoggedInUser->getUID()) && !$subAdminManager->isUserAccessible($currentLoggedInUser, $targetUser)) {
603
-			throw new OCSException('', \OCP\API::RESPOND_UNAUTHORISED);
604
-		}
605
-
606
-		// enable/disable the user now
607
-		$targetUser->setEnabled($value);
608
-		return new DataResponse();
609
-	}
610
-
611
-	/**
612
-	 * @NoAdminRequired
613
-	 * @NoSubAdminRequired
614
-	 *
615
-	 * @param string $userId
616
-	 * @return DataResponse
617
-	 * @throws OCSException
618
-	 */
619
-	public function getUsersGroups(string $userId): DataResponse {
620
-		$loggedInUser = $this->userSession->getUser();
621
-
622
-		$targetUser = $this->userManager->get($userId);
623
-		if ($targetUser === null) {
624
-			throw new OCSException('', \OCP\API::RESPOND_NOT_FOUND);
625
-		}
626
-
627
-		if ($targetUser->getUID() === $loggedInUser->getUID() || $this->groupManager->isAdmin($loggedInUser->getUID())) {
628
-			// Self lookup or admin lookup
629
-			return new DataResponse([
630
-				'groups' => $this->groupManager->getUserGroupIds($targetUser)
631
-			]);
632
-		} else {
633
-			$subAdminManager = $this->groupManager->getSubAdmin();
634
-
635
-			// Looking up someone else
636
-			if ($subAdminManager->isUserAccessible($loggedInUser, $targetUser)) {
637
-				// Return the group that the method caller is subadmin of for the user in question
638
-				/** @var IGroup[] $getSubAdminsGroups */
639
-				$getSubAdminsGroups = $subAdminManager->getSubAdminsGroups($loggedInUser);
640
-				foreach ($getSubAdminsGroups as $key => $group) {
641
-					$getSubAdminsGroups[$key] = $group->getGID();
642
-				}
643
-				$groups = array_intersect(
644
-					$getSubAdminsGroups,
645
-					$this->groupManager->getUserGroupIds($targetUser)
646
-				);
647
-				return new DataResponse(['groups' => $groups]);
648
-			} else {
649
-				// Not permitted
650
-				throw new OCSException('', \OCP\API::RESPOND_UNAUTHORISED);
651
-			}
652
-		}
653
-
654
-	}
655
-
656
-	/**
657
-	 * @PasswordConfirmationRequired
658
-	 * @NoAdminRequired
659
-	 *
660
-	 * @param string $userId
661
-	 * @param string $groupid
662
-	 * @return DataResponse
663
-	 * @throws OCSException
664
-	 */
665
-	public function addToGroup(string $userId, string $groupid = ''): DataResponse {
666
-		if ($groupid === '') {
667
-			throw new OCSException('', 101);
668
-		}
669
-
670
-		$group = $this->groupManager->get($groupid);
671
-		$targetUser = $this->userManager->get($userId);
672
-		if ($group === null) {
673
-			throw new OCSException('', 102);
674
-		}
675
-		if ($targetUser === null) {
676
-			throw new OCSException('', 103);
677
-		}
678
-
679
-		// If they're not an admin, check they are a subadmin of the group in question
680
-		$loggedInUser = $this->userSession->getUser();
681
-		$subAdminManager = $this->groupManager->getSubAdmin();
682
-		if (!$this->groupManager->isAdmin($loggedInUser->getUID()) && !$subAdminManager->isSubAdminOfGroup($loggedInUser, $group)) {
683
-			throw new OCSException('', 104);
684
-		}
685
-
686
-		// Add user to group
687
-		$group->addUser($targetUser);
688
-		return new DataResponse();
689
-	}
690
-
691
-	/**
692
-	 * @PasswordConfirmationRequired
693
-	 * @NoAdminRequired
694
-	 *
695
-	 * @param string $userId
696
-	 * @param string $groupid
697
-	 * @return DataResponse
698
-	 * @throws OCSException
699
-	 */
700
-	public function removeFromGroup(string $userId, string $groupid): DataResponse {
701
-		$loggedInUser = $this->userSession->getUser();
702
-
703
-		if ($groupid === null || trim($groupid) === '') {
704
-			throw new OCSException('', 101);
705
-		}
706
-
707
-		$group = $this->groupManager->get($groupid);
708
-		if ($group === null) {
709
-			throw new OCSException('', 102);
710
-		}
711
-
712
-		$targetUser = $this->userManager->get($userId);
713
-		if ($targetUser === null) {
714
-			throw new OCSException('', 103);
715
-		}
716
-
717
-		// If they're not an admin, check they are a subadmin of the group in question
718
-		$subAdminManager = $this->groupManager->getSubAdmin();
719
-		if (!$this->groupManager->isAdmin($loggedInUser->getUID()) && !$subAdminManager->isSubAdminOfGroup($loggedInUser, $group)) {
720
-			throw new OCSException('', 104);
721
-		}
722
-
723
-		// Check they aren't removing themselves from 'admin' or their 'subadmin; group
724
-		if ($targetUser->getUID() === $loggedInUser->getUID()) {
725
-			if ($this->groupManager->isAdmin($loggedInUser->getUID())) {
726
-				if ($group->getGID() === 'admin') {
727
-					throw new OCSException('Cannot remove yourself from the admin group', 105);
728
-				}
729
-			} else {
730
-				// Not an admin, so the user must be a subadmin of this group, but that is not allowed.
731
-				throw new OCSException('Cannot remove yourself from this group as you are a SubAdmin', 105);
732
-			}
733
-
734
-		} else if (!$this->groupManager->isAdmin($loggedInUser->getUID())) {
735
-			/** @var IGroup[] $subAdminGroups */
736
-			$subAdminGroups = $subAdminManager->getSubAdminsGroups($loggedInUser);
737
-			$subAdminGroups = array_map(function (IGroup $subAdminGroup) {
738
-				return $subAdminGroup->getGID();
739
-			}, $subAdminGroups);
740
-			$userGroups = $this->groupManager->getUserGroupIds($targetUser);
741
-			$userSubAdminGroups = array_intersect($subAdminGroups, $userGroups);
742
-
743
-			if (count($userSubAdminGroups) <= 1) {
744
-				// Subadmin must not be able to remove a user from all their subadmin groups.
745
-				throw new OCSException('Cannot remove user from this group as this is the only remaining group you are a SubAdmin of', 105);
746
-			}
747
-		}
748
-
749
-		// Remove user from group
750
-		$group->removeUser($targetUser);
751
-		return new DataResponse();
752
-	}
753
-
754
-	/**
755
-	 * Creates a subadmin
756
-	 *
757
-	 * @PasswordConfirmationRequired
758
-	 *
759
-	 * @param string $userId
760
-	 * @param string $groupid
761
-	 * @return DataResponse
762
-	 * @throws OCSException
763
-	 */
764
-	public function addSubAdmin(string $userId, string $groupid): DataResponse {
765
-		$group = $this->groupManager->get($groupid);
766
-		$user = $this->userManager->get($userId);
767
-
768
-		// Check if the user exists
769
-		if ($user === null) {
770
-			throw new OCSException('User does not exist', 101);
771
-		}
772
-		// Check if group exists
773
-		if ($group === null) {
774
-			throw new OCSException('Group does not exist',  102);
775
-		}
776
-		// Check if trying to make subadmin of admin group
777
-		if ($group->getGID() === 'admin') {
778
-			throw new OCSException('Cannot create subadmins for admin group', 103);
779
-		}
780
-
781
-		$subAdminManager = $this->groupManager->getSubAdmin();
782
-
783
-		// We cannot be subadmin twice
784
-		if ($subAdminManager->isSubAdminOfGroup($user, $group)) {
785
-			return new DataResponse();
786
-		}
787
-		// Go
788
-		if ($subAdminManager->createSubAdmin($user, $group)) {
789
-			return new DataResponse();
790
-		} else {
791
-			throw new OCSException('Unknown error occurred', 103);
792
-		}
793
-	}
794
-
795
-	/**
796
-	 * Removes a subadmin from a group
797
-	 *
798
-	 * @PasswordConfirmationRequired
799
-	 *
800
-	 * @param string $userId
801
-	 * @param string $groupid
802
-	 * @return DataResponse
803
-	 * @throws OCSException
804
-	 */
805
-	public function removeSubAdmin(string $userId, string $groupid): DataResponse {
806
-		$group = $this->groupManager->get($groupid);
807
-		$user = $this->userManager->get($userId);
808
-		$subAdminManager = $this->groupManager->getSubAdmin();
809
-
810
-		// Check if the user exists
811
-		if ($user === null) {
812
-			throw new OCSException('User does not exist', 101);
813
-		}
814
-		// Check if the group exists
815
-		if ($group === null) {
816
-			throw new OCSException('Group does not exist', 101);
817
-		}
818
-		// Check if they are a subadmin of this said group
819
-		if (!$subAdminManager->isSubAdminOfGroup($user, $group)) {
820
-			throw new OCSException('User is not a subadmin of this group', 102);
821
-		}
822
-
823
-		// Go
824
-		if ($subAdminManager->deleteSubAdmin($user, $group)) {
825
-			return new DataResponse();
826
-		} else {
827
-			throw new OCSException('Unknown error occurred', 103);
828
-		}
829
-	}
830
-
831
-	/**
832
-	 * Get the groups a user is a subadmin of
833
-	 *
834
-	 * @param string $userId
835
-	 * @return DataResponse
836
-	 * @throws OCSException
837
-	 */
838
-	public function getUserSubAdminGroups(string $userId): DataResponse {
839
-		$groups = $this->getUserSubAdminGroupsData($userId);
840
-		return new DataResponse($groups);
841
-	}
842
-
843
-	/**
844
-	 * @NoAdminRequired
845
-	 * @PasswordConfirmationRequired
846
-	 *
847
-	 * resend welcome message
848
-	 *
849
-	 * @param string $userId
850
-	 * @return DataResponse
851
-	 * @throws OCSException
852
-	 */
853
-	public function resendWelcomeMessage(string $userId): DataResponse {
854
-		$currentLoggedInUser = $this->userSession->getUser();
855
-
856
-		$targetUser = $this->userManager->get($userId);
857
-		if ($targetUser === null) {
858
-			throw new OCSException('', \OCP\API::RESPOND_NOT_FOUND);
859
-		}
860
-
861
-		// Check if admin / subadmin
862
-		$subAdminManager = $this->groupManager->getSubAdmin();
863
-		if (!$subAdminManager->isUserAccessible($currentLoggedInUser, $targetUser)
864
-			&& !$this->groupManager->isAdmin($currentLoggedInUser->getUID())) {
865
-			// No rights
866
-			throw new OCSException('', \OCP\API::RESPOND_UNAUTHORISED);
867
-		}
868
-
869
-		$email = $targetUser->getEMailAddress();
870
-		if ($email === '' || $email === null) {
871
-			throw new OCSException('Email address not available', 101);
872
-		}
873
-		$username = $targetUser->getUID();
874
-		$lang = $this->config->getUserValue($username, 'core', 'lang', 'en');
875
-		if (!$this->l10nFactory->languageExists('settings', $lang)) {
876
-			$lang = 'en';
877
-		}
878
-
879
-		$l10n = $this->l10nFactory->get('settings', $lang);
880
-
881
-		try {
882
-			$this->newUserMailHelper->setL10N($l10n);
883
-			$emailTemplate = $this->newUserMailHelper->generateTemplate($targetUser, false);
884
-			$this->newUserMailHelper->sendMail($targetUser, $emailTemplate);
885
-		} catch(\Exception $e) {
886
-			$this->logger->logException($e, [
887
-				'message' => "Can't send new user mail to $email",
888
-				'level' => ILogger::ERROR,
889
-				'app' => 'settings',
890
-			]);
891
-			throw new OCSException('Sending email failed', 102);
892
-		}
893
-
894
-		return new DataResponse();
895
-	}
55
+    /** @var IAppManager */
56
+    private $appManager;
57
+    /** @var ILogger */
58
+    private $logger;
59
+    /** @var IFactory */
60
+    private $l10nFactory;
61
+    /** @var NewUserMailHelper */
62
+    private $newUserMailHelper;
63
+    /** @var FederatedFileSharingFactory */
64
+    private $federatedFileSharingFactory;
65
+    /** @var ISecureRandom */
66
+    private $secureRandom;
67
+
68
+    /**
69
+     * @param string $appName
70
+     * @param IRequest $request
71
+     * @param IUserManager $userManager
72
+     * @param IConfig $config
73
+     * @param IAppManager $appManager
74
+     * @param IGroupManager $groupManager
75
+     * @param IUserSession $userSession
76
+     * @param AccountManager $accountManager
77
+     * @param ILogger $logger
78
+     * @param IFactory $l10nFactory
79
+     * @param NewUserMailHelper $newUserMailHelper
80
+     * @param FederatedFileSharingFactory $federatedFileSharingFactory
81
+     * @param ISecureRandom $secureRandom
82
+     */
83
+    public function __construct(string $appName,
84
+                                IRequest $request,
85
+                                IUserManager $userManager,
86
+                                IConfig $config,
87
+                                IAppManager $appManager,
88
+                                IGroupManager $groupManager,
89
+                                IUserSession $userSession,
90
+                                AccountManager $accountManager,
91
+                                ILogger $logger,
92
+                                IFactory $l10nFactory,
93
+                                NewUserMailHelper $newUserMailHelper,
94
+                                FederatedFileSharingFactory $federatedFileSharingFactory,
95
+                                ISecureRandom $secureRandom) {
96
+        parent::__construct($appName,
97
+                            $request,
98
+                            $userManager,
99
+                            $config,
100
+                            $groupManager,
101
+                            $userSession,
102
+                            $accountManager);
103
+
104
+        $this->appManager = $appManager;
105
+        $this->logger = $logger;
106
+        $this->l10nFactory = $l10nFactory;
107
+        $this->newUserMailHelper = $newUserMailHelper;
108
+        $this->federatedFileSharingFactory = $federatedFileSharingFactory;
109
+        $this->secureRandom = $secureRandom;
110
+    }
111
+
112
+    /**
113
+     * @NoAdminRequired
114
+     *
115
+     * returns a list of users
116
+     *
117
+     * @param string $search
118
+     * @param int $limit
119
+     * @param int $offset
120
+     * @return DataResponse
121
+     */
122
+    public function getUsers(string $search = '', $limit = null, $offset = 0): DataResponse {
123
+        $user = $this->userSession->getUser();
124
+        $users = [];
125
+
126
+        // Admin? Or SubAdmin?
127
+        $uid = $user->getUID();
128
+        $subAdminManager = $this->groupManager->getSubAdmin();
129
+        if ($this->groupManager->isAdmin($uid)){
130
+            $users = $this->userManager->search($search, $limit, $offset);
131
+        } else if ($subAdminManager->isSubAdmin($user)) {
132
+            $subAdminOfGroups = $subAdminManager->getSubAdminsGroups($user);
133
+            foreach ($subAdminOfGroups as $key => $group) {
134
+                $subAdminOfGroups[$key] = $group->getGID();
135
+            }
136
+
137
+            $users = [];
138
+            foreach ($subAdminOfGroups as $group) {
139
+                $users = array_merge($users, $this->groupManager->displayNamesInGroup($group, $search, $limit, $offset));
140
+            }
141
+        }
142
+
143
+        $users = array_keys($users);
144
+
145
+        return new DataResponse([
146
+            'users' => $users
147
+        ]);
148
+    }
149
+
150
+    /**
151
+     * @NoAdminRequired
152
+     *
153
+     * returns a list of users and their data
154
+     */
155
+    public function getUsersDetails(string $search = '', $limit = null, $offset = 0): DataResponse {
156
+        $user = $this->userSession->getUser();
157
+        $users = [];
158
+
159
+        // Admin? Or SubAdmin?
160
+        $uid = $user->getUID();
161
+        $subAdminManager = $this->groupManager->getSubAdmin();
162
+        if ($this->groupManager->isAdmin($uid)){
163
+            $users = $this->userManager->search($search, $limit, $offset);
164
+        } else if ($subAdminManager->isSubAdmin($user)) {
165
+            $subAdminOfGroups = $subAdminManager->getSubAdminsGroups($user);
166
+            foreach ($subAdminOfGroups as $key => $group) {
167
+                $subAdminOfGroups[$key] = $group->getGID();
168
+            }
169
+
170
+            $users = [];
171
+            foreach ($subAdminOfGroups as $group) {
172
+                $users = array_merge($users, $this->groupManager->displayNamesInGroup($group, $search, $limit, $offset));
173
+            }
174
+        }
175
+
176
+        $users = array_keys($users);
177
+        $usersDetails = [];
178
+        foreach ($users as $key => $userId) {
179
+            $userData = $this->getUserData($userId);
180
+            // Do not insert empty entry
181
+            if (!empty($userData)) {
182
+                $usersDetails[$userId] = $userData;
183
+            }
184
+        }
185
+
186
+        return new DataResponse([
187
+            'users' => $usersDetails
188
+        ]);
189
+    }
190
+
191
+    /**
192
+     * @PasswordConfirmationRequired
193
+     * @NoAdminRequired
194
+     *
195
+     * @param string $userid
196
+     * @param string $password
197
+     * @param string $email
198
+     * @param array $groups
199
+     * @param array $subadmins
200
+     * @param string $quota
201
+     * @param string $language
202
+     * @return DataResponse
203
+     * @throws OCSException
204
+     */
205
+    public function addUser(string $userid,
206
+                            string $password = '',
207
+                            string $email = '',
208
+                            array $groups = [],
209
+                            array $subadmin = [],
210
+                            string $quota = '',
211
+                            string $language = ''): DataResponse {
212
+        $user = $this->userSession->getUser();
213
+        $isAdmin = $this->groupManager->isAdmin($user->getUID());
214
+        $subAdminManager = $this->groupManager->getSubAdmin();
215
+
216
+        if ($this->userManager->userExists($userid)) {
217
+            $this->logger->error('Failed addUser attempt: User already exists.', ['app' => 'ocs_api']);
218
+            throw new OCSException('User already exists', 102);
219
+        }
220
+
221
+        if ($groups !== []) {
222
+            foreach ($groups as $group) {
223
+                if (!$this->groupManager->groupExists($group)) {
224
+                    throw new OCSException('group '.$group.' does not exist', 104);
225
+                }
226
+                if (!$isAdmin && !$subAdminManager->isSubAdminOfGroup($user, $this->groupManager->get($group))) {
227
+                    throw new OCSException('insufficient privileges for group '. $group, 105);
228
+                }
229
+            }
230
+        } else {
231
+            if (!$isAdmin) {
232
+                throw new OCSException('no group specified (required for subadmins)', 106);
233
+            }
234
+        }
235
+
236
+        $subadminGroups = [];
237
+        if ($subadmin !== []) {
238
+            foreach ($subadmin as $groupid) {
239
+                $group = $this->groupManager->get($groupid);
240
+                // Check if group exists
241
+                if ($group === null) {
242
+                    throw new OCSException('Subadmin group does not exist',  102);
243
+                }
244
+                // Check if trying to make subadmin of admin group
245
+                if ($group->getGID() === 'admin') {
246
+                    throw new OCSException('Cannot create subadmins for admin group', 103);
247
+                }
248
+                // Check if has permission to promote subadmins
249
+                if (!$subAdminManager->isSubAdminOfGroup($user, $group) && !$isAdmin) {
250
+                    throw new OCSForbiddenException('No permissions to promote subadmins');
251
+                }
252
+                $subadminGroups[] = $group;
253
+            }
254
+        }
255
+
256
+        $generatePasswordResetToken = false;
257
+        if ($password === '') {
258
+            if ($email === '') {
259
+                throw new OCSException('To send a password link to the user an email address is required.', 108);
260
+            }
261
+
262
+            $password = $this->secureRandom->generate(10);
263
+            // Make sure we pass the password_policy
264
+            $password .= $this->secureRandom->generate(2, '$!.,;:-~+*[]{}()');
265
+            $generatePasswordResetToken = true;
266
+        }
267
+
268
+        try {
269
+            $newUser = $this->userManager->createUser($userid, $password);
270
+            $this->logger->info('Successful addUser call with userid: ' . $userid, ['app' => 'ocs_api']);
271
+
272
+            foreach ($groups as $group) {
273
+                $this->groupManager->get($group)->addUser($newUser);
274
+                $this->logger->info('Added userid ' . $userid . ' to group ' . $group, ['app' => 'ocs_api']);
275
+            }
276
+            foreach ($subadminGroups as $group) {
277
+                $subAdminManager->createSubAdmin($newUser, $group);
278
+            }
279
+
280
+            if ($quota !== '') {
281
+                $this->editUser($userid, 'quota', $quota);
282
+            }
283
+
284
+            if ($language !== '') {
285
+                $this->editUser($userid, 'language', $language);
286
+            }
287
+
288
+            // Send new user mail only if a mail is set
289
+            if ($email !== '') {
290
+                $newUser->setEMailAddress($email);
291
+                try {
292
+                    $emailTemplate = $this->newUserMailHelper->generateTemplate($newUser, $generatePasswordResetToken);
293
+                    $this->newUserMailHelper->sendMail($newUser, $emailTemplate);
294
+                } catch (\Exception $e) {
295
+                    $this->logger->logException($e, [
296
+                        'message' => "Can't send new user mail to $email",
297
+                        'level' => ILogger::ERROR,
298
+                        'app' => 'ocs_api',
299
+                    ]);
300
+                    throw new OCSException('Unable to send the invitation mail', 109);
301
+                }
302
+            }
303
+
304
+            return new DataResponse();
305
+
306
+        } catch (HintException $e ) {
307
+            $this->logger->logException($e, [
308
+                'message' => 'Failed addUser attempt with hint exception.',
309
+                'level' => ILogger::WARN,
310
+                'app' => 'ocs_api',
311
+            ]);
312
+            throw new OCSException($e->getHint(), 107);
313
+        } catch (\Exception $e) {
314
+            $this->logger->logException($e, [
315
+                'message' => 'Failed addUser attempt with exception.',
316
+                'level' => ILogger::ERROR,
317
+                'app' => 'ocs_api',
318
+            ]);
319
+            throw new OCSException('Bad request', 101);
320
+        }
321
+    }
322
+
323
+    /**
324
+     * @NoAdminRequired
325
+     * @NoSubAdminRequired
326
+     *
327
+     * gets user info
328
+     *
329
+     * @param string $userId
330
+     * @return DataResponse
331
+     * @throws OCSException
332
+     */
333
+    public function getUser(string $userId): DataResponse {
334
+        $data = $this->getUserData($userId);
335
+        // getUserData returns empty array if not enough permissions
336
+        if (empty($data)) {
337
+            throw new OCSException('', \OCP\API::RESPOND_UNAUTHORISED);
338
+        }
339
+        return new DataResponse($data);
340
+    }
341
+
342
+    /**
343
+     * @NoAdminRequired
344
+     * @NoSubAdminRequired
345
+     *
346
+     * gets user info from the currently logged in user
347
+     *
348
+     * @return DataResponse
349
+     * @throws OCSException
350
+     */
351
+    public function getCurrentUser(): DataResponse {
352
+        $user = $this->userSession->getUser();
353
+        if ($user) {
354
+            $data =  $this->getUserData($user->getUID());
355
+            // rename "displayname" to "display-name" only for this call to keep
356
+            // the API stable.
357
+            $data['display-name'] = $data['displayname'];
358
+            unset($data['displayname']);
359
+            return new DataResponse($data);
360
+
361
+        }
362
+
363
+        throw new OCSException('', \OCP\API::RESPOND_UNAUTHORISED);
364
+    }
365
+
366
+    /**
367
+     * @NoAdminRequired
368
+     * @NoSubAdminRequired
369
+     */
370
+    public function getEditableFields(): DataResponse {
371
+        $permittedFields = [];
372
+
373
+        // Editing self (display, email)
374
+        if ($this->config->getSystemValue('allow_user_to_change_display_name', true) !== false) {
375
+            $permittedFields[] = AccountManager::PROPERTY_DISPLAYNAME;
376
+            $permittedFields[] = AccountManager::PROPERTY_EMAIL;
377
+        }
378
+
379
+        if ($this->appManager->isEnabledForUser('federatedfilesharing')) {
380
+            $federatedFileSharing = $this->federatedFileSharingFactory->get();
381
+            $shareProvider = $federatedFileSharing->getFederatedShareProvider();
382
+            if ($shareProvider->isLookupServerUploadEnabled()) {
383
+                $permittedFields[] = AccountManager::PROPERTY_PHONE;
384
+                $permittedFields[] = AccountManager::PROPERTY_ADDRESS;
385
+                $permittedFields[] = AccountManager::PROPERTY_WEBSITE;
386
+                $permittedFields[] = AccountManager::PROPERTY_TWITTER;
387
+            }
388
+        }
389
+
390
+        return new DataResponse($permittedFields);
391
+    }
392
+
393
+    /**
394
+     * @NoAdminRequired
395
+     * @NoSubAdminRequired
396
+     * @PasswordConfirmationRequired
397
+     *
398
+     * edit users
399
+     *
400
+     * @param string $userId
401
+     * @param string $key
402
+     * @param string $value
403
+     * @return DataResponse
404
+     * @throws OCSException
405
+     */
406
+    public function editUser(string $userId, string $key, string $value): DataResponse {
407
+        $currentLoggedInUser = $this->userSession->getUser();
408
+
409
+        $targetUser = $this->userManager->get($userId);
410
+        if ($targetUser === null) {
411
+            throw new OCSException('', \OCP\API::RESPOND_UNAUTHORISED);
412
+        }
413
+
414
+        $permittedFields = [];
415
+        if ($targetUser->getUID() === $currentLoggedInUser->getUID()) {
416
+            // Editing self (display, email)
417
+            if ($this->config->getSystemValue('allow_user_to_change_display_name', true) !== false) {
418
+                $permittedFields[] = 'display';
419
+                $permittedFields[] = AccountManager::PROPERTY_DISPLAYNAME;
420
+                $permittedFields[] = AccountManager::PROPERTY_EMAIL;
421
+            }
422
+
423
+            $permittedFields[] = 'password';
424
+            if ($this->config->getSystemValue('force_language', false) === false ||
425
+                $this->groupManager->isAdmin($currentLoggedInUser->getUID())) {
426
+                $permittedFields[] = 'language';
427
+            }
428
+
429
+            if ($this->appManager->isEnabledForUser('federatedfilesharing')) {
430
+                $federatedFileSharing = new \OCA\FederatedFileSharing\AppInfo\Application();
431
+                $shareProvider = $federatedFileSharing->getFederatedShareProvider();
432
+                if ($shareProvider->isLookupServerUploadEnabled()) {
433
+                    $permittedFields[] = AccountManager::PROPERTY_PHONE;
434
+                    $permittedFields[] = AccountManager::PROPERTY_ADDRESS;
435
+                    $permittedFields[] = AccountManager::PROPERTY_WEBSITE;
436
+                    $permittedFields[] = AccountManager::PROPERTY_TWITTER;
437
+                }
438
+            }
439
+
440
+            // If admin they can edit their own quota
441
+            if ($this->groupManager->isAdmin($currentLoggedInUser->getUID())) {
442
+                $permittedFields[] = 'quota';
443
+            }
444
+        } else {
445
+            // Check if admin / subadmin
446
+            $subAdminManager = $this->groupManager->getSubAdmin();
447
+            if ($subAdminManager->isUserAccessible($currentLoggedInUser, $targetUser)
448
+            || $this->groupManager->isAdmin($currentLoggedInUser->getUID())) {
449
+                // They have permissions over the user
450
+                $permittedFields[] = 'display';
451
+                $permittedFields[] = AccountManager::PROPERTY_DISPLAYNAME;
452
+                $permittedFields[] = AccountManager::PROPERTY_EMAIL;
453
+                $permittedFields[] = 'password';
454
+                $permittedFields[] = 'language';
455
+                $permittedFields[] = AccountManager::PROPERTY_PHONE;
456
+                $permittedFields[] = AccountManager::PROPERTY_ADDRESS;
457
+                $permittedFields[] = AccountManager::PROPERTY_WEBSITE;
458
+                $permittedFields[] = AccountManager::PROPERTY_TWITTER;
459
+                $permittedFields[] = 'quota';
460
+            } else {
461
+                // No rights
462
+                throw new OCSException('', \OCP\API::RESPOND_UNAUTHORISED);
463
+            }
464
+        }
465
+        // Check if permitted to edit this field
466
+        if (!in_array($key, $permittedFields)) {
467
+            throw new OCSException('', \OCP\API::RESPOND_UNAUTHORISED);
468
+        }
469
+        // Process the edit
470
+        switch($key) {
471
+            case 'display':
472
+            case AccountManager::PROPERTY_DISPLAYNAME:
473
+                $targetUser->setDisplayName($value);
474
+                break;
475
+            case 'quota':
476
+                $quota = $value;
477
+                if ($quota !== 'none' && $quota !== 'default') {
478
+                    if (is_numeric($quota)) {
479
+                        $quota = (float) $quota;
480
+                    } else {
481
+                        $quota = \OCP\Util::computerFileSize($quota);
482
+                    }
483
+                    if ($quota === false) {
484
+                        throw new OCSException('Invalid quota value '.$value, 103);
485
+                    }
486
+                    if ($quota === 0) {
487
+                        $quota = 'default';
488
+                    }else if ($quota === -1) {
489
+                        $quota = 'none';
490
+                    } else {
491
+                        $quota = \OCP\Util::humanFileSize($quota);
492
+                    }
493
+                }
494
+                $targetUser->setQuota($quota);
495
+                break;
496
+            case 'password':
497
+                $targetUser->setPassword($value);
498
+                break;
499
+            case 'language':
500
+                $languagesCodes = $this->l10nFactory->findAvailableLanguages();
501
+                if (!in_array($value, $languagesCodes, true) && $value !== 'en') {
502
+                    throw new OCSException('Invalid language', 102);
503
+                }
504
+                $this->config->setUserValue($targetUser->getUID(), 'core', 'lang', $value);
505
+                break;
506
+            case AccountManager::PROPERTY_EMAIL:
507
+                if (filter_var($value, FILTER_VALIDATE_EMAIL) || $value === '') {
508
+                    $targetUser->setEMailAddress($value);
509
+                } else {
510
+                    throw new OCSException('', 102);
511
+                }
512
+                break;
513
+            case AccountManager::PROPERTY_PHONE:
514
+            case AccountManager::PROPERTY_ADDRESS:
515
+            case AccountManager::PROPERTY_WEBSITE:
516
+            case AccountManager::PROPERTY_TWITTER:
517
+                $userAccount = $this->accountManager->getUser($targetUser);
518
+                if ($userAccount[$key]['value'] !== $value) {
519
+                    $userAccount[$key]['value'] = $value;
520
+                    $this->accountManager->updateUser($targetUser, $userAccount);
521
+                }
522
+                break;
523
+            default:
524
+                throw new OCSException('', 103);
525
+        }
526
+        return new DataResponse();
527
+    }
528
+
529
+    /**
530
+     * @PasswordConfirmationRequired
531
+     * @NoAdminRequired
532
+     *
533
+     * @param string $userId
534
+     * @return DataResponse
535
+     * @throws OCSException
536
+     */
537
+    public function deleteUser(string $userId): DataResponse {
538
+        $currentLoggedInUser = $this->userSession->getUser();
539
+
540
+        $targetUser = $this->userManager->get($userId);
541
+
542
+        if ($targetUser === null || $targetUser->getUID() === $currentLoggedInUser->getUID()) {
543
+            throw new OCSException('', 101);
544
+        }
545
+
546
+        // If not permitted
547
+        $subAdminManager = $this->groupManager->getSubAdmin();
548
+        if (!$this->groupManager->isAdmin($currentLoggedInUser->getUID()) && !$subAdminManager->isUserAccessible($currentLoggedInUser, $targetUser)) {
549
+            throw new OCSException('', \OCP\API::RESPOND_UNAUTHORISED);
550
+        }
551
+
552
+        // Go ahead with the delete
553
+        if ($targetUser->delete()) {
554
+            return new DataResponse();
555
+        } else {
556
+            throw new OCSException('', 101);
557
+        }
558
+    }
559
+
560
+    /**
561
+     * @PasswordConfirmationRequired
562
+     * @NoAdminRequired
563
+     *
564
+     * @param string $userId
565
+     * @return DataResponse
566
+     * @throws OCSException
567
+     * @throws OCSForbiddenException
568
+     */
569
+    public function disableUser(string $userId): DataResponse {
570
+        return $this->setEnabled($userId, false);
571
+    }
572
+
573
+    /**
574
+     * @PasswordConfirmationRequired
575
+     * @NoAdminRequired
576
+     *
577
+     * @param string $userId
578
+     * @return DataResponse
579
+     * @throws OCSException
580
+     * @throws OCSForbiddenException
581
+     */
582
+    public function enableUser(string $userId): DataResponse {
583
+        return $this->setEnabled($userId, true);
584
+    }
585
+
586
+    /**
587
+     * @param string $userId
588
+     * @param bool $value
589
+     * @return DataResponse
590
+     * @throws OCSException
591
+     */
592
+    private function setEnabled(string $userId, bool $value): DataResponse {
593
+        $currentLoggedInUser = $this->userSession->getUser();
594
+
595
+        $targetUser = $this->userManager->get($userId);
596
+        if ($targetUser === null || $targetUser->getUID() === $currentLoggedInUser->getUID()) {
597
+            throw new OCSException('', 101);
598
+        }
599
+
600
+        // If not permitted
601
+        $subAdminManager = $this->groupManager->getSubAdmin();
602
+        if (!$this->groupManager->isAdmin($currentLoggedInUser->getUID()) && !$subAdminManager->isUserAccessible($currentLoggedInUser, $targetUser)) {
603
+            throw new OCSException('', \OCP\API::RESPOND_UNAUTHORISED);
604
+        }
605
+
606
+        // enable/disable the user now
607
+        $targetUser->setEnabled($value);
608
+        return new DataResponse();
609
+    }
610
+
611
+    /**
612
+     * @NoAdminRequired
613
+     * @NoSubAdminRequired
614
+     *
615
+     * @param string $userId
616
+     * @return DataResponse
617
+     * @throws OCSException
618
+     */
619
+    public function getUsersGroups(string $userId): DataResponse {
620
+        $loggedInUser = $this->userSession->getUser();
621
+
622
+        $targetUser = $this->userManager->get($userId);
623
+        if ($targetUser === null) {
624
+            throw new OCSException('', \OCP\API::RESPOND_NOT_FOUND);
625
+        }
626
+
627
+        if ($targetUser->getUID() === $loggedInUser->getUID() || $this->groupManager->isAdmin($loggedInUser->getUID())) {
628
+            // Self lookup or admin lookup
629
+            return new DataResponse([
630
+                'groups' => $this->groupManager->getUserGroupIds($targetUser)
631
+            ]);
632
+        } else {
633
+            $subAdminManager = $this->groupManager->getSubAdmin();
634
+
635
+            // Looking up someone else
636
+            if ($subAdminManager->isUserAccessible($loggedInUser, $targetUser)) {
637
+                // Return the group that the method caller is subadmin of for the user in question
638
+                /** @var IGroup[] $getSubAdminsGroups */
639
+                $getSubAdminsGroups = $subAdminManager->getSubAdminsGroups($loggedInUser);
640
+                foreach ($getSubAdminsGroups as $key => $group) {
641
+                    $getSubAdminsGroups[$key] = $group->getGID();
642
+                }
643
+                $groups = array_intersect(
644
+                    $getSubAdminsGroups,
645
+                    $this->groupManager->getUserGroupIds($targetUser)
646
+                );
647
+                return new DataResponse(['groups' => $groups]);
648
+            } else {
649
+                // Not permitted
650
+                throw new OCSException('', \OCP\API::RESPOND_UNAUTHORISED);
651
+            }
652
+        }
653
+
654
+    }
655
+
656
+    /**
657
+     * @PasswordConfirmationRequired
658
+     * @NoAdminRequired
659
+     *
660
+     * @param string $userId
661
+     * @param string $groupid
662
+     * @return DataResponse
663
+     * @throws OCSException
664
+     */
665
+    public function addToGroup(string $userId, string $groupid = ''): DataResponse {
666
+        if ($groupid === '') {
667
+            throw new OCSException('', 101);
668
+        }
669
+
670
+        $group = $this->groupManager->get($groupid);
671
+        $targetUser = $this->userManager->get($userId);
672
+        if ($group === null) {
673
+            throw new OCSException('', 102);
674
+        }
675
+        if ($targetUser === null) {
676
+            throw new OCSException('', 103);
677
+        }
678
+
679
+        // If they're not an admin, check they are a subadmin of the group in question
680
+        $loggedInUser = $this->userSession->getUser();
681
+        $subAdminManager = $this->groupManager->getSubAdmin();
682
+        if (!$this->groupManager->isAdmin($loggedInUser->getUID()) && !$subAdminManager->isSubAdminOfGroup($loggedInUser, $group)) {
683
+            throw new OCSException('', 104);
684
+        }
685
+
686
+        // Add user to group
687
+        $group->addUser($targetUser);
688
+        return new DataResponse();
689
+    }
690
+
691
+    /**
692
+     * @PasswordConfirmationRequired
693
+     * @NoAdminRequired
694
+     *
695
+     * @param string $userId
696
+     * @param string $groupid
697
+     * @return DataResponse
698
+     * @throws OCSException
699
+     */
700
+    public function removeFromGroup(string $userId, string $groupid): DataResponse {
701
+        $loggedInUser = $this->userSession->getUser();
702
+
703
+        if ($groupid === null || trim($groupid) === '') {
704
+            throw new OCSException('', 101);
705
+        }
706
+
707
+        $group = $this->groupManager->get($groupid);
708
+        if ($group === null) {
709
+            throw new OCSException('', 102);
710
+        }
711
+
712
+        $targetUser = $this->userManager->get($userId);
713
+        if ($targetUser === null) {
714
+            throw new OCSException('', 103);
715
+        }
716
+
717
+        // If they're not an admin, check they are a subadmin of the group in question
718
+        $subAdminManager = $this->groupManager->getSubAdmin();
719
+        if (!$this->groupManager->isAdmin($loggedInUser->getUID()) && !$subAdminManager->isSubAdminOfGroup($loggedInUser, $group)) {
720
+            throw new OCSException('', 104);
721
+        }
722
+
723
+        // Check they aren't removing themselves from 'admin' or their 'subadmin; group
724
+        if ($targetUser->getUID() === $loggedInUser->getUID()) {
725
+            if ($this->groupManager->isAdmin($loggedInUser->getUID())) {
726
+                if ($group->getGID() === 'admin') {
727
+                    throw new OCSException('Cannot remove yourself from the admin group', 105);
728
+                }
729
+            } else {
730
+                // Not an admin, so the user must be a subadmin of this group, but that is not allowed.
731
+                throw new OCSException('Cannot remove yourself from this group as you are a SubAdmin', 105);
732
+            }
733
+
734
+        } else if (!$this->groupManager->isAdmin($loggedInUser->getUID())) {
735
+            /** @var IGroup[] $subAdminGroups */
736
+            $subAdminGroups = $subAdminManager->getSubAdminsGroups($loggedInUser);
737
+            $subAdminGroups = array_map(function (IGroup $subAdminGroup) {
738
+                return $subAdminGroup->getGID();
739
+            }, $subAdminGroups);
740
+            $userGroups = $this->groupManager->getUserGroupIds($targetUser);
741
+            $userSubAdminGroups = array_intersect($subAdminGroups, $userGroups);
742
+
743
+            if (count($userSubAdminGroups) <= 1) {
744
+                // Subadmin must not be able to remove a user from all their subadmin groups.
745
+                throw new OCSException('Cannot remove user from this group as this is the only remaining group you are a SubAdmin of', 105);
746
+            }
747
+        }
748
+
749
+        // Remove user from group
750
+        $group->removeUser($targetUser);
751
+        return new DataResponse();
752
+    }
753
+
754
+    /**
755
+     * Creates a subadmin
756
+     *
757
+     * @PasswordConfirmationRequired
758
+     *
759
+     * @param string $userId
760
+     * @param string $groupid
761
+     * @return DataResponse
762
+     * @throws OCSException
763
+     */
764
+    public function addSubAdmin(string $userId, string $groupid): DataResponse {
765
+        $group = $this->groupManager->get($groupid);
766
+        $user = $this->userManager->get($userId);
767
+
768
+        // Check if the user exists
769
+        if ($user === null) {
770
+            throw new OCSException('User does not exist', 101);
771
+        }
772
+        // Check if group exists
773
+        if ($group === null) {
774
+            throw new OCSException('Group does not exist',  102);
775
+        }
776
+        // Check if trying to make subadmin of admin group
777
+        if ($group->getGID() === 'admin') {
778
+            throw new OCSException('Cannot create subadmins for admin group', 103);
779
+        }
780
+
781
+        $subAdminManager = $this->groupManager->getSubAdmin();
782
+
783
+        // We cannot be subadmin twice
784
+        if ($subAdminManager->isSubAdminOfGroup($user, $group)) {
785
+            return new DataResponse();
786
+        }
787
+        // Go
788
+        if ($subAdminManager->createSubAdmin($user, $group)) {
789
+            return new DataResponse();
790
+        } else {
791
+            throw new OCSException('Unknown error occurred', 103);
792
+        }
793
+    }
794
+
795
+    /**
796
+     * Removes a subadmin from a group
797
+     *
798
+     * @PasswordConfirmationRequired
799
+     *
800
+     * @param string $userId
801
+     * @param string $groupid
802
+     * @return DataResponse
803
+     * @throws OCSException
804
+     */
805
+    public function removeSubAdmin(string $userId, string $groupid): DataResponse {
806
+        $group = $this->groupManager->get($groupid);
807
+        $user = $this->userManager->get($userId);
808
+        $subAdminManager = $this->groupManager->getSubAdmin();
809
+
810
+        // Check if the user exists
811
+        if ($user === null) {
812
+            throw new OCSException('User does not exist', 101);
813
+        }
814
+        // Check if the group exists
815
+        if ($group === null) {
816
+            throw new OCSException('Group does not exist', 101);
817
+        }
818
+        // Check if they are a subadmin of this said group
819
+        if (!$subAdminManager->isSubAdminOfGroup($user, $group)) {
820
+            throw new OCSException('User is not a subadmin of this group', 102);
821
+        }
822
+
823
+        // Go
824
+        if ($subAdminManager->deleteSubAdmin($user, $group)) {
825
+            return new DataResponse();
826
+        } else {
827
+            throw new OCSException('Unknown error occurred', 103);
828
+        }
829
+    }
830
+
831
+    /**
832
+     * Get the groups a user is a subadmin of
833
+     *
834
+     * @param string $userId
835
+     * @return DataResponse
836
+     * @throws OCSException
837
+     */
838
+    public function getUserSubAdminGroups(string $userId): DataResponse {
839
+        $groups = $this->getUserSubAdminGroupsData($userId);
840
+        return new DataResponse($groups);
841
+    }
842
+
843
+    /**
844
+     * @NoAdminRequired
845
+     * @PasswordConfirmationRequired
846
+     *
847
+     * resend welcome message
848
+     *
849
+     * @param string $userId
850
+     * @return DataResponse
851
+     * @throws OCSException
852
+     */
853
+    public function resendWelcomeMessage(string $userId): DataResponse {
854
+        $currentLoggedInUser = $this->userSession->getUser();
855
+
856
+        $targetUser = $this->userManager->get($userId);
857
+        if ($targetUser === null) {
858
+            throw new OCSException('', \OCP\API::RESPOND_NOT_FOUND);
859
+        }
860
+
861
+        // Check if admin / subadmin
862
+        $subAdminManager = $this->groupManager->getSubAdmin();
863
+        if (!$subAdminManager->isUserAccessible($currentLoggedInUser, $targetUser)
864
+            && !$this->groupManager->isAdmin($currentLoggedInUser->getUID())) {
865
+            // No rights
866
+            throw new OCSException('', \OCP\API::RESPOND_UNAUTHORISED);
867
+        }
868
+
869
+        $email = $targetUser->getEMailAddress();
870
+        if ($email === '' || $email === null) {
871
+            throw new OCSException('Email address not available', 101);
872
+        }
873
+        $username = $targetUser->getUID();
874
+        $lang = $this->config->getUserValue($username, 'core', 'lang', 'en');
875
+        if (!$this->l10nFactory->languageExists('settings', $lang)) {
876
+            $lang = 'en';
877
+        }
878
+
879
+        $l10n = $this->l10nFactory->get('settings', $lang);
880
+
881
+        try {
882
+            $this->newUserMailHelper->setL10N($l10n);
883
+            $emailTemplate = $this->newUserMailHelper->generateTemplate($targetUser, false);
884
+            $this->newUserMailHelper->sendMail($targetUser, $emailTemplate);
885
+        } catch(\Exception $e) {
886
+            $this->logger->logException($e, [
887
+                'message' => "Can't send new user mail to $email",
888
+                'level' => ILogger::ERROR,
889
+                'app' => 'settings',
890
+            ]);
891
+            throw new OCSException('Sending email failed', 102);
892
+        }
893
+
894
+        return new DataResponse();
895
+    }
896 896
 }
Please login to merge, or discard this patch.
apps/files_external/lib/Service/LegacyStoragesService.php 1 patch
Indentation   +171 added lines, -171 removed lines patch added patch discarded remove patch
@@ -31,179 +31,179 @@
 block discarded – undo
31 31
  * Read mount config from legacy mount.json
32 32
  */
33 33
 abstract class LegacyStoragesService {
34
-	/** @var BackendService */
35
-	protected $backendService;
34
+    /** @var BackendService */
35
+    protected $backendService;
36 36
 
37
-	/**
38
-	 * Read legacy config data
39
-	 *
40
-	 * @return array list of mount configs
41
-	 */
42
-	abstract protected function readLegacyConfig();
37
+    /**
38
+     * Read legacy config data
39
+     *
40
+     * @return array list of mount configs
41
+     */
42
+    abstract protected function readLegacyConfig();
43 43
 
44
-	/**
45
-	 * Copy legacy storage options into the given storage config object.
46
-	 *
47
-	 * @param StorageConfig $storageConfig storage config to populate
48
-	 * @param string $mountType mount type
49
-	 * @param string $applicable applicable user or group
50
-	 * @param array $storageOptions legacy storage options
51
-	 *
52
-	 * @return StorageConfig populated storage config
53
-	 */
54
-	protected function populateStorageConfigWithLegacyOptions(
55
-		&$storageConfig,
56
-		$mountType,
57
-		$applicable,
58
-		$storageOptions
59
-	) {
60
-		$backend = $this->backendService->getBackend($storageOptions['backend']);
61
-		if (!$backend) {
62
-			throw new \UnexpectedValueException('Invalid backend ' . $storageOptions['backend']);
63
-		}
64
-		$storageConfig->setBackend($backend);
65
-		if (isset($storageOptions['authMechanism']) && $storageOptions['authMechanism'] !== 'builtin::builtin') {
66
-			$authMechanism = $this->backendService->getAuthMechanism($storageOptions['authMechanism']);
67
-		} else {
68
-			$authMechanism = $backend->getLegacyAuthMechanism($storageOptions);
69
-			$storageOptions['authMechanism'] = 'null'; // to make error handling easier
70
-		}
71
-		if (!$authMechanism) {
72
-			throw new \UnexpectedValueException('Invalid authentication mechanism ' . $storageOptions['authMechanism']);
73
-		}
74
-		$storageConfig->setAuthMechanism($authMechanism);
75
-		$storageConfig->setBackendOptions($storageOptions['options']);
76
-		if (isset($storageOptions['mountOptions'])) {
77
-			$storageConfig->setMountOptions($storageOptions['mountOptions']);
78
-		}
79
-		if (!isset($storageOptions['priority'])) {
80
-			$storageOptions['priority'] = $backend->getPriority();
81
-		}
82
-		$storageConfig->setPriority($storageOptions['priority']);
83
-		if ($mountType === \OC_Mount_Config::MOUNT_TYPE_USER) {
84
-			$applicableUsers = $storageConfig->getApplicableUsers();
85
-			if ($applicable !== 'all') {
86
-				$applicableUsers[] = $applicable;
87
-				$storageConfig->setApplicableUsers($applicableUsers);
88
-			}
89
-		} else if ($mountType === \OC_Mount_Config::MOUNT_TYPE_GROUP) {
90
-			$applicableGroups = $storageConfig->getApplicableGroups();
91
-			$applicableGroups[] = $applicable;
92
-			$storageConfig->setApplicableGroups($applicableGroups);
93
-		}
94
-		return $storageConfig;
95
-	}
44
+    /**
45
+     * Copy legacy storage options into the given storage config object.
46
+     *
47
+     * @param StorageConfig $storageConfig storage config to populate
48
+     * @param string $mountType mount type
49
+     * @param string $applicable applicable user or group
50
+     * @param array $storageOptions legacy storage options
51
+     *
52
+     * @return StorageConfig populated storage config
53
+     */
54
+    protected function populateStorageConfigWithLegacyOptions(
55
+        &$storageConfig,
56
+        $mountType,
57
+        $applicable,
58
+        $storageOptions
59
+    ) {
60
+        $backend = $this->backendService->getBackend($storageOptions['backend']);
61
+        if (!$backend) {
62
+            throw new \UnexpectedValueException('Invalid backend ' . $storageOptions['backend']);
63
+        }
64
+        $storageConfig->setBackend($backend);
65
+        if (isset($storageOptions['authMechanism']) && $storageOptions['authMechanism'] !== 'builtin::builtin') {
66
+            $authMechanism = $this->backendService->getAuthMechanism($storageOptions['authMechanism']);
67
+        } else {
68
+            $authMechanism = $backend->getLegacyAuthMechanism($storageOptions);
69
+            $storageOptions['authMechanism'] = 'null'; // to make error handling easier
70
+        }
71
+        if (!$authMechanism) {
72
+            throw new \UnexpectedValueException('Invalid authentication mechanism ' . $storageOptions['authMechanism']);
73
+        }
74
+        $storageConfig->setAuthMechanism($authMechanism);
75
+        $storageConfig->setBackendOptions($storageOptions['options']);
76
+        if (isset($storageOptions['mountOptions'])) {
77
+            $storageConfig->setMountOptions($storageOptions['mountOptions']);
78
+        }
79
+        if (!isset($storageOptions['priority'])) {
80
+            $storageOptions['priority'] = $backend->getPriority();
81
+        }
82
+        $storageConfig->setPriority($storageOptions['priority']);
83
+        if ($mountType === \OC_Mount_Config::MOUNT_TYPE_USER) {
84
+            $applicableUsers = $storageConfig->getApplicableUsers();
85
+            if ($applicable !== 'all') {
86
+                $applicableUsers[] = $applicable;
87
+                $storageConfig->setApplicableUsers($applicableUsers);
88
+            }
89
+        } else if ($mountType === \OC_Mount_Config::MOUNT_TYPE_GROUP) {
90
+            $applicableGroups = $storageConfig->getApplicableGroups();
91
+            $applicableGroups[] = $applicable;
92
+            $storageConfig->setApplicableGroups($applicableGroups);
93
+        }
94
+        return $storageConfig;
95
+    }
96 96
 
97
-	/**
98
-	 * Read the external storages config
99
-	 *
100
-	 * @return StorageConfig[] map of storage id to storage config
101
-	 */
102
-	public function getAllStorages() {
103
-		$mountPoints = $this->readLegacyConfig();
104
-		/**
105
-		 * Here is the how the horribly messy mount point array looks like
106
-		 * from the mount.json file:
107
-		 *
108
-		 * $storageOptions = $mountPoints[$mountType][$applicable][$mountPath]
109
-		 *
110
-		 * - $mountType is either "user" or "group"
111
-		 * - $applicable is the name of a user or group (or the current user for personal mounts)
112
-		 * - $mountPath is the mount point path (where the storage must be mounted)
113
-		 * - $storageOptions is a map of storage options:
114
-		 *     - "priority": storage priority
115
-		 *     - "backend": backend identifier
116
-		 *     - "class": LEGACY backend class name
117
-		 *     - "options": backend-specific options
118
-		 *     - "authMechanism": authentication mechanism identifier
119
-		 *     - "mountOptions": mount-specific options (ex: disable previews, scanner, etc)
120
-		 */
121
-		// group by storage id
122
-		/** @var StorageConfig[] $storages */
123
-		$storages = [];
124
-		// for storages without id (legacy), group by config hash for
125
-		// later processing
126
-		$storagesWithConfigHash = [];
127
-		foreach ($mountPoints as $mountType => $applicables) {
128
-			foreach ($applicables as $applicable => $mountPaths) {
129
-				foreach ($mountPaths as $rootMountPath => $storageOptions) {
130
-					$currentStorage = null;
131
-					/**
132
-					 * Flag whether the config that was read already has an id.
133
-					 * If not, it will use a config hash instead and generate
134
-					 * a proper id later
135
-					 *
136
-					 * @var boolean
137
-					 */
138
-					$hasId = false;
139
-					// the root mount point is in the format "/$user/files/the/mount/point"
140
-					// we remove the "/$user/files" prefix
141
-					$parts = explode('/', ltrim($rootMountPath, '/'), 3);
142
-					if (count($parts) < 3) {
143
-						// something went wrong, skip
144
-						\OC::$server->getLogger()->error('Could not parse mount point "' . $rootMountPath . '"', ['app' => 'files_external']);
145
-						continue;
146
-					}
147
-					$relativeMountPath = rtrim($parts[2], '/');
148
-					// note: we cannot do this after the loop because the decrypted config
149
-					// options might be needed for the config hash
150
-					$storageOptions['options'] = \OC_Mount_Config::decryptPasswords($storageOptions['options']);
151
-					if (!isset($storageOptions['backend'])) {
152
-						$storageOptions['backend'] = $storageOptions['class']; // legacy compat
153
-					}
154
-					if (!isset($storageOptions['authMechanism'])) {
155
-						$storageOptions['authMechanism'] = null; // ensure config hash works
156
-					}
157
-					if (isset($storageOptions['id'])) {
158
-						$configId = (int)$storageOptions['id'];
159
-						if (isset($storages[$configId])) {
160
-							$currentStorage = $storages[$configId];
161
-						}
162
-						$hasId = true;
163
-					} else {
164
-						// missing id in legacy config, need to generate
165
-						// but at this point we don't know the max-id, so use
166
-						// first group it by config hash
167
-						$storageOptions['mountpoint'] = $rootMountPath;
168
-						$configId = \OC_Mount_Config::makeConfigHash($storageOptions);
169
-						if (isset($storagesWithConfigHash[$configId])) {
170
-							$currentStorage = $storagesWithConfigHash[$configId];
171
-						}
172
-					}
173
-					if (is_null($currentStorage)) {
174
-						// create new
175
-						$currentStorage = new StorageConfig($configId);
176
-						$currentStorage->setMountPoint($relativeMountPath);
177
-					}
178
-					try {
179
-						$this->populateStorageConfigWithLegacyOptions(
180
-							$currentStorage,
181
-							$mountType,
182
-							$applicable,
183
-							$storageOptions
184
-						);
185
-						if ($hasId) {
186
-							$storages[$configId] = $currentStorage;
187
-						} else {
188
-							$storagesWithConfigHash[$configId] = $currentStorage;
189
-						}
190
-					} catch (\UnexpectedValueException $e) {
191
-						// don't die if a storage backend doesn't exist
192
-						\OC::$server->getLogger()->logException($e, [
193
-							'message' => 'Could not load storage.',
194
-							'level' => ILogger::ERROR,
195
-							'app' => 'files_external',
196
-						]);
197
-					}
198
-				}
199
-			}
200
-		}
97
+    /**
98
+     * Read the external storages config
99
+     *
100
+     * @return StorageConfig[] map of storage id to storage config
101
+     */
102
+    public function getAllStorages() {
103
+        $mountPoints = $this->readLegacyConfig();
104
+        /**
105
+         * Here is the how the horribly messy mount point array looks like
106
+         * from the mount.json file:
107
+         *
108
+         * $storageOptions = $mountPoints[$mountType][$applicable][$mountPath]
109
+         *
110
+         * - $mountType is either "user" or "group"
111
+         * - $applicable is the name of a user or group (or the current user for personal mounts)
112
+         * - $mountPath is the mount point path (where the storage must be mounted)
113
+         * - $storageOptions is a map of storage options:
114
+         *     - "priority": storage priority
115
+         *     - "backend": backend identifier
116
+         *     - "class": LEGACY backend class name
117
+         *     - "options": backend-specific options
118
+         *     - "authMechanism": authentication mechanism identifier
119
+         *     - "mountOptions": mount-specific options (ex: disable previews, scanner, etc)
120
+         */
121
+        // group by storage id
122
+        /** @var StorageConfig[] $storages */
123
+        $storages = [];
124
+        // for storages without id (legacy), group by config hash for
125
+        // later processing
126
+        $storagesWithConfigHash = [];
127
+        foreach ($mountPoints as $mountType => $applicables) {
128
+            foreach ($applicables as $applicable => $mountPaths) {
129
+                foreach ($mountPaths as $rootMountPath => $storageOptions) {
130
+                    $currentStorage = null;
131
+                    /**
132
+                     * Flag whether the config that was read already has an id.
133
+                     * If not, it will use a config hash instead and generate
134
+                     * a proper id later
135
+                     *
136
+                     * @var boolean
137
+                     */
138
+                    $hasId = false;
139
+                    // the root mount point is in the format "/$user/files/the/mount/point"
140
+                    // we remove the "/$user/files" prefix
141
+                    $parts = explode('/', ltrim($rootMountPath, '/'), 3);
142
+                    if (count($parts) < 3) {
143
+                        // something went wrong, skip
144
+                        \OC::$server->getLogger()->error('Could not parse mount point "' . $rootMountPath . '"', ['app' => 'files_external']);
145
+                        continue;
146
+                    }
147
+                    $relativeMountPath = rtrim($parts[2], '/');
148
+                    // note: we cannot do this after the loop because the decrypted config
149
+                    // options might be needed for the config hash
150
+                    $storageOptions['options'] = \OC_Mount_Config::decryptPasswords($storageOptions['options']);
151
+                    if (!isset($storageOptions['backend'])) {
152
+                        $storageOptions['backend'] = $storageOptions['class']; // legacy compat
153
+                    }
154
+                    if (!isset($storageOptions['authMechanism'])) {
155
+                        $storageOptions['authMechanism'] = null; // ensure config hash works
156
+                    }
157
+                    if (isset($storageOptions['id'])) {
158
+                        $configId = (int)$storageOptions['id'];
159
+                        if (isset($storages[$configId])) {
160
+                            $currentStorage = $storages[$configId];
161
+                        }
162
+                        $hasId = true;
163
+                    } else {
164
+                        // missing id in legacy config, need to generate
165
+                        // but at this point we don't know the max-id, so use
166
+                        // first group it by config hash
167
+                        $storageOptions['mountpoint'] = $rootMountPath;
168
+                        $configId = \OC_Mount_Config::makeConfigHash($storageOptions);
169
+                        if (isset($storagesWithConfigHash[$configId])) {
170
+                            $currentStorage = $storagesWithConfigHash[$configId];
171
+                        }
172
+                    }
173
+                    if (is_null($currentStorage)) {
174
+                        // create new
175
+                        $currentStorage = new StorageConfig($configId);
176
+                        $currentStorage->setMountPoint($relativeMountPath);
177
+                    }
178
+                    try {
179
+                        $this->populateStorageConfigWithLegacyOptions(
180
+                            $currentStorage,
181
+                            $mountType,
182
+                            $applicable,
183
+                            $storageOptions
184
+                        );
185
+                        if ($hasId) {
186
+                            $storages[$configId] = $currentStorage;
187
+                        } else {
188
+                            $storagesWithConfigHash[$configId] = $currentStorage;
189
+                        }
190
+                    } catch (\UnexpectedValueException $e) {
191
+                        // don't die if a storage backend doesn't exist
192
+                        \OC::$server->getLogger()->logException($e, [
193
+                            'message' => 'Could not load storage.',
194
+                            'level' => ILogger::ERROR,
195
+                            'app' => 'files_external',
196
+                        ]);
197
+                    }
198
+                }
199
+            }
200
+        }
201 201
 
202
-		// convert parameter values
203
-		foreach ($storages as $storage) {
204
-			$storage->getBackend()->validateStorageDefinition($storage);
205
-			$storage->getAuthMechanism()->validateStorageDefinition($storage);
206
-		}
207
-		return $storages;
208
-	}
202
+        // convert parameter values
203
+        foreach ($storages as $storage) {
204
+            $storage->getBackend()->validateStorageDefinition($storage);
205
+            $storage->getAuthMechanism()->validateStorageDefinition($storage);
206
+        }
207
+        return $storages;
208
+    }
209 209
 }
Please login to merge, or discard this patch.
apps/files_external/lib/Service/StoragesService.php 1 patch
Indentation   +489 added lines, -489 removed lines patch added patch discarded remove patch
@@ -44,493 +44,493 @@
 block discarded – undo
44 44
  */
45 45
 abstract class StoragesService {
46 46
 
47
-	/** @var BackendService */
48
-	protected $backendService;
49
-
50
-	/**
51
-	 * @var DBConfigService
52
-	 */
53
-	protected $dbConfig;
54
-
55
-	/**
56
-	 * @var IUserMountCache
57
-	 */
58
-	protected $userMountCache;
59
-
60
-	/**
61
-	 * @param BackendService $backendService
62
-	 * @param DBConfigService $dbConfigService
63
-	 * @param IUserMountCache $userMountCache
64
-	 */
65
-	public function __construct(BackendService $backendService, DBConfigService $dbConfigService, IUserMountCache $userMountCache) {
66
-		$this->backendService = $backendService;
67
-		$this->dbConfig = $dbConfigService;
68
-		$this->userMountCache = $userMountCache;
69
-	}
70
-
71
-	protected function readDBConfig() {
72
-		return $this->dbConfig->getAdminMounts();
73
-	}
74
-
75
-	protected function getStorageConfigFromDBMount(array $mount) {
76
-		$applicableUsers = array_filter($mount['applicable'], function ($applicable) {
77
-			return $applicable['type'] === DBConfigService::APPLICABLE_TYPE_USER;
78
-		});
79
-		$applicableUsers = array_map(function ($applicable) {
80
-			return $applicable['value'];
81
-		}, $applicableUsers);
82
-
83
-		$applicableGroups = array_filter($mount['applicable'], function ($applicable) {
84
-			return $applicable['type'] === DBConfigService::APPLICABLE_TYPE_GROUP;
85
-		});
86
-		$applicableGroups = array_map(function ($applicable) {
87
-			return $applicable['value'];
88
-		}, $applicableGroups);
89
-
90
-		try {
91
-			$config = $this->createStorage(
92
-				$mount['mount_point'],
93
-				$mount['storage_backend'],
94
-				$mount['auth_backend'],
95
-				$mount['config'],
96
-				$mount['options'],
97
-				array_values($applicableUsers),
98
-				array_values($applicableGroups),
99
-				$mount['priority']
100
-			);
101
-			$config->setType($mount['type']);
102
-			$config->setId((int)$mount['mount_id']);
103
-			return $config;
104
-		} catch (\UnexpectedValueException $e) {
105
-			// don't die if a storage backend doesn't exist
106
-			\OC::$server->getLogger()->logException($e, [
107
-				'message' => 'Could not load storage.',
108
-				'level' => ILogger::ERROR,
109
-				'app' => 'files_external',
110
-			]);
111
-			return null;
112
-		} catch (\InvalidArgumentException $e) {
113
-			\OC::$server->getLogger()->logException($e, [
114
-				'message' => 'Could not load storage.',
115
-				'level' => ILogger::ERROR,
116
-				'app' => 'files_external',
117
-			]);
118
-			return null;
119
-		}
120
-	}
121
-
122
-	/**
123
-	 * Read the external storages config
124
-	 *
125
-	 * @return array map of storage id to storage config
126
-	 */
127
-	protected function readConfig() {
128
-		$mounts = $this->readDBConfig();
129
-		$configs = array_map([$this, 'getStorageConfigFromDBMount'], $mounts);
130
-		$configs = array_filter($configs, function ($config) {
131
-			return $config instanceof StorageConfig;
132
-		});
133
-
134
-		$keys = array_map(function (StorageConfig $config) {
135
-			return $config->getId();
136
-		}, $configs);
137
-
138
-		return array_combine($keys, $configs);
139
-	}
140
-
141
-	/**
142
-	 * Get a storage with status
143
-	 *
144
-	 * @param int $id storage id
145
-	 *
146
-	 * @return StorageConfig
147
-	 * @throws NotFoundException if the storage with the given id was not found
148
-	 */
149
-	public function getStorage($id) {
150
-		$mount = $this->dbConfig->getMountById($id);
151
-
152
-		if (!is_array($mount)) {
153
-			throw new NotFoundException('Storage with ID "' . $id . '" not found');
154
-		}
155
-
156
-		$config = $this->getStorageConfigFromDBMount($mount);
157
-		if ($this->isApplicable($config)) {
158
-			return $config;
159
-		} else {
160
-			throw new NotFoundException('Storage with ID "' . $id . '" not found');
161
-		}
162
-	}
163
-
164
-	/**
165
-	 * Check whether this storage service should provide access to a storage
166
-	 *
167
-	 * @param StorageConfig $config
168
-	 * @return bool
169
-	 */
170
-	abstract protected function isApplicable(StorageConfig $config);
171
-
172
-	/**
173
-	 * Gets all storages, valid or not
174
-	 *
175
-	 * @return StorageConfig[] array of storage configs
176
-	 */
177
-	public function getAllStorages() {
178
-		return $this->readConfig();
179
-	}
180
-
181
-	/**
182
-	 * Gets all valid storages
183
-	 *
184
-	 * @return StorageConfig[]
185
-	 */
186
-	public function getStorages() {
187
-		return array_filter($this->getAllStorages(), [$this, 'validateStorage']);
188
-	}
189
-
190
-	/**
191
-	 * Validate storage
192
-	 * FIXME: De-duplicate with StoragesController::validate()
193
-	 *
194
-	 * @param StorageConfig $storage
195
-	 * @return bool
196
-	 */
197
-	protected function validateStorage(StorageConfig $storage) {
198
-		/** @var Backend */
199
-		$backend = $storage->getBackend();
200
-		/** @var AuthMechanism */
201
-		$authMechanism = $storage->getAuthMechanism();
202
-
203
-		if (!$backend->isVisibleFor($this->getVisibilityType())) {
204
-			// not permitted to use backend
205
-			return false;
206
-		}
207
-		if (!$authMechanism->isVisibleFor($this->getVisibilityType())) {
208
-			// not permitted to use auth mechanism
209
-			return false;
210
-		}
211
-
212
-		return true;
213
-	}
214
-
215
-	/**
216
-	 * Get the visibility type for this controller, used in validation
217
-	 *
218
-	 * @return string BackendService::VISIBILITY_* constants
219
-	 */
220
-	abstract public function getVisibilityType();
221
-
222
-	/**
223
-	 * @return integer
224
-	 */
225
-	protected function getType() {
226
-		return DBConfigService::MOUNT_TYPE_ADMIN;
227
-	}
228
-
229
-	/**
230
-	 * Add new storage to the configuration
231
-	 *
232
-	 * @param StorageConfig $newStorage storage attributes
233
-	 *
234
-	 * @return StorageConfig storage config, with added id
235
-	 */
236
-	public function addStorage(StorageConfig $newStorage) {
237
-		$allStorages = $this->readConfig();
238
-
239
-		$configId = $this->dbConfig->addMount(
240
-			$newStorage->getMountPoint(),
241
-			$newStorage->getBackend()->getIdentifier(),
242
-			$newStorage->getAuthMechanism()->getIdentifier(),
243
-			$newStorage->getPriority(),
244
-			$this->getType()
245
-		);
246
-
247
-		$newStorage->setId($configId);
248
-
249
-		foreach ($newStorage->getApplicableUsers() as $user) {
250
-			$this->dbConfig->addApplicable($configId, DBConfigService::APPLICABLE_TYPE_USER, $user);
251
-		}
252
-		foreach ($newStorage->getApplicableGroups() as $group) {
253
-			$this->dbConfig->addApplicable($configId, DBConfigService::APPLICABLE_TYPE_GROUP, $group);
254
-		}
255
-		foreach ($newStorage->getBackendOptions() as $key => $value) {
256
-			$this->dbConfig->setConfig($configId, $key, $value);
257
-		}
258
-		foreach ($newStorage->getMountOptions() as $key => $value) {
259
-			$this->dbConfig->setOption($configId, $key, $value);
260
-		}
261
-
262
-		if (count($newStorage->getApplicableUsers()) === 0 && count($newStorage->getApplicableGroups()) === 0) {
263
-			$this->dbConfig->addApplicable($configId, DBConfigService::APPLICABLE_TYPE_GLOBAL, null);
264
-		}
265
-
266
-		// add new storage
267
-		$allStorages[$configId] = $newStorage;
268
-
269
-		$this->triggerHooks($newStorage, Filesystem::signal_create_mount);
270
-
271
-		$newStorage->setStatus(StorageNotAvailableException::STATUS_SUCCESS);
272
-		return $newStorage;
273
-	}
274
-
275
-	/**
276
-	 * Create a storage from its parameters
277
-	 *
278
-	 * @param string $mountPoint storage mount point
279
-	 * @param string $backendIdentifier backend identifier
280
-	 * @param string $authMechanismIdentifier authentication mechanism identifier
281
-	 * @param array $backendOptions backend-specific options
282
-	 * @param array|null $mountOptions mount-specific options
283
-	 * @param array|null $applicableUsers users for which to mount the storage
284
-	 * @param array|null $applicableGroups groups for which to mount the storage
285
-	 * @param int|null $priority priority
286
-	 *
287
-	 * @return StorageConfig
288
-	 */
289
-	public function createStorage(
290
-		$mountPoint,
291
-		$backendIdentifier,
292
-		$authMechanismIdentifier,
293
-		$backendOptions,
294
-		$mountOptions = null,
295
-		$applicableUsers = null,
296
-		$applicableGroups = null,
297
-		$priority = null
298
-	) {
299
-		$backend = $this->backendService->getBackend($backendIdentifier);
300
-		if (!$backend) {
301
-			$backend = new InvalidBackend($backendIdentifier);
302
-		}
303
-		$authMechanism = $this->backendService->getAuthMechanism($authMechanismIdentifier);
304
-		if (!$authMechanism) {
305
-			$authMechanism = new InvalidAuth($authMechanismIdentifier);
306
-		}
307
-		$newStorage = new StorageConfig();
308
-		$newStorage->setMountPoint($mountPoint);
309
-		$newStorage->setBackend($backend);
310
-		$newStorage->setAuthMechanism($authMechanism);
311
-		$newStorage->setBackendOptions($backendOptions);
312
-		if (isset($mountOptions)) {
313
-			$newStorage->setMountOptions($mountOptions);
314
-		}
315
-		if (isset($applicableUsers)) {
316
-			$newStorage->setApplicableUsers($applicableUsers);
317
-		}
318
-		if (isset($applicableGroups)) {
319
-			$newStorage->setApplicableGroups($applicableGroups);
320
-		}
321
-		if (isset($priority)) {
322
-			$newStorage->setPriority($priority);
323
-		}
324
-
325
-		return $newStorage;
326
-	}
327
-
328
-	/**
329
-	 * Triggers the given hook signal for all the applicables given
330
-	 *
331
-	 * @param string $signal signal
332
-	 * @param string $mountPoint hook mount pount param
333
-	 * @param string $mountType hook mount type param
334
-	 * @param array $applicableArray array of applicable users/groups for which to trigger the hook
335
-	 */
336
-	protected function triggerApplicableHooks($signal, $mountPoint, $mountType, $applicableArray) {
337
-		foreach ($applicableArray as $applicable) {
338
-			\OCP\Util::emitHook(
339
-				Filesystem::CLASSNAME,
340
-				$signal,
341
-				[
342
-					Filesystem::signal_param_path => $mountPoint,
343
-					Filesystem::signal_param_mount_type => $mountType,
344
-					Filesystem::signal_param_users => $applicable,
345
-				]
346
-			);
347
-		}
348
-	}
349
-
350
-	/**
351
-	 * Triggers $signal for all applicable users of the given
352
-	 * storage
353
-	 *
354
-	 * @param StorageConfig $storage storage data
355
-	 * @param string $signal signal to trigger
356
-	 */
357
-	abstract protected function triggerHooks(StorageConfig $storage, $signal);
358
-
359
-	/**
360
-	 * Triggers signal_create_mount or signal_delete_mount to
361
-	 * accommodate for additions/deletions in applicableUsers
362
-	 * and applicableGroups fields.
363
-	 *
364
-	 * @param StorageConfig $oldStorage old storage data
365
-	 * @param StorageConfig $newStorage new storage data
366
-	 */
367
-	abstract protected function triggerChangeHooks(StorageConfig $oldStorage, StorageConfig $newStorage);
368
-
369
-	/**
370
-	 * Update storage to the configuration
371
-	 *
372
-	 * @param StorageConfig $updatedStorage storage attributes
373
-	 *
374
-	 * @return StorageConfig storage config
375
-	 * @throws NotFoundException if the given storage does not exist in the config
376
-	 */
377
-	public function updateStorage(StorageConfig $updatedStorage) {
378
-		$id = $updatedStorage->getId();
379
-
380
-		$existingMount = $this->dbConfig->getMountById($id);
381
-
382
-		if (!is_array($existingMount)) {
383
-			throw new NotFoundException('Storage with ID "' . $id . '" not found while updating storage');
384
-		}
385
-
386
-		$oldStorage = $this->getStorageConfigFromDBMount($existingMount);
387
-
388
-		if ($oldStorage->getBackend() instanceof InvalidBackend) {
389
-			throw new NotFoundException('Storage with id "' . $id . '" cannot be edited due to missing backend');
390
-		}
391
-
392
-		$removedUsers = array_diff($oldStorage->getApplicableUsers(), $updatedStorage->getApplicableUsers());
393
-		$removedGroups = array_diff($oldStorage->getApplicableGroups(), $updatedStorage->getApplicableGroups());
394
-		$addedUsers = array_diff($updatedStorage->getApplicableUsers(), $oldStorage->getApplicableUsers());
395
-		$addedGroups = array_diff($updatedStorage->getApplicableGroups(), $oldStorage->getApplicableGroups());
396
-
397
-		$oldUserCount = count($oldStorage->getApplicableUsers());
398
-		$oldGroupCount = count($oldStorage->getApplicableGroups());
399
-		$newUserCount = count($updatedStorage->getApplicableUsers());
400
-		$newGroupCount = count($updatedStorage->getApplicableGroups());
401
-		$wasGlobal = ($oldUserCount + $oldGroupCount) === 0;
402
-		$isGlobal = ($newUserCount + $newGroupCount) === 0;
403
-
404
-		foreach ($removedUsers as $user) {
405
-			$this->dbConfig->removeApplicable($id, DBConfigService::APPLICABLE_TYPE_USER, $user);
406
-		}
407
-		foreach ($removedGroups as $group) {
408
-			$this->dbConfig->removeApplicable($id, DBConfigService::APPLICABLE_TYPE_GROUP, $group);
409
-		}
410
-		foreach ($addedUsers as $user) {
411
-			$this->dbConfig->addApplicable($id, DBConfigService::APPLICABLE_TYPE_USER, $user);
412
-		}
413
-		foreach ($addedGroups as $group) {
414
-			$this->dbConfig->addApplicable($id, DBConfigService::APPLICABLE_TYPE_GROUP, $group);
415
-		}
416
-
417
-		if ($wasGlobal && !$isGlobal) {
418
-			$this->dbConfig->removeApplicable($id, DBConfigService::APPLICABLE_TYPE_GLOBAL, null);
419
-		} else if (!$wasGlobal && $isGlobal) {
420
-			$this->dbConfig->addApplicable($id, DBConfigService::APPLICABLE_TYPE_GLOBAL, null);
421
-		}
422
-
423
-		$changedConfig = array_diff_assoc($updatedStorage->getBackendOptions(), $oldStorage->getBackendOptions());
424
-		$changedOptions = array_diff_assoc($updatedStorage->getMountOptions(), $oldStorage->getMountOptions());
425
-
426
-		foreach ($changedConfig as $key => $value) {
427
-			$this->dbConfig->setConfig($id, $key, $value);
428
-		}
429
-		foreach ($changedOptions as $key => $value) {
430
-			$this->dbConfig->setOption($id, $key, $value);
431
-		}
432
-
433
-		if ($updatedStorage->getMountPoint() !== $oldStorage->getMountPoint()) {
434
-			$this->dbConfig->setMountPoint($id, $updatedStorage->getMountPoint());
435
-		}
436
-
437
-		if ($updatedStorage->getAuthMechanism()->getIdentifier() !== $oldStorage->getAuthMechanism()->getIdentifier()) {
438
-			$this->dbConfig->setAuthBackend($id, $updatedStorage->getAuthMechanism()->getIdentifier());
439
-		}
440
-
441
-		$this->triggerChangeHooks($oldStorage, $updatedStorage);
442
-
443
-		if (($wasGlobal && !$isGlobal) || count($removedGroups) > 0) { // to expensive to properly handle these on the fly
444
-			$this->userMountCache->remoteStorageMounts($this->getStorageId($updatedStorage));
445
-		} else {
446
-			$storageId = $this->getStorageId($updatedStorage);
447
-			foreach ($removedUsers as $userId) {
448
-				$this->userMountCache->removeUserStorageMount($storageId, $userId);
449
-			}
450
-		}
451
-
452
-		return $this->getStorage($id);
453
-	}
454
-
455
-	/**
456
-	 * Delete the storage with the given id.
457
-	 *
458
-	 * @param int $id storage id
459
-	 *
460
-	 * @throws NotFoundException if no storage was found with the given id
461
-	 */
462
-	public function removeStorage($id) {
463
-		$existingMount = $this->dbConfig->getMountById($id);
464
-
465
-		if (!is_array($existingMount)) {
466
-			throw new NotFoundException('Storage with ID "' . $id . '" not found');
467
-		}
468
-
469
-		$this->dbConfig->removeMount($id);
470
-
471
-		$deletedStorage = $this->getStorageConfigFromDBMount($existingMount);
472
-		$this->triggerHooks($deletedStorage, Filesystem::signal_delete_mount);
473
-
474
-		// delete oc_storages entries and oc_filecache
475
-		try {
476
-			$rustyStorageId = $this->getRustyStorageIdFromConfig($deletedStorage);
477
-			\OC\Files\Cache\Storage::remove($rustyStorageId);
478
-		} catch (\Exception $e) {
479
-			// can happen either for invalid configs where the storage could not
480
-			// be instantiated or whenever $user vars where used, in which case
481
-			// the storage id could not be computed
482
-			\OC::$server->getLogger()->logException($e, [
483
-				'level' => ILogger::ERROR,
484
-				'app' => 'files_external',
485
-			]);
486
-		}
487
-	}
488
-
489
-	/**
490
-	 * Returns the rusty storage id from oc_storages from the given storage config.
491
-	 *
492
-	 * @param StorageConfig $storageConfig
493
-	 * @return string rusty storage id
494
-	 */
495
-	private function getRustyStorageIdFromConfig(StorageConfig $storageConfig) {
496
-		// if any of the storage options contains $user, it is not possible
497
-		// to compute the possible storage id as we don't know which users
498
-		// mounted it already (and we certainly don't want to iterate over ALL users)
499
-		foreach ($storageConfig->getBackendOptions() as $value) {
500
-			if (strpos($value, '$user') !== false) {
501
-				throw new \Exception('Cannot compute storage id for deletion due to $user vars in the configuration');
502
-			}
503
-		}
504
-
505
-		// note: similar to ConfigAdapter->prepateStorageConfig()
506
-		$storageConfig->getAuthMechanism()->manipulateStorageConfig($storageConfig);
507
-		$storageConfig->getBackend()->manipulateStorageConfig($storageConfig);
508
-
509
-		$class = $storageConfig->getBackend()->getStorageClass();
510
-		$storageImpl = new $class($storageConfig->getBackendOptions());
511
-
512
-		return $storageImpl->getId();
513
-	}
514
-
515
-	/**
516
-	 * Construct the storage implementation
517
-	 *
518
-	 * @param StorageConfig $storageConfig
519
-	 * @return int
520
-	 */
521
-	private function getStorageId(StorageConfig $storageConfig) {
522
-		try {
523
-			$class = $storageConfig->getBackend()->getStorageClass();
524
-			/** @var \OC\Files\Storage\Storage $storage */
525
-			$storage = new $class($storageConfig->getBackendOptions());
526
-
527
-			// auth mechanism should fire first
528
-			$storage = $storageConfig->getBackend()->wrapStorage($storage);
529
-			$storage = $storageConfig->getAuthMechanism()->wrapStorage($storage);
530
-
531
-			return $storage->getStorageCache()->getNumericId();
532
-		} catch (\Exception $e) {
533
-			return -1;
534
-		}
535
-	}
47
+    /** @var BackendService */
48
+    protected $backendService;
49
+
50
+    /**
51
+     * @var DBConfigService
52
+     */
53
+    protected $dbConfig;
54
+
55
+    /**
56
+     * @var IUserMountCache
57
+     */
58
+    protected $userMountCache;
59
+
60
+    /**
61
+     * @param BackendService $backendService
62
+     * @param DBConfigService $dbConfigService
63
+     * @param IUserMountCache $userMountCache
64
+     */
65
+    public function __construct(BackendService $backendService, DBConfigService $dbConfigService, IUserMountCache $userMountCache) {
66
+        $this->backendService = $backendService;
67
+        $this->dbConfig = $dbConfigService;
68
+        $this->userMountCache = $userMountCache;
69
+    }
70
+
71
+    protected function readDBConfig() {
72
+        return $this->dbConfig->getAdminMounts();
73
+    }
74
+
75
+    protected function getStorageConfigFromDBMount(array $mount) {
76
+        $applicableUsers = array_filter($mount['applicable'], function ($applicable) {
77
+            return $applicable['type'] === DBConfigService::APPLICABLE_TYPE_USER;
78
+        });
79
+        $applicableUsers = array_map(function ($applicable) {
80
+            return $applicable['value'];
81
+        }, $applicableUsers);
82
+
83
+        $applicableGroups = array_filter($mount['applicable'], function ($applicable) {
84
+            return $applicable['type'] === DBConfigService::APPLICABLE_TYPE_GROUP;
85
+        });
86
+        $applicableGroups = array_map(function ($applicable) {
87
+            return $applicable['value'];
88
+        }, $applicableGroups);
89
+
90
+        try {
91
+            $config = $this->createStorage(
92
+                $mount['mount_point'],
93
+                $mount['storage_backend'],
94
+                $mount['auth_backend'],
95
+                $mount['config'],
96
+                $mount['options'],
97
+                array_values($applicableUsers),
98
+                array_values($applicableGroups),
99
+                $mount['priority']
100
+            );
101
+            $config->setType($mount['type']);
102
+            $config->setId((int)$mount['mount_id']);
103
+            return $config;
104
+        } catch (\UnexpectedValueException $e) {
105
+            // don't die if a storage backend doesn't exist
106
+            \OC::$server->getLogger()->logException($e, [
107
+                'message' => 'Could not load storage.',
108
+                'level' => ILogger::ERROR,
109
+                'app' => 'files_external',
110
+            ]);
111
+            return null;
112
+        } catch (\InvalidArgumentException $e) {
113
+            \OC::$server->getLogger()->logException($e, [
114
+                'message' => 'Could not load storage.',
115
+                'level' => ILogger::ERROR,
116
+                'app' => 'files_external',
117
+            ]);
118
+            return null;
119
+        }
120
+    }
121
+
122
+    /**
123
+     * Read the external storages config
124
+     *
125
+     * @return array map of storage id to storage config
126
+     */
127
+    protected function readConfig() {
128
+        $mounts = $this->readDBConfig();
129
+        $configs = array_map([$this, 'getStorageConfigFromDBMount'], $mounts);
130
+        $configs = array_filter($configs, function ($config) {
131
+            return $config instanceof StorageConfig;
132
+        });
133
+
134
+        $keys = array_map(function (StorageConfig $config) {
135
+            return $config->getId();
136
+        }, $configs);
137
+
138
+        return array_combine($keys, $configs);
139
+    }
140
+
141
+    /**
142
+     * Get a storage with status
143
+     *
144
+     * @param int $id storage id
145
+     *
146
+     * @return StorageConfig
147
+     * @throws NotFoundException if the storage with the given id was not found
148
+     */
149
+    public function getStorage($id) {
150
+        $mount = $this->dbConfig->getMountById($id);
151
+
152
+        if (!is_array($mount)) {
153
+            throw new NotFoundException('Storage with ID "' . $id . '" not found');
154
+        }
155
+
156
+        $config = $this->getStorageConfigFromDBMount($mount);
157
+        if ($this->isApplicable($config)) {
158
+            return $config;
159
+        } else {
160
+            throw new NotFoundException('Storage with ID "' . $id . '" not found');
161
+        }
162
+    }
163
+
164
+    /**
165
+     * Check whether this storage service should provide access to a storage
166
+     *
167
+     * @param StorageConfig $config
168
+     * @return bool
169
+     */
170
+    abstract protected function isApplicable(StorageConfig $config);
171
+
172
+    /**
173
+     * Gets all storages, valid or not
174
+     *
175
+     * @return StorageConfig[] array of storage configs
176
+     */
177
+    public function getAllStorages() {
178
+        return $this->readConfig();
179
+    }
180
+
181
+    /**
182
+     * Gets all valid storages
183
+     *
184
+     * @return StorageConfig[]
185
+     */
186
+    public function getStorages() {
187
+        return array_filter($this->getAllStorages(), [$this, 'validateStorage']);
188
+    }
189
+
190
+    /**
191
+     * Validate storage
192
+     * FIXME: De-duplicate with StoragesController::validate()
193
+     *
194
+     * @param StorageConfig $storage
195
+     * @return bool
196
+     */
197
+    protected function validateStorage(StorageConfig $storage) {
198
+        /** @var Backend */
199
+        $backend = $storage->getBackend();
200
+        /** @var AuthMechanism */
201
+        $authMechanism = $storage->getAuthMechanism();
202
+
203
+        if (!$backend->isVisibleFor($this->getVisibilityType())) {
204
+            // not permitted to use backend
205
+            return false;
206
+        }
207
+        if (!$authMechanism->isVisibleFor($this->getVisibilityType())) {
208
+            // not permitted to use auth mechanism
209
+            return false;
210
+        }
211
+
212
+        return true;
213
+    }
214
+
215
+    /**
216
+     * Get the visibility type for this controller, used in validation
217
+     *
218
+     * @return string BackendService::VISIBILITY_* constants
219
+     */
220
+    abstract public function getVisibilityType();
221
+
222
+    /**
223
+     * @return integer
224
+     */
225
+    protected function getType() {
226
+        return DBConfigService::MOUNT_TYPE_ADMIN;
227
+    }
228
+
229
+    /**
230
+     * Add new storage to the configuration
231
+     *
232
+     * @param StorageConfig $newStorage storage attributes
233
+     *
234
+     * @return StorageConfig storage config, with added id
235
+     */
236
+    public function addStorage(StorageConfig $newStorage) {
237
+        $allStorages = $this->readConfig();
238
+
239
+        $configId = $this->dbConfig->addMount(
240
+            $newStorage->getMountPoint(),
241
+            $newStorage->getBackend()->getIdentifier(),
242
+            $newStorage->getAuthMechanism()->getIdentifier(),
243
+            $newStorage->getPriority(),
244
+            $this->getType()
245
+        );
246
+
247
+        $newStorage->setId($configId);
248
+
249
+        foreach ($newStorage->getApplicableUsers() as $user) {
250
+            $this->dbConfig->addApplicable($configId, DBConfigService::APPLICABLE_TYPE_USER, $user);
251
+        }
252
+        foreach ($newStorage->getApplicableGroups() as $group) {
253
+            $this->dbConfig->addApplicable($configId, DBConfigService::APPLICABLE_TYPE_GROUP, $group);
254
+        }
255
+        foreach ($newStorage->getBackendOptions() as $key => $value) {
256
+            $this->dbConfig->setConfig($configId, $key, $value);
257
+        }
258
+        foreach ($newStorage->getMountOptions() as $key => $value) {
259
+            $this->dbConfig->setOption($configId, $key, $value);
260
+        }
261
+
262
+        if (count($newStorage->getApplicableUsers()) === 0 && count($newStorage->getApplicableGroups()) === 0) {
263
+            $this->dbConfig->addApplicable($configId, DBConfigService::APPLICABLE_TYPE_GLOBAL, null);
264
+        }
265
+
266
+        // add new storage
267
+        $allStorages[$configId] = $newStorage;
268
+
269
+        $this->triggerHooks($newStorage, Filesystem::signal_create_mount);
270
+
271
+        $newStorage->setStatus(StorageNotAvailableException::STATUS_SUCCESS);
272
+        return $newStorage;
273
+    }
274
+
275
+    /**
276
+     * Create a storage from its parameters
277
+     *
278
+     * @param string $mountPoint storage mount point
279
+     * @param string $backendIdentifier backend identifier
280
+     * @param string $authMechanismIdentifier authentication mechanism identifier
281
+     * @param array $backendOptions backend-specific options
282
+     * @param array|null $mountOptions mount-specific options
283
+     * @param array|null $applicableUsers users for which to mount the storage
284
+     * @param array|null $applicableGroups groups for which to mount the storage
285
+     * @param int|null $priority priority
286
+     *
287
+     * @return StorageConfig
288
+     */
289
+    public function createStorage(
290
+        $mountPoint,
291
+        $backendIdentifier,
292
+        $authMechanismIdentifier,
293
+        $backendOptions,
294
+        $mountOptions = null,
295
+        $applicableUsers = null,
296
+        $applicableGroups = null,
297
+        $priority = null
298
+    ) {
299
+        $backend = $this->backendService->getBackend($backendIdentifier);
300
+        if (!$backend) {
301
+            $backend = new InvalidBackend($backendIdentifier);
302
+        }
303
+        $authMechanism = $this->backendService->getAuthMechanism($authMechanismIdentifier);
304
+        if (!$authMechanism) {
305
+            $authMechanism = new InvalidAuth($authMechanismIdentifier);
306
+        }
307
+        $newStorage = new StorageConfig();
308
+        $newStorage->setMountPoint($mountPoint);
309
+        $newStorage->setBackend($backend);
310
+        $newStorage->setAuthMechanism($authMechanism);
311
+        $newStorage->setBackendOptions($backendOptions);
312
+        if (isset($mountOptions)) {
313
+            $newStorage->setMountOptions($mountOptions);
314
+        }
315
+        if (isset($applicableUsers)) {
316
+            $newStorage->setApplicableUsers($applicableUsers);
317
+        }
318
+        if (isset($applicableGroups)) {
319
+            $newStorage->setApplicableGroups($applicableGroups);
320
+        }
321
+        if (isset($priority)) {
322
+            $newStorage->setPriority($priority);
323
+        }
324
+
325
+        return $newStorage;
326
+    }
327
+
328
+    /**
329
+     * Triggers the given hook signal for all the applicables given
330
+     *
331
+     * @param string $signal signal
332
+     * @param string $mountPoint hook mount pount param
333
+     * @param string $mountType hook mount type param
334
+     * @param array $applicableArray array of applicable users/groups for which to trigger the hook
335
+     */
336
+    protected function triggerApplicableHooks($signal, $mountPoint, $mountType, $applicableArray) {
337
+        foreach ($applicableArray as $applicable) {
338
+            \OCP\Util::emitHook(
339
+                Filesystem::CLASSNAME,
340
+                $signal,
341
+                [
342
+                    Filesystem::signal_param_path => $mountPoint,
343
+                    Filesystem::signal_param_mount_type => $mountType,
344
+                    Filesystem::signal_param_users => $applicable,
345
+                ]
346
+            );
347
+        }
348
+    }
349
+
350
+    /**
351
+     * Triggers $signal for all applicable users of the given
352
+     * storage
353
+     *
354
+     * @param StorageConfig $storage storage data
355
+     * @param string $signal signal to trigger
356
+     */
357
+    abstract protected function triggerHooks(StorageConfig $storage, $signal);
358
+
359
+    /**
360
+     * Triggers signal_create_mount or signal_delete_mount to
361
+     * accommodate for additions/deletions in applicableUsers
362
+     * and applicableGroups fields.
363
+     *
364
+     * @param StorageConfig $oldStorage old storage data
365
+     * @param StorageConfig $newStorage new storage data
366
+     */
367
+    abstract protected function triggerChangeHooks(StorageConfig $oldStorage, StorageConfig $newStorage);
368
+
369
+    /**
370
+     * Update storage to the configuration
371
+     *
372
+     * @param StorageConfig $updatedStorage storage attributes
373
+     *
374
+     * @return StorageConfig storage config
375
+     * @throws NotFoundException if the given storage does not exist in the config
376
+     */
377
+    public function updateStorage(StorageConfig $updatedStorage) {
378
+        $id = $updatedStorage->getId();
379
+
380
+        $existingMount = $this->dbConfig->getMountById($id);
381
+
382
+        if (!is_array($existingMount)) {
383
+            throw new NotFoundException('Storage with ID "' . $id . '" not found while updating storage');
384
+        }
385
+
386
+        $oldStorage = $this->getStorageConfigFromDBMount($existingMount);
387
+
388
+        if ($oldStorage->getBackend() instanceof InvalidBackend) {
389
+            throw new NotFoundException('Storage with id "' . $id . '" cannot be edited due to missing backend');
390
+        }
391
+
392
+        $removedUsers = array_diff($oldStorage->getApplicableUsers(), $updatedStorage->getApplicableUsers());
393
+        $removedGroups = array_diff($oldStorage->getApplicableGroups(), $updatedStorage->getApplicableGroups());
394
+        $addedUsers = array_diff($updatedStorage->getApplicableUsers(), $oldStorage->getApplicableUsers());
395
+        $addedGroups = array_diff($updatedStorage->getApplicableGroups(), $oldStorage->getApplicableGroups());
396
+
397
+        $oldUserCount = count($oldStorage->getApplicableUsers());
398
+        $oldGroupCount = count($oldStorage->getApplicableGroups());
399
+        $newUserCount = count($updatedStorage->getApplicableUsers());
400
+        $newGroupCount = count($updatedStorage->getApplicableGroups());
401
+        $wasGlobal = ($oldUserCount + $oldGroupCount) === 0;
402
+        $isGlobal = ($newUserCount + $newGroupCount) === 0;
403
+
404
+        foreach ($removedUsers as $user) {
405
+            $this->dbConfig->removeApplicable($id, DBConfigService::APPLICABLE_TYPE_USER, $user);
406
+        }
407
+        foreach ($removedGroups as $group) {
408
+            $this->dbConfig->removeApplicable($id, DBConfigService::APPLICABLE_TYPE_GROUP, $group);
409
+        }
410
+        foreach ($addedUsers as $user) {
411
+            $this->dbConfig->addApplicable($id, DBConfigService::APPLICABLE_TYPE_USER, $user);
412
+        }
413
+        foreach ($addedGroups as $group) {
414
+            $this->dbConfig->addApplicable($id, DBConfigService::APPLICABLE_TYPE_GROUP, $group);
415
+        }
416
+
417
+        if ($wasGlobal && !$isGlobal) {
418
+            $this->dbConfig->removeApplicable($id, DBConfigService::APPLICABLE_TYPE_GLOBAL, null);
419
+        } else if (!$wasGlobal && $isGlobal) {
420
+            $this->dbConfig->addApplicable($id, DBConfigService::APPLICABLE_TYPE_GLOBAL, null);
421
+        }
422
+
423
+        $changedConfig = array_diff_assoc($updatedStorage->getBackendOptions(), $oldStorage->getBackendOptions());
424
+        $changedOptions = array_diff_assoc($updatedStorage->getMountOptions(), $oldStorage->getMountOptions());
425
+
426
+        foreach ($changedConfig as $key => $value) {
427
+            $this->dbConfig->setConfig($id, $key, $value);
428
+        }
429
+        foreach ($changedOptions as $key => $value) {
430
+            $this->dbConfig->setOption($id, $key, $value);
431
+        }
432
+
433
+        if ($updatedStorage->getMountPoint() !== $oldStorage->getMountPoint()) {
434
+            $this->dbConfig->setMountPoint($id, $updatedStorage->getMountPoint());
435
+        }
436
+
437
+        if ($updatedStorage->getAuthMechanism()->getIdentifier() !== $oldStorage->getAuthMechanism()->getIdentifier()) {
438
+            $this->dbConfig->setAuthBackend($id, $updatedStorage->getAuthMechanism()->getIdentifier());
439
+        }
440
+
441
+        $this->triggerChangeHooks($oldStorage, $updatedStorage);
442
+
443
+        if (($wasGlobal && !$isGlobal) || count($removedGroups) > 0) { // to expensive to properly handle these on the fly
444
+            $this->userMountCache->remoteStorageMounts($this->getStorageId($updatedStorage));
445
+        } else {
446
+            $storageId = $this->getStorageId($updatedStorage);
447
+            foreach ($removedUsers as $userId) {
448
+                $this->userMountCache->removeUserStorageMount($storageId, $userId);
449
+            }
450
+        }
451
+
452
+        return $this->getStorage($id);
453
+    }
454
+
455
+    /**
456
+     * Delete the storage with the given id.
457
+     *
458
+     * @param int $id storage id
459
+     *
460
+     * @throws NotFoundException if no storage was found with the given id
461
+     */
462
+    public function removeStorage($id) {
463
+        $existingMount = $this->dbConfig->getMountById($id);
464
+
465
+        if (!is_array($existingMount)) {
466
+            throw new NotFoundException('Storage with ID "' . $id . '" not found');
467
+        }
468
+
469
+        $this->dbConfig->removeMount($id);
470
+
471
+        $deletedStorage = $this->getStorageConfigFromDBMount($existingMount);
472
+        $this->triggerHooks($deletedStorage, Filesystem::signal_delete_mount);
473
+
474
+        // delete oc_storages entries and oc_filecache
475
+        try {
476
+            $rustyStorageId = $this->getRustyStorageIdFromConfig($deletedStorage);
477
+            \OC\Files\Cache\Storage::remove($rustyStorageId);
478
+        } catch (\Exception $e) {
479
+            // can happen either for invalid configs where the storage could not
480
+            // be instantiated or whenever $user vars where used, in which case
481
+            // the storage id could not be computed
482
+            \OC::$server->getLogger()->logException($e, [
483
+                'level' => ILogger::ERROR,
484
+                'app' => 'files_external',
485
+            ]);
486
+        }
487
+    }
488
+
489
+    /**
490
+     * Returns the rusty storage id from oc_storages from the given storage config.
491
+     *
492
+     * @param StorageConfig $storageConfig
493
+     * @return string rusty storage id
494
+     */
495
+    private function getRustyStorageIdFromConfig(StorageConfig $storageConfig) {
496
+        // if any of the storage options contains $user, it is not possible
497
+        // to compute the possible storage id as we don't know which users
498
+        // mounted it already (and we certainly don't want to iterate over ALL users)
499
+        foreach ($storageConfig->getBackendOptions() as $value) {
500
+            if (strpos($value, '$user') !== false) {
501
+                throw new \Exception('Cannot compute storage id for deletion due to $user vars in the configuration');
502
+            }
503
+        }
504
+
505
+        // note: similar to ConfigAdapter->prepateStorageConfig()
506
+        $storageConfig->getAuthMechanism()->manipulateStorageConfig($storageConfig);
507
+        $storageConfig->getBackend()->manipulateStorageConfig($storageConfig);
508
+
509
+        $class = $storageConfig->getBackend()->getStorageClass();
510
+        $storageImpl = new $class($storageConfig->getBackendOptions());
511
+
512
+        return $storageImpl->getId();
513
+    }
514
+
515
+    /**
516
+     * Construct the storage implementation
517
+     *
518
+     * @param StorageConfig $storageConfig
519
+     * @return int
520
+     */
521
+    private function getStorageId(StorageConfig $storageConfig) {
522
+        try {
523
+            $class = $storageConfig->getBackend()->getStorageClass();
524
+            /** @var \OC\Files\Storage\Storage $storage */
525
+            $storage = new $class($storageConfig->getBackendOptions());
526
+
527
+            // auth mechanism should fire first
528
+            $storage = $storageConfig->getBackend()->wrapStorage($storage);
529
+            $storage = $storageConfig->getAuthMechanism()->wrapStorage($storage);
530
+
531
+            return $storage->getStorageCache()->getNumericId();
532
+        } catch (\Exception $e) {
533
+            return -1;
534
+        }
535
+    }
536 536
 }
Please login to merge, or discard this patch.
apps/files_external/lib/Lib/Storage/SMB.php 1 patch
Indentation   +480 added lines, -480 removed lines patch added patch discarded remove patch
@@ -56,484 +56,484 @@
 block discarded – undo
56 56
 use OCP\Util;
57 57
 
58 58
 class SMB extends Common implements INotifyStorage {
59
-	/**
60
-	 * @var \Icewind\SMB\Server
61
-	 */
62
-	protected $server;
63
-
64
-	/**
65
-	 * @var \Icewind\SMB\Share
66
-	 */
67
-	protected $share;
68
-
69
-	/**
70
-	 * @var string
71
-	 */
72
-	protected $root;
73
-
74
-	/**
75
-	 * @var \Icewind\SMB\FileInfo[]
76
-	 */
77
-	protected $statCache;
78
-
79
-	public function __construct($params) {
80
-		if (isset($params['host']) && isset($params['user']) && isset($params['password']) && isset($params['share'])) {
81
-			if (Server::NativeAvailable()) {
82
-				$this->server = new NativeServer($params['host'], $params['user'], $params['password']);
83
-			} else {
84
-				$this->server = new Server($params['host'], $params['user'], $params['password']);
85
-			}
86
-			$this->share = $this->server->getShare(trim($params['share'], '/'));
87
-
88
-			$this->root = $params['root'] ?? '/';
89
-			$this->root = '/' . ltrim($this->root, '/');
90
-			$this->root = rtrim($this->root, '/') . '/';
91
-		} else {
92
-			throw new \Exception('Invalid configuration');
93
-		}
94
-		$this->statCache = new CappedMemoryCache();
95
-		parent::__construct($params);
96
-	}
97
-
98
-	/**
99
-	 * @return string
100
-	 */
101
-	public function getId() {
102
-		// FIXME: double slash to keep compatible with the old storage ids,
103
-		// failure to do so will lead to creation of a new storage id and
104
-		// loss of shares from the storage
105
-		return 'smb::' . $this->server->getUser() . '@' . $this->server->getHost() . '//' . $this->share->getName() . '/' . $this->root;
106
-	}
107
-
108
-	/**
109
-	 * @param string $path
110
-	 * @return string
111
-	 */
112
-	protected function buildPath($path) {
113
-		return Filesystem::normalizePath($this->root . '/' . $path, true, false, true);
114
-	}
115
-
116
-	protected function relativePath($fullPath) {
117
-		if ($fullPath === $this->root) {
118
-			return '';
119
-		} else if (substr($fullPath, 0, strlen($this->root)) === $this->root) {
120
-			return substr($fullPath, strlen($this->root));
121
-		} else {
122
-			return null;
123
-		}
124
-	}
125
-
126
-	/**
127
-	 * @param string $path
128
-	 * @return \Icewind\SMB\IFileInfo
129
-	 * @throws StorageNotAvailableException
130
-	 */
131
-	protected function getFileInfo($path) {
132
-		try {
133
-			$path = $this->buildPath($path);
134
-			if (!isset($this->statCache[$path])) {
135
-				$this->statCache[$path] = $this->share->stat($path);
136
-			}
137
-			return $this->statCache[$path];
138
-		} catch (ConnectException $e) {
139
-			throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
140
-		}
141
-	}
142
-
143
-	/**
144
-	 * @param string $path
145
-	 * @return \Icewind\SMB\IFileInfo[]
146
-	 * @throws StorageNotAvailableException
147
-	 */
148
-	protected function getFolderContents($path) {
149
-		try {
150
-			$path = $this->buildPath($path);
151
-			$files = $this->share->dir($path);
152
-			foreach ($files as $file) {
153
-				$this->statCache[$path . '/' . $file->getName()] = $file;
154
-			}
155
-			return array_filter($files, function (IFileInfo $file) {
156
-				try {
157
-					return !$file->isHidden();
158
-				} catch (ForbiddenException $e) {
159
-					return false;
160
-				} catch (NotFoundException $e) {
161
-					return false;
162
-				}
163
-			});
164
-		} catch (ConnectException $e) {
165
-			throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
166
-		}
167
-	}
168
-
169
-	/**
170
-	 * @param \Icewind\SMB\IFileInfo $info
171
-	 * @return array
172
-	 */
173
-	protected function formatInfo($info) {
174
-		$result = [
175
-			'size' => $info->getSize(),
176
-			'mtime' => $info->getMTime(),
177
-		];
178
-		if ($info->isDirectory()) {
179
-			$result['type'] = 'dir';
180
-		} else {
181
-			$result['type'] = 'file';
182
-		}
183
-		return $result;
184
-	}
185
-
186
-	/**
187
-	 * Rename the files. If the source or the target is the root, the rename won't happen.
188
-	 *
189
-	 * @param string $source the old name of the path
190
-	 * @param string $target the new name of the path
191
-	 * @return bool true if the rename is successful, false otherwise
192
-	 */
193
-	public function rename($source, $target) {
194
-		if ($this->isRootDir($source) || $this->isRootDir($target)) {
195
-			return false;
196
-		}
197
-
198
-		$absoluteSource = $this->buildPath($source);
199
-		$absoluteTarget = $this->buildPath($target);
200
-		try {
201
-			$result = $this->share->rename($absoluteSource, $absoluteTarget);
202
-		} catch (AlreadyExistsException $e) {
203
-			$this->remove($target);
204
-			$result = $this->share->rename($absoluteSource, $absoluteTarget);
205
-		} catch (\Exception $e) {
206
-			\OC::$server->getLogger()->logException($e, ['level' => ILogger::WARN]);
207
-			return false;
208
-		}
209
-		unset($this->statCache[$absoluteSource], $this->statCache[$absoluteTarget]);
210
-		return $result;
211
-	}
212
-
213
-	public function stat($path) {
214
-		try {
215
-			$result = $this->formatInfo($this->getFileInfo($path));
216
-		} catch (ForbiddenException $e) {
217
-			return false;
218
-		} catch (NotFoundException $e) {
219
-			return false;
220
-		}
221
-		if ($this->remoteIsShare() && $this->isRootDir($path)) {
222
-			$result['mtime'] = $this->shareMTime();
223
-		}
224
-		return $result;
225
-	}
226
-
227
-	/**
228
-	 * get the best guess for the modification time of the share
229
-	 *
230
-	 * @return int
231
-	 */
232
-	private function shareMTime() {
233
-		$highestMTime = 0;
234
-		$files = $this->share->dir($this->root);
235
-		foreach ($files as $fileInfo) {
236
-			try {
237
-				if ($fileInfo->getMTime() > $highestMTime) {
238
-					$highestMTime = $fileInfo->getMTime();
239
-				}
240
-			} catch (NotFoundException $e) {
241
-				// Ignore this, can happen on unavailable DFS shares
242
-			}
243
-		}
244
-		return $highestMTime;
245
-	}
246
-
247
-	/**
248
-	 * Check if the path is our root dir (not the smb one)
249
-	 *
250
-	 * @param string $path the path
251
-	 * @return bool
252
-	 */
253
-	private function isRootDir($path) {
254
-		return $path === '' || $path === '/' || $path === '.';
255
-	}
256
-
257
-	/**
258
-	 * Check if our root points to a smb share
259
-	 *
260
-	 * @return bool true if our root points to a share false otherwise
261
-	 */
262
-	private function remoteIsShare() {
263
-		return $this->share->getName() && (!$this->root || $this->root === '/');
264
-	}
265
-
266
-	/**
267
-	 * @param string $path
268
-	 * @return bool
269
-	 */
270
-	public function unlink($path) {
271
-		if ($this->isRootDir($path)) {
272
-			return false;
273
-		}
274
-
275
-		try {
276
-			if ($this->is_dir($path)) {
277
-				return $this->rmdir($path);
278
-			} else {
279
-				$path = $this->buildPath($path);
280
-				unset($this->statCache[$path]);
281
-				$this->share->del($path);
282
-				return true;
283
-			}
284
-		} catch (NotFoundException $e) {
285
-			return false;
286
-		} catch (ForbiddenException $e) {
287
-			return false;
288
-		} catch (ConnectException $e) {
289
-			throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
290
-		}
291
-	}
292
-
293
-	/**
294
-	 * check if a file or folder has been updated since $time
295
-	 *
296
-	 * @param string $path
297
-	 * @param int $time
298
-	 * @return bool
299
-	 */
300
-	public function hasUpdated($path, $time) {
301
-		if (!$path and $this->root === '/') {
302
-			// mtime doesn't work for shares, but giving the nature of the backend,
303
-			// doing a full update is still just fast enough
304
-			return true;
305
-		} else {
306
-			$actualTime = $this->filemtime($path);
307
-			return $actualTime > $time;
308
-		}
309
-	}
310
-
311
-	/**
312
-	 * @param string $path
313
-	 * @param string $mode
314
-	 * @return resource|false
315
-	 */
316
-	public function fopen($path, $mode) {
317
-		$fullPath = $this->buildPath($path);
318
-		try {
319
-			switch ($mode) {
320
-				case 'r':
321
-				case 'rb':
322
-					if (!$this->file_exists($path)) {
323
-						return false;
324
-					}
325
-					return $this->share->read($fullPath);
326
-				case 'w':
327
-				case 'wb':
328
-					$source = $this->share->write($fullPath);
329
-					return CallBackWrapper::wrap($source, null, null, function () use ($fullPath) {
330
-						unset($this->statCache[$fullPath]);
331
-					});
332
-				case 'a':
333
-				case 'ab':
334
-				case 'r+':
335
-				case 'w+':
336
-				case 'wb+':
337
-				case 'a+':
338
-				case 'x':
339
-				case 'x+':
340
-				case 'c':
341
-				case 'c+':
342
-					//emulate these
343
-					if (strrpos($path, '.') !== false) {
344
-						$ext = substr($path, strrpos($path, '.'));
345
-					} else {
346
-						$ext = '';
347
-					}
348
-					if ($this->file_exists($path)) {
349
-						if (!$this->isUpdatable($path)) {
350
-							return false;
351
-						}
352
-						$tmpFile = $this->getCachedFile($path);
353
-					} else {
354
-						if (!$this->isCreatable(dirname($path))) {
355
-							return false;
356
-						}
357
-						$tmpFile = \OC::$server->getTempManager()->getTemporaryFile($ext);
358
-					}
359
-					$source = fopen($tmpFile, $mode);
360
-					$share = $this->share;
361
-					return CallbackWrapper::wrap($source, null, null, function () use ($tmpFile, $fullPath, $share) {
362
-						unset($this->statCache[$fullPath]);
363
-						$share->put($tmpFile, $fullPath);
364
-						unlink($tmpFile);
365
-					});
366
-			}
367
-			return false;
368
-		} catch (NotFoundException $e) {
369
-			return false;
370
-		} catch (ForbiddenException $e) {
371
-			return false;
372
-		} catch (ConnectException $e) {
373
-			throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
374
-		}
375
-	}
376
-
377
-	public function rmdir($path) {
378
-		if ($this->isRootDir($path)) {
379
-			return false;
380
-		}
381
-
382
-		try {
383
-			$this->statCache = array();
384
-			$content = $this->share->dir($this->buildPath($path));
385
-			foreach ($content as $file) {
386
-				if ($file->isDirectory()) {
387
-					$this->rmdir($path . '/' . $file->getName());
388
-				} else {
389
-					$this->share->del($file->getPath());
390
-				}
391
-			}
392
-			$this->share->rmdir($this->buildPath($path));
393
-			return true;
394
-		} catch (NotFoundException $e) {
395
-			return false;
396
-		} catch (ForbiddenException $e) {
397
-			return false;
398
-		} catch (ConnectException $e) {
399
-			throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
400
-		}
401
-	}
402
-
403
-	public function touch($path, $time = null) {
404
-		try {
405
-			if (!$this->file_exists($path)) {
406
-				$fh = $this->share->write($this->buildPath($path));
407
-				fclose($fh);
408
-				return true;
409
-			}
410
-			return false;
411
-		} catch (ConnectException $e) {
412
-			throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
413
-		}
414
-	}
415
-
416
-	public function opendir($path) {
417
-		try {
418
-			$files = $this->getFolderContents($path);
419
-		} catch (NotFoundException $e) {
420
-			return false;
421
-		} catch (ForbiddenException $e) {
422
-			return false;
423
-		}
424
-		$names = array_map(function ($info) {
425
-			/** @var \Icewind\SMB\IFileInfo $info */
426
-			return $info->getName();
427
-		}, $files);
428
-		return IteratorDirectory::wrap($names);
429
-	}
430
-
431
-	public function filetype($path) {
432
-		try {
433
-			return $this->getFileInfo($path)->isDirectory() ? 'dir' : 'file';
434
-		} catch (NotFoundException $e) {
435
-			return false;
436
-		} catch (ForbiddenException $e) {
437
-			return false;
438
-		}
439
-	}
440
-
441
-	public function mkdir($path) {
442
-		$path = $this->buildPath($path);
443
-		try {
444
-			$this->share->mkdir($path);
445
-			return true;
446
-		} catch (ConnectException $e) {
447
-			throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
448
-		} catch (Exception $e) {
449
-			return false;
450
-		}
451
-	}
452
-
453
-	public function file_exists($path) {
454
-		try {
455
-			$this->getFileInfo($path);
456
-			return true;
457
-		} catch (NotFoundException $e) {
458
-			return false;
459
-		} catch (ForbiddenException $e) {
460
-			return false;
461
-		} catch (ConnectException $e) {
462
-			throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
463
-		}
464
-	}
465
-
466
-	public function isReadable($path) {
467
-		try {
468
-			$info = $this->getFileInfo($path);
469
-			return !$info->isHidden();
470
-		} catch (NotFoundException $e) {
471
-			return false;
472
-		} catch (ForbiddenException $e) {
473
-			return false;
474
-		}
475
-	}
476
-
477
-	public function isUpdatable($path) {
478
-		try {
479
-			$info = $this->getFileInfo($path);
480
-			// following windows behaviour for read-only folders: they can be written into
481
-			// (https://support.microsoft.com/en-us/kb/326549 - "cause" section)
482
-			return !$info->isHidden() && (!$info->isReadOnly() || $this->is_dir($path));
483
-		} catch (NotFoundException $e) {
484
-			return false;
485
-		} catch (ForbiddenException $e) {
486
-			return false;
487
-		}
488
-	}
489
-
490
-	public function isDeletable($path) {
491
-		try {
492
-			$info = $this->getFileInfo($path);
493
-			return !$info->isHidden() && !$info->isReadOnly();
494
-		} catch (NotFoundException $e) {
495
-			return false;
496
-		} catch (ForbiddenException $e) {
497
-			return false;
498
-		}
499
-	}
500
-
501
-	/**
502
-	 * check if smbclient is installed
503
-	 */
504
-	public static function checkDependencies() {
505
-		return (
506
-			(bool)\OC_Helper::findBinaryPath('smbclient')
507
-			|| Server::NativeAvailable()
508
-		) ? true : ['smbclient'];
509
-	}
510
-
511
-	/**
512
-	 * Test a storage for availability
513
-	 *
514
-	 * @return bool
515
-	 */
516
-	public function test() {
517
-		try {
518
-			return parent::test();
519
-		} catch (Exception $e) {
520
-			return false;
521
-		}
522
-	}
523
-
524
-	public function listen($path, callable $callback) {
525
-		$this->notify($path)->listen(function (IChange $change) use ($callback) {
526
-			if ($change instanceof IRenameChange) {
527
-				return $callback($change->getType(), $change->getPath(), $change->getTargetPath());
528
-			} else {
529
-				return $callback($change->getType(), $change->getPath());
530
-			}
531
-		});
532
-	}
533
-
534
-	public function notify($path) {
535
-		$path = '/' . ltrim($path, '/');
536
-		$shareNotifyHandler = $this->share->notify($this->buildPath($path));
537
-		return new SMBNotifyHandler($shareNotifyHandler, $this->root);
538
-	}
59
+    /**
60
+     * @var \Icewind\SMB\Server
61
+     */
62
+    protected $server;
63
+
64
+    /**
65
+     * @var \Icewind\SMB\Share
66
+     */
67
+    protected $share;
68
+
69
+    /**
70
+     * @var string
71
+     */
72
+    protected $root;
73
+
74
+    /**
75
+     * @var \Icewind\SMB\FileInfo[]
76
+     */
77
+    protected $statCache;
78
+
79
+    public function __construct($params) {
80
+        if (isset($params['host']) && isset($params['user']) && isset($params['password']) && isset($params['share'])) {
81
+            if (Server::NativeAvailable()) {
82
+                $this->server = new NativeServer($params['host'], $params['user'], $params['password']);
83
+            } else {
84
+                $this->server = new Server($params['host'], $params['user'], $params['password']);
85
+            }
86
+            $this->share = $this->server->getShare(trim($params['share'], '/'));
87
+
88
+            $this->root = $params['root'] ?? '/';
89
+            $this->root = '/' . ltrim($this->root, '/');
90
+            $this->root = rtrim($this->root, '/') . '/';
91
+        } else {
92
+            throw new \Exception('Invalid configuration');
93
+        }
94
+        $this->statCache = new CappedMemoryCache();
95
+        parent::__construct($params);
96
+    }
97
+
98
+    /**
99
+     * @return string
100
+     */
101
+    public function getId() {
102
+        // FIXME: double slash to keep compatible with the old storage ids,
103
+        // failure to do so will lead to creation of a new storage id and
104
+        // loss of shares from the storage
105
+        return 'smb::' . $this->server->getUser() . '@' . $this->server->getHost() . '//' . $this->share->getName() . '/' . $this->root;
106
+    }
107
+
108
+    /**
109
+     * @param string $path
110
+     * @return string
111
+     */
112
+    protected function buildPath($path) {
113
+        return Filesystem::normalizePath($this->root . '/' . $path, true, false, true);
114
+    }
115
+
116
+    protected function relativePath($fullPath) {
117
+        if ($fullPath === $this->root) {
118
+            return '';
119
+        } else if (substr($fullPath, 0, strlen($this->root)) === $this->root) {
120
+            return substr($fullPath, strlen($this->root));
121
+        } else {
122
+            return null;
123
+        }
124
+    }
125
+
126
+    /**
127
+     * @param string $path
128
+     * @return \Icewind\SMB\IFileInfo
129
+     * @throws StorageNotAvailableException
130
+     */
131
+    protected function getFileInfo($path) {
132
+        try {
133
+            $path = $this->buildPath($path);
134
+            if (!isset($this->statCache[$path])) {
135
+                $this->statCache[$path] = $this->share->stat($path);
136
+            }
137
+            return $this->statCache[$path];
138
+        } catch (ConnectException $e) {
139
+            throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
140
+        }
141
+    }
142
+
143
+    /**
144
+     * @param string $path
145
+     * @return \Icewind\SMB\IFileInfo[]
146
+     * @throws StorageNotAvailableException
147
+     */
148
+    protected function getFolderContents($path) {
149
+        try {
150
+            $path = $this->buildPath($path);
151
+            $files = $this->share->dir($path);
152
+            foreach ($files as $file) {
153
+                $this->statCache[$path . '/' . $file->getName()] = $file;
154
+            }
155
+            return array_filter($files, function (IFileInfo $file) {
156
+                try {
157
+                    return !$file->isHidden();
158
+                } catch (ForbiddenException $e) {
159
+                    return false;
160
+                } catch (NotFoundException $e) {
161
+                    return false;
162
+                }
163
+            });
164
+        } catch (ConnectException $e) {
165
+            throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
166
+        }
167
+    }
168
+
169
+    /**
170
+     * @param \Icewind\SMB\IFileInfo $info
171
+     * @return array
172
+     */
173
+    protected function formatInfo($info) {
174
+        $result = [
175
+            'size' => $info->getSize(),
176
+            'mtime' => $info->getMTime(),
177
+        ];
178
+        if ($info->isDirectory()) {
179
+            $result['type'] = 'dir';
180
+        } else {
181
+            $result['type'] = 'file';
182
+        }
183
+        return $result;
184
+    }
185
+
186
+    /**
187
+     * Rename the files. If the source or the target is the root, the rename won't happen.
188
+     *
189
+     * @param string $source the old name of the path
190
+     * @param string $target the new name of the path
191
+     * @return bool true if the rename is successful, false otherwise
192
+     */
193
+    public function rename($source, $target) {
194
+        if ($this->isRootDir($source) || $this->isRootDir($target)) {
195
+            return false;
196
+        }
197
+
198
+        $absoluteSource = $this->buildPath($source);
199
+        $absoluteTarget = $this->buildPath($target);
200
+        try {
201
+            $result = $this->share->rename($absoluteSource, $absoluteTarget);
202
+        } catch (AlreadyExistsException $e) {
203
+            $this->remove($target);
204
+            $result = $this->share->rename($absoluteSource, $absoluteTarget);
205
+        } catch (\Exception $e) {
206
+            \OC::$server->getLogger()->logException($e, ['level' => ILogger::WARN]);
207
+            return false;
208
+        }
209
+        unset($this->statCache[$absoluteSource], $this->statCache[$absoluteTarget]);
210
+        return $result;
211
+    }
212
+
213
+    public function stat($path) {
214
+        try {
215
+            $result = $this->formatInfo($this->getFileInfo($path));
216
+        } catch (ForbiddenException $e) {
217
+            return false;
218
+        } catch (NotFoundException $e) {
219
+            return false;
220
+        }
221
+        if ($this->remoteIsShare() && $this->isRootDir($path)) {
222
+            $result['mtime'] = $this->shareMTime();
223
+        }
224
+        return $result;
225
+    }
226
+
227
+    /**
228
+     * get the best guess for the modification time of the share
229
+     *
230
+     * @return int
231
+     */
232
+    private function shareMTime() {
233
+        $highestMTime = 0;
234
+        $files = $this->share->dir($this->root);
235
+        foreach ($files as $fileInfo) {
236
+            try {
237
+                if ($fileInfo->getMTime() > $highestMTime) {
238
+                    $highestMTime = $fileInfo->getMTime();
239
+                }
240
+            } catch (NotFoundException $e) {
241
+                // Ignore this, can happen on unavailable DFS shares
242
+            }
243
+        }
244
+        return $highestMTime;
245
+    }
246
+
247
+    /**
248
+     * Check if the path is our root dir (not the smb one)
249
+     *
250
+     * @param string $path the path
251
+     * @return bool
252
+     */
253
+    private function isRootDir($path) {
254
+        return $path === '' || $path === '/' || $path === '.';
255
+    }
256
+
257
+    /**
258
+     * Check if our root points to a smb share
259
+     *
260
+     * @return bool true if our root points to a share false otherwise
261
+     */
262
+    private function remoteIsShare() {
263
+        return $this->share->getName() && (!$this->root || $this->root === '/');
264
+    }
265
+
266
+    /**
267
+     * @param string $path
268
+     * @return bool
269
+     */
270
+    public function unlink($path) {
271
+        if ($this->isRootDir($path)) {
272
+            return false;
273
+        }
274
+
275
+        try {
276
+            if ($this->is_dir($path)) {
277
+                return $this->rmdir($path);
278
+            } else {
279
+                $path = $this->buildPath($path);
280
+                unset($this->statCache[$path]);
281
+                $this->share->del($path);
282
+                return true;
283
+            }
284
+        } catch (NotFoundException $e) {
285
+            return false;
286
+        } catch (ForbiddenException $e) {
287
+            return false;
288
+        } catch (ConnectException $e) {
289
+            throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
290
+        }
291
+    }
292
+
293
+    /**
294
+     * check if a file or folder has been updated since $time
295
+     *
296
+     * @param string $path
297
+     * @param int $time
298
+     * @return bool
299
+     */
300
+    public function hasUpdated($path, $time) {
301
+        if (!$path and $this->root === '/') {
302
+            // mtime doesn't work for shares, but giving the nature of the backend,
303
+            // doing a full update is still just fast enough
304
+            return true;
305
+        } else {
306
+            $actualTime = $this->filemtime($path);
307
+            return $actualTime > $time;
308
+        }
309
+    }
310
+
311
+    /**
312
+     * @param string $path
313
+     * @param string $mode
314
+     * @return resource|false
315
+     */
316
+    public function fopen($path, $mode) {
317
+        $fullPath = $this->buildPath($path);
318
+        try {
319
+            switch ($mode) {
320
+                case 'r':
321
+                case 'rb':
322
+                    if (!$this->file_exists($path)) {
323
+                        return false;
324
+                    }
325
+                    return $this->share->read($fullPath);
326
+                case 'w':
327
+                case 'wb':
328
+                    $source = $this->share->write($fullPath);
329
+                    return CallBackWrapper::wrap($source, null, null, function () use ($fullPath) {
330
+                        unset($this->statCache[$fullPath]);
331
+                    });
332
+                case 'a':
333
+                case 'ab':
334
+                case 'r+':
335
+                case 'w+':
336
+                case 'wb+':
337
+                case 'a+':
338
+                case 'x':
339
+                case 'x+':
340
+                case 'c':
341
+                case 'c+':
342
+                    //emulate these
343
+                    if (strrpos($path, '.') !== false) {
344
+                        $ext = substr($path, strrpos($path, '.'));
345
+                    } else {
346
+                        $ext = '';
347
+                    }
348
+                    if ($this->file_exists($path)) {
349
+                        if (!$this->isUpdatable($path)) {
350
+                            return false;
351
+                        }
352
+                        $tmpFile = $this->getCachedFile($path);
353
+                    } else {
354
+                        if (!$this->isCreatable(dirname($path))) {
355
+                            return false;
356
+                        }
357
+                        $tmpFile = \OC::$server->getTempManager()->getTemporaryFile($ext);
358
+                    }
359
+                    $source = fopen($tmpFile, $mode);
360
+                    $share = $this->share;
361
+                    return CallbackWrapper::wrap($source, null, null, function () use ($tmpFile, $fullPath, $share) {
362
+                        unset($this->statCache[$fullPath]);
363
+                        $share->put($tmpFile, $fullPath);
364
+                        unlink($tmpFile);
365
+                    });
366
+            }
367
+            return false;
368
+        } catch (NotFoundException $e) {
369
+            return false;
370
+        } catch (ForbiddenException $e) {
371
+            return false;
372
+        } catch (ConnectException $e) {
373
+            throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
374
+        }
375
+    }
376
+
377
+    public function rmdir($path) {
378
+        if ($this->isRootDir($path)) {
379
+            return false;
380
+        }
381
+
382
+        try {
383
+            $this->statCache = array();
384
+            $content = $this->share->dir($this->buildPath($path));
385
+            foreach ($content as $file) {
386
+                if ($file->isDirectory()) {
387
+                    $this->rmdir($path . '/' . $file->getName());
388
+                } else {
389
+                    $this->share->del($file->getPath());
390
+                }
391
+            }
392
+            $this->share->rmdir($this->buildPath($path));
393
+            return true;
394
+        } catch (NotFoundException $e) {
395
+            return false;
396
+        } catch (ForbiddenException $e) {
397
+            return false;
398
+        } catch (ConnectException $e) {
399
+            throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
400
+        }
401
+    }
402
+
403
+    public function touch($path, $time = null) {
404
+        try {
405
+            if (!$this->file_exists($path)) {
406
+                $fh = $this->share->write($this->buildPath($path));
407
+                fclose($fh);
408
+                return true;
409
+            }
410
+            return false;
411
+        } catch (ConnectException $e) {
412
+            throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
413
+        }
414
+    }
415
+
416
+    public function opendir($path) {
417
+        try {
418
+            $files = $this->getFolderContents($path);
419
+        } catch (NotFoundException $e) {
420
+            return false;
421
+        } catch (ForbiddenException $e) {
422
+            return false;
423
+        }
424
+        $names = array_map(function ($info) {
425
+            /** @var \Icewind\SMB\IFileInfo $info */
426
+            return $info->getName();
427
+        }, $files);
428
+        return IteratorDirectory::wrap($names);
429
+    }
430
+
431
+    public function filetype($path) {
432
+        try {
433
+            return $this->getFileInfo($path)->isDirectory() ? 'dir' : 'file';
434
+        } catch (NotFoundException $e) {
435
+            return false;
436
+        } catch (ForbiddenException $e) {
437
+            return false;
438
+        }
439
+    }
440
+
441
+    public function mkdir($path) {
442
+        $path = $this->buildPath($path);
443
+        try {
444
+            $this->share->mkdir($path);
445
+            return true;
446
+        } catch (ConnectException $e) {
447
+            throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
448
+        } catch (Exception $e) {
449
+            return false;
450
+        }
451
+    }
452
+
453
+    public function file_exists($path) {
454
+        try {
455
+            $this->getFileInfo($path);
456
+            return true;
457
+        } catch (NotFoundException $e) {
458
+            return false;
459
+        } catch (ForbiddenException $e) {
460
+            return false;
461
+        } catch (ConnectException $e) {
462
+            throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
463
+        }
464
+    }
465
+
466
+    public function isReadable($path) {
467
+        try {
468
+            $info = $this->getFileInfo($path);
469
+            return !$info->isHidden();
470
+        } catch (NotFoundException $e) {
471
+            return false;
472
+        } catch (ForbiddenException $e) {
473
+            return false;
474
+        }
475
+    }
476
+
477
+    public function isUpdatable($path) {
478
+        try {
479
+            $info = $this->getFileInfo($path);
480
+            // following windows behaviour for read-only folders: they can be written into
481
+            // (https://support.microsoft.com/en-us/kb/326549 - "cause" section)
482
+            return !$info->isHidden() && (!$info->isReadOnly() || $this->is_dir($path));
483
+        } catch (NotFoundException $e) {
484
+            return false;
485
+        } catch (ForbiddenException $e) {
486
+            return false;
487
+        }
488
+    }
489
+
490
+    public function isDeletable($path) {
491
+        try {
492
+            $info = $this->getFileInfo($path);
493
+            return !$info->isHidden() && !$info->isReadOnly();
494
+        } catch (NotFoundException $e) {
495
+            return false;
496
+        } catch (ForbiddenException $e) {
497
+            return false;
498
+        }
499
+    }
500
+
501
+    /**
502
+     * check if smbclient is installed
503
+     */
504
+    public static function checkDependencies() {
505
+        return (
506
+            (bool)\OC_Helper::findBinaryPath('smbclient')
507
+            || Server::NativeAvailable()
508
+        ) ? true : ['smbclient'];
509
+    }
510
+
511
+    /**
512
+     * Test a storage for availability
513
+     *
514
+     * @return bool
515
+     */
516
+    public function test() {
517
+        try {
518
+            return parent::test();
519
+        } catch (Exception $e) {
520
+            return false;
521
+        }
522
+    }
523
+
524
+    public function listen($path, callable $callback) {
525
+        $this->notify($path)->listen(function (IChange $change) use ($callback) {
526
+            if ($change instanceof IRenameChange) {
527
+                return $callback($change->getType(), $change->getPath(), $change->getTargetPath());
528
+            } else {
529
+                return $callback($change->getType(), $change->getPath());
530
+            }
531
+        });
532
+    }
533
+
534
+    public function notify($path) {
535
+        $path = '/' . ltrim($path, '/');
536
+        $shareNotifyHandler = $this->share->notify($this->buildPath($path));
537
+        return new SMBNotifyHandler($shareNotifyHandler, $this->root);
538
+    }
539 539
 }
Please login to merge, or discard this patch.
apps/files_external/lib/Lib/Storage/Swift.php 1 patch
Indentation   +569 added lines, -569 removed lines patch added patch discarded remove patch
@@ -50,577 +50,577 @@
 block discarded – undo
50 50
 use OpenStack\ObjectStore\v1\Models\StorageObject;
51 51
 
52 52
 class Swift extends \OC\Files\Storage\Common {
53
-	/** @var SwiftFactory */
54
-	private $connectionFactory;
55
-	/**
56
-	 * @var \OpenStack\ObjectStore\v1\Models\Container
57
-	 */
58
-	private $container;
59
-	/**
60
-	 * @var string
61
-	 */
62
-	private $bucket;
63
-	/**
64
-	 * Connection parameters
65
-	 *
66
-	 * @var array
67
-	 */
68
-	private $params;
69
-
70
-	/** @var string */
71
-	private $id;
72
-
73
-	/** @var \OC\Files\ObjectStore\Swift */
74
-	private $objectStore;
75
-
76
-	/**
77
-	 * Key value cache mapping path to data object. Maps path to
78
-	 * \OpenCloud\OpenStack\ObjectStorage\Resource\DataObject for existing
79
-	 * paths and path to false for not existing paths.
80
-	 *
81
-	 * @var \OCP\ICache
82
-	 */
83
-	private $objectCache;
84
-
85
-	/**
86
-	 * @param string $path
87
-	 * @return mixed|string
88
-	 */
89
-	private function normalizePath(string $path) {
90
-		$path = trim($path, '/');
91
-
92
-		if (!$path) {
93
-			$path = '.';
94
-		}
95
-
96
-		$path = str_replace('#', '%23', $path);
97
-
98
-		return $path;
99
-	}
100
-
101
-	const SUBCONTAINER_FILE = '.subcontainers';
102
-
103
-	/**
104
-	 * translate directory path to container name
105
-	 *
106
-	 * @param string $path
107
-	 * @return string
108
-	 */
109
-
110
-	/**
111
-	 * Fetches an object from the API.
112
-	 * If the object is cached already or a
113
-	 * failed "doesn't exist" response was cached,
114
-	 * that one will be returned.
115
-	 *
116
-	 * @param string $path
117
-	 * @return StorageObject|bool object
118
-	 * or false if the object did not exist
119
-	 * @throws \OCP\Files\StorageAuthException
120
-	 * @throws \OCP\Files\StorageNotAvailableException
121
-	 */
122
-	private function fetchObject(string $path) {
123
-		if ($this->objectCache->hasKey($path)) {
124
-			// might be "false" if object did not exist from last check
125
-			return $this->objectCache->get($path);
126
-		}
127
-		try {
128
-			$object = $this->getContainer()->getObject($path);
129
-			$object->retrieve();
130
-			$this->objectCache->set($path, $object);
131
-			return $object;
132
-		} catch (BadResponseError $e) {
133
-			// Expected response is "404 Not Found", so only log if it isn't
134
-			if ($e->getResponse()->getStatusCode() !== 404) {
135
-				\OC::$server->getLogger()->logException($e, [
136
-					'level' => ILogger::ERROR,
137
-					'app' => 'files_external',
138
-				]);
139
-			}
140
-			$this->objectCache->set($path, false);
141
-			return false;
142
-		}
143
-	}
144
-
145
-	/**
146
-	 * Returns whether the given path exists.
147
-	 *
148
-	 * @param string $path
149
-	 *
150
-	 * @return bool true if the object exist, false otherwise
151
-	 * @throws \OCP\Files\StorageAuthException
152
-	 * @throws \OCP\Files\StorageNotAvailableException
153
-	 */
154
-	private function doesObjectExist($path) {
155
-		return $this->fetchObject($path) !== false;
156
-	}
157
-
158
-	public function __construct($params) {
159
-		if ((empty($params['key']) and empty($params['password']))
160
-			or (empty($params['user']) && empty($params['userid'])) or empty($params['bucket'])
161
-			or empty($params['region'])
162
-		) {
163
-			throw new StorageBadConfigException("API Key or password, Username, Bucket and Region have to be configured.");
164
-		}
165
-
166
-		$user = $params['user'];
167
-		$this->id = 'swift::' . $user . md5($params['bucket']);
168
-
169
-		$bucketUrl = new Uri($params['bucket']);
170
-		if ($bucketUrl->getHost()) {
171
-			$params['bucket'] = basename($bucketUrl->getPath());
172
-			$params['endpoint_url'] = (string)$bucketUrl->withPath(dirname($bucketUrl->getPath()));
173
-		}
174
-
175
-		if (empty($params['url'])) {
176
-			$params['url'] = 'https://identity.api.rackspacecloud.com/v2.0/';
177
-		}
178
-
179
-		if (empty($params['service_name'])) {
180
-			$params['service_name'] = 'cloudFiles';
181
-		}
182
-
183
-		$params['autocreate'] = true;
184
-
185
-		if (isset($params['domain'])) {
186
-			$params['user'] = [
187
-				'name' => $params['user'],
188
-				'password' => $params['password'],
189
-				'domain' => [
190
-					'name' => $params['domain'],
191
-				]
192
-			];
193
-		}
194
-
195
-		$this->params = $params;
196
-		// FIXME: private class...
197
-		$this->objectCache = new \OC\Cache\CappedMemoryCache();
198
-		$this->connectionFactory = new SwiftFactory(
199
-			\OC::$server->getMemCacheFactory()->createDistributed('swift/'),
200
-			$this->params,
201
-			\OC::$server->getLogger()
202
-		);
203
-		$this->objectStore = new \OC\Files\ObjectStore\Swift($this->params, $this->connectionFactory);
204
-		$this->bucket = $params['bucket'];
205
-	}
206
-
207
-	public function mkdir($path) {
208
-		$path = $this->normalizePath($path);
209
-
210
-		if ($this->is_dir($path)) {
211
-			return false;
212
-		}
213
-
214
-		if ($path !== '.') {
215
-			$path .= '/';
216
-		}
217
-
218
-		try {
219
-			$this->getContainer()->createObject([
220
-				'name' => $path,
221
-				'content' => '',
222
-				'headers' => ['content-type' => 'httpd/unix-directory']
223
-			]);
224
-			// invalidate so that the next access gets the real object
225
-			// with all properties
226
-			$this->objectCache->remove($path);
227
-		} catch (BadResponseError $e) {
228
-			\OC::$server->getLogger()->logException($e, [
229
-				'level' => ILogger::ERROR,
230
-				'app' => 'files_external',
231
-			]);
232
-			return false;
233
-		}
234
-
235
-		return true;
236
-	}
237
-
238
-	public function file_exists($path) {
239
-		$path = $this->normalizePath($path);
240
-
241
-		if ($path !== '.' && $this->is_dir($path)) {
242
-			$path .= '/';
243
-		}
244
-
245
-		return $this->doesObjectExist($path);
246
-	}
247
-
248
-	public function rmdir($path) {
249
-		$path = $this->normalizePath($path);
250
-
251
-		if (!$this->is_dir($path) || !$this->isDeletable($path)) {
252
-			return false;
253
-		}
254
-
255
-		$dh = $this->opendir($path);
256
-		while ($file = readdir($dh)) {
257
-			if (\OC\Files\Filesystem::isIgnoredDir($file)) {
258
-				continue;
259
-			}
260
-
261
-			if ($this->is_dir($path . '/' . $file)) {
262
-				$this->rmdir($path . '/' . $file);
263
-			} else {
264
-				$this->unlink($path . '/' . $file);
265
-			}
266
-		}
267
-
268
-		try {
269
-			$this->objectStore->deleteObject($path . '/');
270
-			$this->objectCache->remove($path . '/');
271
-		} catch (BadResponseError $e) {
272
-			\OC::$server->getLogger()->logException($e, [
273
-				'level' => ILogger::ERROR,
274
-				'app' => 'files_external',
275
-			]);
276
-			return false;
277
-		}
278
-
279
-		return true;
280
-	}
281
-
282
-	public function opendir($path) {
283
-		$path = $this->normalizePath($path);
284
-
285
-		if ($path === '.') {
286
-			$path = '';
287
-		} else {
288
-			$path .= '/';
289
-		}
53
+    /** @var SwiftFactory */
54
+    private $connectionFactory;
55
+    /**
56
+     * @var \OpenStack\ObjectStore\v1\Models\Container
57
+     */
58
+    private $container;
59
+    /**
60
+     * @var string
61
+     */
62
+    private $bucket;
63
+    /**
64
+     * Connection parameters
65
+     *
66
+     * @var array
67
+     */
68
+    private $params;
69
+
70
+    /** @var string */
71
+    private $id;
72
+
73
+    /** @var \OC\Files\ObjectStore\Swift */
74
+    private $objectStore;
75
+
76
+    /**
77
+     * Key value cache mapping path to data object. Maps path to
78
+     * \OpenCloud\OpenStack\ObjectStorage\Resource\DataObject for existing
79
+     * paths and path to false for not existing paths.
80
+     *
81
+     * @var \OCP\ICache
82
+     */
83
+    private $objectCache;
84
+
85
+    /**
86
+     * @param string $path
87
+     * @return mixed|string
88
+     */
89
+    private function normalizePath(string $path) {
90
+        $path = trim($path, '/');
91
+
92
+        if (!$path) {
93
+            $path = '.';
94
+        }
95
+
96
+        $path = str_replace('#', '%23', $path);
97
+
98
+        return $path;
99
+    }
100
+
101
+    const SUBCONTAINER_FILE = '.subcontainers';
102
+
103
+    /**
104
+     * translate directory path to container name
105
+     *
106
+     * @param string $path
107
+     * @return string
108
+     */
109
+
110
+    /**
111
+     * Fetches an object from the API.
112
+     * If the object is cached already or a
113
+     * failed "doesn't exist" response was cached,
114
+     * that one will be returned.
115
+     *
116
+     * @param string $path
117
+     * @return StorageObject|bool object
118
+     * or false if the object did not exist
119
+     * @throws \OCP\Files\StorageAuthException
120
+     * @throws \OCP\Files\StorageNotAvailableException
121
+     */
122
+    private function fetchObject(string $path) {
123
+        if ($this->objectCache->hasKey($path)) {
124
+            // might be "false" if object did not exist from last check
125
+            return $this->objectCache->get($path);
126
+        }
127
+        try {
128
+            $object = $this->getContainer()->getObject($path);
129
+            $object->retrieve();
130
+            $this->objectCache->set($path, $object);
131
+            return $object;
132
+        } catch (BadResponseError $e) {
133
+            // Expected response is "404 Not Found", so only log if it isn't
134
+            if ($e->getResponse()->getStatusCode() !== 404) {
135
+                \OC::$server->getLogger()->logException($e, [
136
+                    'level' => ILogger::ERROR,
137
+                    'app' => 'files_external',
138
+                ]);
139
+            }
140
+            $this->objectCache->set($path, false);
141
+            return false;
142
+        }
143
+    }
144
+
145
+    /**
146
+     * Returns whether the given path exists.
147
+     *
148
+     * @param string $path
149
+     *
150
+     * @return bool true if the object exist, false otherwise
151
+     * @throws \OCP\Files\StorageAuthException
152
+     * @throws \OCP\Files\StorageNotAvailableException
153
+     */
154
+    private function doesObjectExist($path) {
155
+        return $this->fetchObject($path) !== false;
156
+    }
157
+
158
+    public function __construct($params) {
159
+        if ((empty($params['key']) and empty($params['password']))
160
+            or (empty($params['user']) && empty($params['userid'])) or empty($params['bucket'])
161
+            or empty($params['region'])
162
+        ) {
163
+            throw new StorageBadConfigException("API Key or password, Username, Bucket and Region have to be configured.");
164
+        }
165
+
166
+        $user = $params['user'];
167
+        $this->id = 'swift::' . $user . md5($params['bucket']);
168
+
169
+        $bucketUrl = new Uri($params['bucket']);
170
+        if ($bucketUrl->getHost()) {
171
+            $params['bucket'] = basename($bucketUrl->getPath());
172
+            $params['endpoint_url'] = (string)$bucketUrl->withPath(dirname($bucketUrl->getPath()));
173
+        }
174
+
175
+        if (empty($params['url'])) {
176
+            $params['url'] = 'https://identity.api.rackspacecloud.com/v2.0/';
177
+        }
178
+
179
+        if (empty($params['service_name'])) {
180
+            $params['service_name'] = 'cloudFiles';
181
+        }
182
+
183
+        $params['autocreate'] = true;
184
+
185
+        if (isset($params['domain'])) {
186
+            $params['user'] = [
187
+                'name' => $params['user'],
188
+                'password' => $params['password'],
189
+                'domain' => [
190
+                    'name' => $params['domain'],
191
+                ]
192
+            ];
193
+        }
194
+
195
+        $this->params = $params;
196
+        // FIXME: private class...
197
+        $this->objectCache = new \OC\Cache\CappedMemoryCache();
198
+        $this->connectionFactory = new SwiftFactory(
199
+            \OC::$server->getMemCacheFactory()->createDistributed('swift/'),
200
+            $this->params,
201
+            \OC::$server->getLogger()
202
+        );
203
+        $this->objectStore = new \OC\Files\ObjectStore\Swift($this->params, $this->connectionFactory);
204
+        $this->bucket = $params['bucket'];
205
+    }
206
+
207
+    public function mkdir($path) {
208
+        $path = $this->normalizePath($path);
209
+
210
+        if ($this->is_dir($path)) {
211
+            return false;
212
+        }
213
+
214
+        if ($path !== '.') {
215
+            $path .= '/';
216
+        }
217
+
218
+        try {
219
+            $this->getContainer()->createObject([
220
+                'name' => $path,
221
+                'content' => '',
222
+                'headers' => ['content-type' => 'httpd/unix-directory']
223
+            ]);
224
+            // invalidate so that the next access gets the real object
225
+            // with all properties
226
+            $this->objectCache->remove($path);
227
+        } catch (BadResponseError $e) {
228
+            \OC::$server->getLogger()->logException($e, [
229
+                'level' => ILogger::ERROR,
230
+                'app' => 'files_external',
231
+            ]);
232
+            return false;
233
+        }
234
+
235
+        return true;
236
+    }
237
+
238
+    public function file_exists($path) {
239
+        $path = $this->normalizePath($path);
240
+
241
+        if ($path !== '.' && $this->is_dir($path)) {
242
+            $path .= '/';
243
+        }
244
+
245
+        return $this->doesObjectExist($path);
246
+    }
247
+
248
+    public function rmdir($path) {
249
+        $path = $this->normalizePath($path);
250
+
251
+        if (!$this->is_dir($path) || !$this->isDeletable($path)) {
252
+            return false;
253
+        }
254
+
255
+        $dh = $this->opendir($path);
256
+        while ($file = readdir($dh)) {
257
+            if (\OC\Files\Filesystem::isIgnoredDir($file)) {
258
+                continue;
259
+            }
260
+
261
+            if ($this->is_dir($path . '/' . $file)) {
262
+                $this->rmdir($path . '/' . $file);
263
+            } else {
264
+                $this->unlink($path . '/' . $file);
265
+            }
266
+        }
267
+
268
+        try {
269
+            $this->objectStore->deleteObject($path . '/');
270
+            $this->objectCache->remove($path . '/');
271
+        } catch (BadResponseError $e) {
272
+            \OC::$server->getLogger()->logException($e, [
273
+                'level' => ILogger::ERROR,
274
+                'app' => 'files_external',
275
+            ]);
276
+            return false;
277
+        }
278
+
279
+        return true;
280
+    }
281
+
282
+    public function opendir($path) {
283
+        $path = $this->normalizePath($path);
284
+
285
+        if ($path === '.') {
286
+            $path = '';
287
+        } else {
288
+            $path .= '/';
289
+        }
290 290
 
291 291
 //		$path = str_replace('%23', '#', $path); // the prefix is sent as a query param, so revert the encoding of #
292 292
 
293
-		try {
294
-			$files = [];
295
-			$objects = $this->getContainer()->listObjects([
296
-				'prefix' => $path,
297
-				'delimiter' => '/'
298
-			]);
299
-
300
-			/** @var StorageObject $object */
301
-			foreach ($objects as $object) {
302
-				$file = basename($object->name);
303
-				if ($file !== basename($path) && $file !== '.') {
304
-					$files[] = $file;
305
-				}
306
-			}
307
-
308
-			return IteratorDirectory::wrap($files);
309
-		} catch (\Exception $e) {
310
-			\OC::$server->getLogger()->logException($e, [
311
-				'level' => ILogger::ERROR,
312
-				'app' => 'files_external',
313
-			]);
314
-			return false;
315
-		}
316
-
317
-	}
318
-
319
-	public function stat($path) {
320
-		$path = $this->normalizePath($path);
321
-
322
-		if ($path === '.') {
323
-			$path = '';
324
-		} else if ($this->is_dir($path)) {
325
-			$path .= '/';
326
-		}
327
-
328
-		try {
329
-			$object = $this->fetchObject($path);
330
-			if (!$object) {
331
-				return false;
332
-			}
333
-		} catch (BadResponseError $e) {
334
-			\OC::$server->getLogger()->logException($e, [
335
-				'level' => ILogger::ERROR,
336
-				'app' => 'files_external',
337
-			]);
338
-			return false;
339
-		}
340
-
341
-		$dateTime = $object->lastModified ? \DateTime::createFromFormat(\DateTime::RFC1123, $object->lastModified) : false;
342
-		$mtime = $dateTime ? $dateTime->getTimestamp() : null;
343
-		$objectMetadata = $object->getMetadata();
344
-		if (isset($objectMetadata['timestamp'])) {
345
-			$mtime = $objectMetadata['timestamp'];
346
-		}
347
-
348
-		if (!empty($mtime)) {
349
-			$mtime = floor($mtime);
350
-		}
351
-
352
-		$stat = array();
353
-		$stat['size'] = (int)$object->contentLength;
354
-		$stat['mtime'] = $mtime;
355
-		$stat['atime'] = time();
356
-		return $stat;
357
-	}
358
-
359
-	public function filetype($path) {
360
-		$path = $this->normalizePath($path);
361
-
362
-		if ($path !== '.' && $this->doesObjectExist($path)) {
363
-			return 'file';
364
-		}
365
-
366
-		if ($path !== '.') {
367
-			$path .= '/';
368
-		}
369
-
370
-		if ($this->doesObjectExist($path)) {
371
-			return 'dir';
372
-		}
373
-	}
374
-
375
-	public function unlink($path) {
376
-		$path = $this->normalizePath($path);
377
-
378
-		if ($this->is_dir($path)) {
379
-			return $this->rmdir($path);
380
-		}
381
-
382
-		try {
383
-			$this->objectStore->deleteObject($path);
384
-			$this->objectCache->remove($path);
385
-			$this->objectCache->remove($path . '/');
386
-		} catch (BadResponseError $e) {
387
-			if ($e->getResponse()->getStatusCode() !== 404) {
388
-				\OC::$server->getLogger()->logException($e, [
389
-					'level' => ILogger::ERROR,
390
-					'app' => 'files_external',
391
-				]);
392
-				throw $e;
393
-			}
394
-		}
395
-
396
-		return true;
397
-	}
398
-
399
-	public function fopen($path, $mode) {
400
-		$path = $this->normalizePath($path);
401
-
402
-		switch ($mode) {
403
-			case 'a':
404
-			case 'ab':
405
-			case 'a+':
406
-				return false;
407
-			case 'r':
408
-			case 'rb':
409
-				try {
410
-					return $this->objectStore->readObject($path);
411
-				} catch (BadResponseError $e) {
412
-					\OC::$server->getLogger()->logException($e, [
413
-						'level' => ILogger::ERROR,
414
-						'app' => 'files_external',
415
-					]);
416
-					return false;
417
-				}
418
-			case 'w':
419
-			case 'wb':
420
-			case 'r+':
421
-			case 'w+':
422
-			case 'wb+':
423
-			case 'x':
424
-			case 'x+':
425
-			case 'c':
426
-			case 'c+':
427
-				if (strrpos($path, '.') !== false) {
428
-					$ext = substr($path, strrpos($path, '.'));
429
-				} else {
430
-					$ext = '';
431
-				}
432
-				$tmpFile = \OC::$server->getTempManager()->getTemporaryFile($ext);
433
-				// Fetch existing file if required
434
-				if ($mode[0] !== 'w' && $this->file_exists($path)) {
435
-					if ($mode[0] === 'x') {
436
-						// File cannot already exist
437
-						return false;
438
-					}
439
-					$source = $this->fopen($path, 'r');
440
-					file_put_contents($tmpFile, $source);
441
-				}
442
-				$handle = fopen($tmpFile, $mode);
443
-				return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
444
-					$this->writeBack($tmpFile, $path);
445
-				});
446
-		}
447
-	}
448
-
449
-	public function touch($path, $mtime = null) {
450
-		$path = $this->normalizePath($path);
451
-		if (is_null($mtime)) {
452
-			$mtime = time();
453
-		}
454
-		$metadata = ['timestamp' => $mtime];
455
-		if ($this->file_exists($path)) {
456
-			if ($this->is_dir($path) && $path !== '.') {
457
-				$path .= '/';
458
-			}
459
-
460
-			$object = $this->fetchObject($path);
461
-			if ($object->mergeMetadata($metadata)) {
462
-				// invalidate target object to force repopulation on fetch
463
-				$this->objectCache->remove($path);
464
-			}
465
-			return true;
466
-		} else {
467
-			$mimeType = \OC::$server->getMimeTypeDetector()->detectPath($path);
468
-			$this->getContainer()->createObject([
469
-				'name' => $path,
470
-				'content' => '',
471
-				'headers' => ['content-type' => 'httpd/unix-directory']
472
-			]);
473
-			// invalidate target object to force repopulation on fetch
474
-			$this->objectCache->remove($path);
475
-			return true;
476
-		}
477
-	}
478
-
479
-	public function copy($path1, $path2) {
480
-		$path1 = $this->normalizePath($path1);
481
-		$path2 = $this->normalizePath($path2);
482
-
483
-		$fileType = $this->filetype($path1);
484
-		if ($fileType) {
485
-			// make way
486
-			$this->unlink($path2);
487
-		}
488
-
489
-		if ($fileType === 'file') {
490
-			try {
491
-				$source = $this->fetchObject($path1);
492
-				$source->copy([
493
-					'destination' => $this->bucket . '/' . $path2
494
-				]);
495
-				// invalidate target object to force repopulation on fetch
496
-				$this->objectCache->remove($path2);
497
-				$this->objectCache->remove($path2 . '/');
498
-			} catch (BadResponseError $e) {
499
-				\OC::$server->getLogger()->logException($e, [
500
-					'level' => ILogger::ERROR,
501
-					'app' => 'files_external',
502
-				]);
503
-				return false;
504
-			}
505
-
506
-		} else if ($fileType === 'dir') {
507
-			try {
508
-				$source = $this->fetchObject($path1 . '/');
509
-				$source->copy([
510
-					'destination' => $this->bucket . '/' . $path2 . '/'
511
-				]);
512
-				// invalidate target object to force repopulation on fetch
513
-				$this->objectCache->remove($path2);
514
-				$this->objectCache->remove($path2 . '/');
515
-			} catch (BadResponseError $e) {
516
-				\OC::$server->getLogger()->logException($e, [
517
-					'level' => ILogger::ERROR,
518
-					'app' => 'files_external',
519
-				]);
520
-				return false;
521
-			}
522
-
523
-			$dh = $this->opendir($path1);
524
-			while ($file = readdir($dh)) {
525
-				if (\OC\Files\Filesystem::isIgnoredDir($file)) {
526
-					continue;
527
-				}
528
-
529
-				$source = $path1 . '/' . $file;
530
-				$target = $path2 . '/' . $file;
531
-				$this->copy($source, $target);
532
-			}
533
-
534
-		} else {
535
-			//file does not exist
536
-			return false;
537
-		}
538
-
539
-		return true;
540
-	}
541
-
542
-	public function rename($path1, $path2) {
543
-		$path1 = $this->normalizePath($path1);
544
-		$path2 = $this->normalizePath($path2);
545
-
546
-		$fileType = $this->filetype($path1);
547
-
548
-		if ($fileType === 'dir' || $fileType === 'file') {
549
-			// copy
550
-			if ($this->copy($path1, $path2) === false) {
551
-				return false;
552
-			}
553
-
554
-			// cleanup
555
-			if ($this->unlink($path1) === false) {
556
-				throw new \Exception('failed to remove original');
557
-				$this->unlink($path2);
558
-				return false;
559
-			}
560
-
561
-			return true;
562
-		}
563
-
564
-		return false;
565
-	}
566
-
567
-	public function getId() {
568
-		return $this->id;
569
-	}
570
-
571
-	/**
572
-	 * Returns the initialized object store container.
573
-	 *
574
-	 * @return \OpenStack\ObjectStore\v1\Models\Container
575
-	 * @throws \OCP\Files\StorageAuthException
576
-	 * @throws \OCP\Files\StorageNotAvailableException
577
-	 */
578
-	public function getContainer() {
579
-		if (is_null($this->container)) {
580
-			$this->container = $this->connectionFactory->getContainer();
581
-
582
-			if (!$this->file_exists('.')) {
583
-				$this->mkdir('.');
584
-			}
585
-		}
586
-		return $this->container;
587
-	}
588
-
589
-	public function writeBack($tmpFile, $path) {
590
-		$fileData = fopen($tmpFile, 'r');
591
-		$this->objectStore->writeObject($path, $fileData);
592
-		// invalidate target object to force repopulation on fetch
593
-		$this->objectCache->remove($path);
594
-		unlink($tmpFile);
595
-	}
596
-
597
-	public function hasUpdated($path, $time) {
598
-		if ($this->is_file($path)) {
599
-			return parent::hasUpdated($path, $time);
600
-		}
601
-		$path = $this->normalizePath($path);
602
-		$dh = $this->opendir($path);
603
-		$content = array();
604
-		while (($file = readdir($dh)) !== false) {
605
-			$content[] = $file;
606
-		}
607
-		if ($path === '.') {
608
-			$path = '';
609
-		}
610
-		$cachedContent = $this->getCache()->getFolderContents($path);
611
-		$cachedNames = array_map(function ($content) {
612
-			return $content['name'];
613
-		}, $cachedContent);
614
-		sort($cachedNames);
615
-		sort($content);
616
-		return $cachedNames !== $content;
617
-	}
618
-
619
-	/**
620
-	 * check if curl is installed
621
-	 */
622
-	public static function checkDependencies() {
623
-		return true;
624
-	}
293
+        try {
294
+            $files = [];
295
+            $objects = $this->getContainer()->listObjects([
296
+                'prefix' => $path,
297
+                'delimiter' => '/'
298
+            ]);
299
+
300
+            /** @var StorageObject $object */
301
+            foreach ($objects as $object) {
302
+                $file = basename($object->name);
303
+                if ($file !== basename($path) && $file !== '.') {
304
+                    $files[] = $file;
305
+                }
306
+            }
307
+
308
+            return IteratorDirectory::wrap($files);
309
+        } catch (\Exception $e) {
310
+            \OC::$server->getLogger()->logException($e, [
311
+                'level' => ILogger::ERROR,
312
+                'app' => 'files_external',
313
+            ]);
314
+            return false;
315
+        }
316
+
317
+    }
318
+
319
+    public function stat($path) {
320
+        $path = $this->normalizePath($path);
321
+
322
+        if ($path === '.') {
323
+            $path = '';
324
+        } else if ($this->is_dir($path)) {
325
+            $path .= '/';
326
+        }
327
+
328
+        try {
329
+            $object = $this->fetchObject($path);
330
+            if (!$object) {
331
+                return false;
332
+            }
333
+        } catch (BadResponseError $e) {
334
+            \OC::$server->getLogger()->logException($e, [
335
+                'level' => ILogger::ERROR,
336
+                'app' => 'files_external',
337
+            ]);
338
+            return false;
339
+        }
340
+
341
+        $dateTime = $object->lastModified ? \DateTime::createFromFormat(\DateTime::RFC1123, $object->lastModified) : false;
342
+        $mtime = $dateTime ? $dateTime->getTimestamp() : null;
343
+        $objectMetadata = $object->getMetadata();
344
+        if (isset($objectMetadata['timestamp'])) {
345
+            $mtime = $objectMetadata['timestamp'];
346
+        }
347
+
348
+        if (!empty($mtime)) {
349
+            $mtime = floor($mtime);
350
+        }
351
+
352
+        $stat = array();
353
+        $stat['size'] = (int)$object->contentLength;
354
+        $stat['mtime'] = $mtime;
355
+        $stat['atime'] = time();
356
+        return $stat;
357
+    }
358
+
359
+    public function filetype($path) {
360
+        $path = $this->normalizePath($path);
361
+
362
+        if ($path !== '.' && $this->doesObjectExist($path)) {
363
+            return 'file';
364
+        }
365
+
366
+        if ($path !== '.') {
367
+            $path .= '/';
368
+        }
369
+
370
+        if ($this->doesObjectExist($path)) {
371
+            return 'dir';
372
+        }
373
+    }
374
+
375
+    public function unlink($path) {
376
+        $path = $this->normalizePath($path);
377
+
378
+        if ($this->is_dir($path)) {
379
+            return $this->rmdir($path);
380
+        }
381
+
382
+        try {
383
+            $this->objectStore->deleteObject($path);
384
+            $this->objectCache->remove($path);
385
+            $this->objectCache->remove($path . '/');
386
+        } catch (BadResponseError $e) {
387
+            if ($e->getResponse()->getStatusCode() !== 404) {
388
+                \OC::$server->getLogger()->logException($e, [
389
+                    'level' => ILogger::ERROR,
390
+                    'app' => 'files_external',
391
+                ]);
392
+                throw $e;
393
+            }
394
+        }
395
+
396
+        return true;
397
+    }
398
+
399
+    public function fopen($path, $mode) {
400
+        $path = $this->normalizePath($path);
401
+
402
+        switch ($mode) {
403
+            case 'a':
404
+            case 'ab':
405
+            case 'a+':
406
+                return false;
407
+            case 'r':
408
+            case 'rb':
409
+                try {
410
+                    return $this->objectStore->readObject($path);
411
+                } catch (BadResponseError $e) {
412
+                    \OC::$server->getLogger()->logException($e, [
413
+                        'level' => ILogger::ERROR,
414
+                        'app' => 'files_external',
415
+                    ]);
416
+                    return false;
417
+                }
418
+            case 'w':
419
+            case 'wb':
420
+            case 'r+':
421
+            case 'w+':
422
+            case 'wb+':
423
+            case 'x':
424
+            case 'x+':
425
+            case 'c':
426
+            case 'c+':
427
+                if (strrpos($path, '.') !== false) {
428
+                    $ext = substr($path, strrpos($path, '.'));
429
+                } else {
430
+                    $ext = '';
431
+                }
432
+                $tmpFile = \OC::$server->getTempManager()->getTemporaryFile($ext);
433
+                // Fetch existing file if required
434
+                if ($mode[0] !== 'w' && $this->file_exists($path)) {
435
+                    if ($mode[0] === 'x') {
436
+                        // File cannot already exist
437
+                        return false;
438
+                    }
439
+                    $source = $this->fopen($path, 'r');
440
+                    file_put_contents($tmpFile, $source);
441
+                }
442
+                $handle = fopen($tmpFile, $mode);
443
+                return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
444
+                    $this->writeBack($tmpFile, $path);
445
+                });
446
+        }
447
+    }
448
+
449
+    public function touch($path, $mtime = null) {
450
+        $path = $this->normalizePath($path);
451
+        if (is_null($mtime)) {
452
+            $mtime = time();
453
+        }
454
+        $metadata = ['timestamp' => $mtime];
455
+        if ($this->file_exists($path)) {
456
+            if ($this->is_dir($path) && $path !== '.') {
457
+                $path .= '/';
458
+            }
459
+
460
+            $object = $this->fetchObject($path);
461
+            if ($object->mergeMetadata($metadata)) {
462
+                // invalidate target object to force repopulation on fetch
463
+                $this->objectCache->remove($path);
464
+            }
465
+            return true;
466
+        } else {
467
+            $mimeType = \OC::$server->getMimeTypeDetector()->detectPath($path);
468
+            $this->getContainer()->createObject([
469
+                'name' => $path,
470
+                'content' => '',
471
+                'headers' => ['content-type' => 'httpd/unix-directory']
472
+            ]);
473
+            // invalidate target object to force repopulation on fetch
474
+            $this->objectCache->remove($path);
475
+            return true;
476
+        }
477
+    }
478
+
479
+    public function copy($path1, $path2) {
480
+        $path1 = $this->normalizePath($path1);
481
+        $path2 = $this->normalizePath($path2);
482
+
483
+        $fileType = $this->filetype($path1);
484
+        if ($fileType) {
485
+            // make way
486
+            $this->unlink($path2);
487
+        }
488
+
489
+        if ($fileType === 'file') {
490
+            try {
491
+                $source = $this->fetchObject($path1);
492
+                $source->copy([
493
+                    'destination' => $this->bucket . '/' . $path2
494
+                ]);
495
+                // invalidate target object to force repopulation on fetch
496
+                $this->objectCache->remove($path2);
497
+                $this->objectCache->remove($path2 . '/');
498
+            } catch (BadResponseError $e) {
499
+                \OC::$server->getLogger()->logException($e, [
500
+                    'level' => ILogger::ERROR,
501
+                    'app' => 'files_external',
502
+                ]);
503
+                return false;
504
+            }
505
+
506
+        } else if ($fileType === 'dir') {
507
+            try {
508
+                $source = $this->fetchObject($path1 . '/');
509
+                $source->copy([
510
+                    'destination' => $this->bucket . '/' . $path2 . '/'
511
+                ]);
512
+                // invalidate target object to force repopulation on fetch
513
+                $this->objectCache->remove($path2);
514
+                $this->objectCache->remove($path2 . '/');
515
+            } catch (BadResponseError $e) {
516
+                \OC::$server->getLogger()->logException($e, [
517
+                    'level' => ILogger::ERROR,
518
+                    'app' => 'files_external',
519
+                ]);
520
+                return false;
521
+            }
522
+
523
+            $dh = $this->opendir($path1);
524
+            while ($file = readdir($dh)) {
525
+                if (\OC\Files\Filesystem::isIgnoredDir($file)) {
526
+                    continue;
527
+                }
528
+
529
+                $source = $path1 . '/' . $file;
530
+                $target = $path2 . '/' . $file;
531
+                $this->copy($source, $target);
532
+            }
533
+
534
+        } else {
535
+            //file does not exist
536
+            return false;
537
+        }
538
+
539
+        return true;
540
+    }
541
+
542
+    public function rename($path1, $path2) {
543
+        $path1 = $this->normalizePath($path1);
544
+        $path2 = $this->normalizePath($path2);
545
+
546
+        $fileType = $this->filetype($path1);
547
+
548
+        if ($fileType === 'dir' || $fileType === 'file') {
549
+            // copy
550
+            if ($this->copy($path1, $path2) === false) {
551
+                return false;
552
+            }
553
+
554
+            // cleanup
555
+            if ($this->unlink($path1) === false) {
556
+                throw new \Exception('failed to remove original');
557
+                $this->unlink($path2);
558
+                return false;
559
+            }
560
+
561
+            return true;
562
+        }
563
+
564
+        return false;
565
+    }
566
+
567
+    public function getId() {
568
+        return $this->id;
569
+    }
570
+
571
+    /**
572
+     * Returns the initialized object store container.
573
+     *
574
+     * @return \OpenStack\ObjectStore\v1\Models\Container
575
+     * @throws \OCP\Files\StorageAuthException
576
+     * @throws \OCP\Files\StorageNotAvailableException
577
+     */
578
+    public function getContainer() {
579
+        if (is_null($this->container)) {
580
+            $this->container = $this->connectionFactory->getContainer();
581
+
582
+            if (!$this->file_exists('.')) {
583
+                $this->mkdir('.');
584
+            }
585
+        }
586
+        return $this->container;
587
+    }
588
+
589
+    public function writeBack($tmpFile, $path) {
590
+        $fileData = fopen($tmpFile, 'r');
591
+        $this->objectStore->writeObject($path, $fileData);
592
+        // invalidate target object to force repopulation on fetch
593
+        $this->objectCache->remove($path);
594
+        unlink($tmpFile);
595
+    }
596
+
597
+    public function hasUpdated($path, $time) {
598
+        if ($this->is_file($path)) {
599
+            return parent::hasUpdated($path, $time);
600
+        }
601
+        $path = $this->normalizePath($path);
602
+        $dh = $this->opendir($path);
603
+        $content = array();
604
+        while (($file = readdir($dh)) !== false) {
605
+            $content[] = $file;
606
+        }
607
+        if ($path === '.') {
608
+            $path = '';
609
+        }
610
+        $cachedContent = $this->getCache()->getFolderContents($path);
611
+        $cachedNames = array_map(function ($content) {
612
+            return $content['name'];
613
+        }, $cachedContent);
614
+        sort($cachedNames);
615
+        sort($content);
616
+        return $cachedNames !== $content;
617
+    }
618
+
619
+    /**
620
+     * check if curl is installed
621
+     */
622
+    public static function checkDependencies() {
623
+        return true;
624
+    }
625 625
 
626 626
 }
Please login to merge, or discard this patch.
apps/user_ldap/lib/Jobs/UpdateGroups.php 1 patch
Indentation   +164 added lines, -164 removed lines patch added patch discarded remove patch
@@ -46,183 +46,183 @@
 block discarded – undo
46 46
 use OCP\ILogger;
47 47
 
48 48
 class UpdateGroups extends \OC\BackgroundJob\TimedJob {
49
-	static private $groupsFromDB;
50
-
51
-	static private $groupBE;
52
-
53
-	public function __construct(){
54
-		$this->interval = self::getRefreshInterval();
55
-	}
56
-
57
-	/**
58
-	 * @param mixed $argument
59
-	 */
60
-	public function run($argument){
61
-		self::updateGroups();
62
-	}
63
-
64
-	static public function updateGroups() {
65
-		\OCP\Util::writeLog('user_ldap', 'Run background job "updateGroups"', ILogger::DEBUG);
66
-
67
-		$knownGroups = array_keys(self::getKnownGroups());
68
-		$actualGroups = self::getGroupBE()->getGroups();
69
-
70
-		if(empty($actualGroups) && empty($knownGroups)) {
71
-			\OCP\Util::writeLog('user_ldap',
72
-				'bgJ "updateGroups" – groups do not seem to be configured properly, aborting.',
73
-				ILogger::INFO);
74
-			return;
75
-		}
76
-
77
-		self::handleKnownGroups(array_intersect($actualGroups, $knownGroups));
78
-		self::handleCreatedGroups(array_diff($actualGroups, $knownGroups));
79
-		self::handleRemovedGroups(array_diff($knownGroups, $actualGroups));
80
-
81
-		\OCP\Util::writeLog('user_ldap', 'bgJ "updateGroups" – Finished.', ILogger::DEBUG);
82
-	}
83
-
84
-	/**
85
-	 * @return int
86
-	 */
87
-	static private function getRefreshInterval() {
88
-		//defaults to every hour
89
-		return \OC::$server->getConfig()->getAppValue('user_ldap', 'bgjRefreshInterval', 3600);
90
-	}
91
-
92
-	/**
93
-	 * @param string[] $groups
94
-	 */
95
-	static private function handleKnownGroups($groups) {
96
-		\OCP\Util::writeLog('user_ldap', 'bgJ "updateGroups" – Dealing with known Groups.', ILogger::DEBUG);
97
-		$query = \OC_DB::prepare('
49
+    static private $groupsFromDB;
50
+
51
+    static private $groupBE;
52
+
53
+    public function __construct(){
54
+        $this->interval = self::getRefreshInterval();
55
+    }
56
+
57
+    /**
58
+     * @param mixed $argument
59
+     */
60
+    public function run($argument){
61
+        self::updateGroups();
62
+    }
63
+
64
+    static public function updateGroups() {
65
+        \OCP\Util::writeLog('user_ldap', 'Run background job "updateGroups"', ILogger::DEBUG);
66
+
67
+        $knownGroups = array_keys(self::getKnownGroups());
68
+        $actualGroups = self::getGroupBE()->getGroups();
69
+
70
+        if(empty($actualGroups) && empty($knownGroups)) {
71
+            \OCP\Util::writeLog('user_ldap',
72
+                'bgJ "updateGroups" – groups do not seem to be configured properly, aborting.',
73
+                ILogger::INFO);
74
+            return;
75
+        }
76
+
77
+        self::handleKnownGroups(array_intersect($actualGroups, $knownGroups));
78
+        self::handleCreatedGroups(array_diff($actualGroups, $knownGroups));
79
+        self::handleRemovedGroups(array_diff($knownGroups, $actualGroups));
80
+
81
+        \OCP\Util::writeLog('user_ldap', 'bgJ "updateGroups" – Finished.', ILogger::DEBUG);
82
+    }
83
+
84
+    /**
85
+     * @return int
86
+     */
87
+    static private function getRefreshInterval() {
88
+        //defaults to every hour
89
+        return \OC::$server->getConfig()->getAppValue('user_ldap', 'bgjRefreshInterval', 3600);
90
+    }
91
+
92
+    /**
93
+     * @param string[] $groups
94
+     */
95
+    static private function handleKnownGroups($groups) {
96
+        \OCP\Util::writeLog('user_ldap', 'bgJ "updateGroups" – Dealing with known Groups.', ILogger::DEBUG);
97
+        $query = \OC_DB::prepare('
98 98
 			UPDATE `*PREFIX*ldap_group_members`
99 99
 			SET `owncloudusers` = ?
100 100
 			WHERE `owncloudname` = ?
101 101
 		');
102
-		foreach($groups as $group) {
103
-			//we assume, that self::$groupsFromDB has been retrieved already
104
-			$knownUsers = unserialize(self::$groupsFromDB[$group]['owncloudusers']);
105
-			$actualUsers = self::getGroupBE()->usersInGroup($group);
106
-			$hasChanged = false;
107
-			foreach(array_diff($knownUsers, $actualUsers) as $removedUser) {
108
-				\OCP\Util::emitHook('OC_User', 'post_removeFromGroup', array('uid' => $removedUser, 'gid' => $group));
109
-				\OCP\Util::writeLog('user_ldap',
110
-				'bgJ "updateGroups" – "'.$removedUser.'" removed from "'.$group.'".',
111
-					ILogger::INFO);
112
-				$hasChanged = true;
113
-			}
114
-			foreach(array_diff($actualUsers, $knownUsers) as $addedUser) {
115
-				\OCP\Util::emitHook('OC_User', 'post_addToGroup', array('uid' => $addedUser, 'gid' => $group));
116
-				\OCP\Util::writeLog('user_ldap',
117
-				'bgJ "updateGroups" – "'.$addedUser.'" added to "'.$group.'".',
118
-					ILogger::INFO);
119
-				$hasChanged = true;
120
-			}
121
-			if($hasChanged) {
122
-				$query->execute(array(serialize($actualUsers), $group));
123
-			}
124
-		}
125
-		\OCP\Util::writeLog('user_ldap',
126
-			'bgJ "updateGroups" – FINISHED dealing with known Groups.',
127
-			ILogger::DEBUG);
128
-	}
129
-
130
-	/**
131
-	 * @param string[] $createdGroups
132
-	 */
133
-	static private function handleCreatedGroups($createdGroups) {
134
-		\OCP\Util::writeLog('user_ldap', 'bgJ "updateGroups" – dealing with created Groups.', ILogger::DEBUG);
135
-		$query = \OC_DB::prepare('
102
+        foreach($groups as $group) {
103
+            //we assume, that self::$groupsFromDB has been retrieved already
104
+            $knownUsers = unserialize(self::$groupsFromDB[$group]['owncloudusers']);
105
+            $actualUsers = self::getGroupBE()->usersInGroup($group);
106
+            $hasChanged = false;
107
+            foreach(array_diff($knownUsers, $actualUsers) as $removedUser) {
108
+                \OCP\Util::emitHook('OC_User', 'post_removeFromGroup', array('uid' => $removedUser, 'gid' => $group));
109
+                \OCP\Util::writeLog('user_ldap',
110
+                'bgJ "updateGroups" – "'.$removedUser.'" removed from "'.$group.'".',
111
+                    ILogger::INFO);
112
+                $hasChanged = true;
113
+            }
114
+            foreach(array_diff($actualUsers, $knownUsers) as $addedUser) {
115
+                \OCP\Util::emitHook('OC_User', 'post_addToGroup', array('uid' => $addedUser, 'gid' => $group));
116
+                \OCP\Util::writeLog('user_ldap',
117
+                'bgJ "updateGroups" – "'.$addedUser.'" added to "'.$group.'".',
118
+                    ILogger::INFO);
119
+                $hasChanged = true;
120
+            }
121
+            if($hasChanged) {
122
+                $query->execute(array(serialize($actualUsers), $group));
123
+            }
124
+        }
125
+        \OCP\Util::writeLog('user_ldap',
126
+            'bgJ "updateGroups" – FINISHED dealing with known Groups.',
127
+            ILogger::DEBUG);
128
+    }
129
+
130
+    /**
131
+     * @param string[] $createdGroups
132
+     */
133
+    static private function handleCreatedGroups($createdGroups) {
134
+        \OCP\Util::writeLog('user_ldap', 'bgJ "updateGroups" – dealing with created Groups.', ILogger::DEBUG);
135
+        $query = \OC_DB::prepare('
136 136
 			INSERT
137 137
 			INTO `*PREFIX*ldap_group_members` (`owncloudname`, `owncloudusers`)
138 138
 			VALUES (?, ?)
139 139
 		');
140
-		foreach($createdGroups as $createdGroup) {
141
-			\OCP\Util::writeLog('user_ldap',
142
-				'bgJ "updateGroups" – new group "'.$createdGroup.'" found.',
143
-				ILogger::INFO);
144
-			$users = serialize(self::getGroupBE()->usersInGroup($createdGroup));
145
-			$query->execute(array($createdGroup, $users));
146
-		}
147
-		\OCP\Util::writeLog('user_ldap',
148
-			'bgJ "updateGroups" – FINISHED dealing with created Groups.',
149
-			ILogger::DEBUG);
150
-	}
151
-
152
-	/**
153
-	 * @param string[] $removedGroups
154
-	 */
155
-	static private function handleRemovedGroups($removedGroups) {
156
-		\OCP\Util::writeLog('user_ldap', 'bgJ "updateGroups" – dealing with removed groups.', ILogger::DEBUG);
157
-		$query = \OC_DB::prepare('
140
+        foreach($createdGroups as $createdGroup) {
141
+            \OCP\Util::writeLog('user_ldap',
142
+                'bgJ "updateGroups" – new group "'.$createdGroup.'" found.',
143
+                ILogger::INFO);
144
+            $users = serialize(self::getGroupBE()->usersInGroup($createdGroup));
145
+            $query->execute(array($createdGroup, $users));
146
+        }
147
+        \OCP\Util::writeLog('user_ldap',
148
+            'bgJ "updateGroups" – FINISHED dealing with created Groups.',
149
+            ILogger::DEBUG);
150
+    }
151
+
152
+    /**
153
+     * @param string[] $removedGroups
154
+     */
155
+    static private function handleRemovedGroups($removedGroups) {
156
+        \OCP\Util::writeLog('user_ldap', 'bgJ "updateGroups" – dealing with removed groups.', ILogger::DEBUG);
157
+        $query = \OC_DB::prepare('
158 158
 			DELETE
159 159
 			FROM `*PREFIX*ldap_group_members`
160 160
 			WHERE `owncloudname` = ?
161 161
 		');
162
-		foreach($removedGroups as $removedGroup) {
163
-			\OCP\Util::writeLog('user_ldap',
164
-				'bgJ "updateGroups" – group "'.$removedGroup.'" was removed.',
165
-				ILogger::INFO);
166
-			$query->execute(array($removedGroup));
167
-		}
168
-		\OCP\Util::writeLog('user_ldap',
169
-			'bgJ "updateGroups" – FINISHED dealing with removed groups.',
170
-			ILogger::DEBUG);
171
-	}
172
-
173
-	/**
174
-	 * @return \OCA\User_LDAP\Group_LDAP|\OCA\User_LDAP\Group_Proxy
175
-	 */
176
-	static private function getGroupBE() {
177
-		if(!is_null(self::$groupBE)) {
178
-			return self::$groupBE;
179
-		}
180
-		$helper = new Helper(\OC::$server->getConfig());
181
-		$configPrefixes = $helper->getServerConfigurationPrefixes(true);
182
-		$ldapWrapper = new LDAP();
183
-		if(count($configPrefixes) === 1) {
184
-			//avoid the proxy when there is only one LDAP server configured
185
-			$dbc = \OC::$server->getDatabaseConnection();
186
-			$userManager = new Manager(
187
-				\OC::$server->getConfig(),
188
-				new FilesystemHelper(),
189
-				new LogWrapper(),
190
-				\OC::$server->getAvatarManager(),
191
-				new \OCP\Image(),
192
-				$dbc,
193
-				\OC::$server->getUserManager(),
194
-				\OC::$server->getNotificationManager());
195
-			$connector = new Connection($ldapWrapper, $configPrefixes[0]);
196
-			$ldapAccess = new Access($connector, $ldapWrapper, $userManager, $helper, \OC::$server->getConfig(), \OC::$server->getUserManager());
197
-			$groupMapper = new GroupMapping($dbc);
198
-			$userMapper  = new UserMapping($dbc);
199
-			$ldapAccess->setGroupMapper($groupMapper);
200
-			$ldapAccess->setUserMapper($userMapper);
201
-			self::$groupBE = new \OCA\User_LDAP\Group_LDAP($ldapAccess, \OC::$server->query('LDAPGroupPluginManager'));
202
-		} else {
203
-			self::$groupBE = new \OCA\User_LDAP\Group_Proxy($configPrefixes, $ldapWrapper, \OC::$server->query('LDAPGroupPluginManager'));
204
-		}
205
-
206
-		return self::$groupBE;
207
-	}
208
-
209
-	/**
210
-	 * @return array
211
-	 */
212
-	static private function getKnownGroups() {
213
-		if(is_array(self::$groupsFromDB)) {
214
-			return self::$groupsFromDB;
215
-		}
216
-		$query = \OC_DB::prepare('
162
+        foreach($removedGroups as $removedGroup) {
163
+            \OCP\Util::writeLog('user_ldap',
164
+                'bgJ "updateGroups" – group "'.$removedGroup.'" was removed.',
165
+                ILogger::INFO);
166
+            $query->execute(array($removedGroup));
167
+        }
168
+        \OCP\Util::writeLog('user_ldap',
169
+            'bgJ "updateGroups" – FINISHED dealing with removed groups.',
170
+            ILogger::DEBUG);
171
+    }
172
+
173
+    /**
174
+     * @return \OCA\User_LDAP\Group_LDAP|\OCA\User_LDAP\Group_Proxy
175
+     */
176
+    static private function getGroupBE() {
177
+        if(!is_null(self::$groupBE)) {
178
+            return self::$groupBE;
179
+        }
180
+        $helper = new Helper(\OC::$server->getConfig());
181
+        $configPrefixes = $helper->getServerConfigurationPrefixes(true);
182
+        $ldapWrapper = new LDAP();
183
+        if(count($configPrefixes) === 1) {
184
+            //avoid the proxy when there is only one LDAP server configured
185
+            $dbc = \OC::$server->getDatabaseConnection();
186
+            $userManager = new Manager(
187
+                \OC::$server->getConfig(),
188
+                new FilesystemHelper(),
189
+                new LogWrapper(),
190
+                \OC::$server->getAvatarManager(),
191
+                new \OCP\Image(),
192
+                $dbc,
193
+                \OC::$server->getUserManager(),
194
+                \OC::$server->getNotificationManager());
195
+            $connector = new Connection($ldapWrapper, $configPrefixes[0]);
196
+            $ldapAccess = new Access($connector, $ldapWrapper, $userManager, $helper, \OC::$server->getConfig(), \OC::$server->getUserManager());
197
+            $groupMapper = new GroupMapping($dbc);
198
+            $userMapper  = new UserMapping($dbc);
199
+            $ldapAccess->setGroupMapper($groupMapper);
200
+            $ldapAccess->setUserMapper($userMapper);
201
+            self::$groupBE = new \OCA\User_LDAP\Group_LDAP($ldapAccess, \OC::$server->query('LDAPGroupPluginManager'));
202
+        } else {
203
+            self::$groupBE = new \OCA\User_LDAP\Group_Proxy($configPrefixes, $ldapWrapper, \OC::$server->query('LDAPGroupPluginManager'));
204
+        }
205
+
206
+        return self::$groupBE;
207
+    }
208
+
209
+    /**
210
+     * @return array
211
+     */
212
+    static private function getKnownGroups() {
213
+        if(is_array(self::$groupsFromDB)) {
214
+            return self::$groupsFromDB;
215
+        }
216
+        $query = \OC_DB::prepare('
217 217
 			SELECT `owncloudname`, `owncloudusers`
218 218
 			FROM `*PREFIX*ldap_group_members`
219 219
 		');
220
-		$result = $query->execute()->fetchAll();
221
-		self::$groupsFromDB = array();
222
-		foreach($result as $dataset) {
223
-			self::$groupsFromDB[$dataset['owncloudname']] = $dataset;
224
-		}
225
-
226
-		return self::$groupsFromDB;
227
-	}
220
+        $result = $query->execute()->fetchAll();
221
+        self::$groupsFromDB = array();
222
+        foreach($result as $dataset) {
223
+            self::$groupsFromDB[$dataset['owncloudname']] = $dataset;
224
+        }
225
+
226
+        return self::$groupsFromDB;
227
+    }
228 228
 }
Please login to merge, or discard this patch.
apps/user_ldap/lib/Connection.php 1 patch
Indentation   +615 added lines, -615 removed lines patch added patch discarded remove patch
@@ -59,620 +59,620 @@
 block discarded – undo
59 59
  * @property string ldapExpertUUIDGroupAttr
60 60
  */
61 61
 class Connection extends LDAPUtility {
62
-	private $ldapConnectionRes = null;
63
-	private $configPrefix;
64
-	private $configID;
65
-	private $configured = false;
66
-	private $hasPagedResultSupport = true;
67
-	//whether connection should be kept on __destruct
68
-	private $dontDestruct = false;
69
-
70
-	/**
71
-	 * @var bool runtime flag that indicates whether supported primary groups are available
72
-	 */
73
-	public $hasPrimaryGroups = true;
74
-
75
-	/**
76
-	 * @var bool runtime flag that indicates whether supported POSIX gidNumber are available
77
-	 */
78
-	public $hasGidNumber = true;
79
-
80
-	//cache handler
81
-	protected $cache;
82
-
83
-	/** @var Configuration settings handler **/
84
-	protected $configuration;
85
-
86
-	protected $doNotValidate = false;
87
-
88
-	protected $ignoreValidation = false;
89
-
90
-	protected $bindResult = [];
91
-
92
-	/**
93
-	 * Constructor
94
-	 * @param ILDAPWrapper $ldap
95
-	 * @param string $configPrefix a string with the prefix for the configkey column (appconfig table)
96
-	 * @param string|null $configID a string with the value for the appid column (appconfig table) or null for on-the-fly connections
97
-	 */
98
-	public function __construct(ILDAPWrapper $ldap, $configPrefix = '', $configID = 'user_ldap') {
99
-		parent::__construct($ldap);
100
-		$this->configPrefix = $configPrefix;
101
-		$this->configID = $configID;
102
-		$this->configuration = new Configuration($configPrefix,
103
-												 !is_null($configID));
104
-		$memcache = \OC::$server->getMemCacheFactory();
105
-		if($memcache->isAvailable()) {
106
-			$this->cache = $memcache->createDistributed();
107
-		}
108
-		$helper = new Helper(\OC::$server->getConfig());
109
-		$this->doNotValidate = !in_array($this->configPrefix,
110
-			$helper->getServerConfigurationPrefixes());
111
-		$this->hasPagedResultSupport =
112
-			(int)$this->configuration->ldapPagingSize !== 0
113
-			|| $this->ldap->hasPagedResultSupport();
114
-	}
115
-
116
-	public function __destruct() {
117
-		if(!$this->dontDestruct && $this->ldap->isResource($this->ldapConnectionRes)) {
118
-			@$this->ldap->unbind($this->ldapConnectionRes);
119
-			$this->bindResult = [];
120
-		}
121
-	}
122
-
123
-	/**
124
-	 * defines behaviour when the instance is cloned
125
-	 */
126
-	public function __clone() {
127
-		$this->configuration = new Configuration($this->configPrefix,
128
-												 !is_null($this->configID));
129
-		$this->ldapConnectionRes = null;
130
-		$this->dontDestruct = true;
131
-	}
132
-
133
-	/**
134
-	 * @param string $name
135
-	 * @return bool|mixed
136
-	 */
137
-	public function __get($name) {
138
-		if(!$this->configured) {
139
-			$this->readConfiguration();
140
-		}
141
-
142
-		if($name === 'hasPagedResultSupport') {
143
-			return $this->hasPagedResultSupport;
144
-		}
145
-
146
-		return $this->configuration->$name;
147
-	}
148
-
149
-	/**
150
-	 * @param string $name
151
-	 * @param mixed $value
152
-	 */
153
-	public function __set($name, $value) {
154
-		$this->doNotValidate = false;
155
-		$before = $this->configuration->$name;
156
-		$this->configuration->$name = $value;
157
-		$after = $this->configuration->$name;
158
-		if($before !== $after) {
159
-			if ($this->configID !== '' && $this->configID !== null) {
160
-				$this->configuration->saveConfiguration();
161
-			}
162
-			$this->validateConfiguration();
163
-		}
164
-	}
165
-
166
-	/**
167
-	 * sets whether the result of the configuration validation shall
168
-	 * be ignored when establishing the connection. Used by the Wizard
169
-	 * in early configuration state.
170
-	 * @param bool $state
171
-	 */
172
-	public function setIgnoreValidation($state) {
173
-		$this->ignoreValidation = (bool)$state;
174
-	}
175
-
176
-	/**
177
-	 * initializes the LDAP backend
178
-	 * @param bool $force read the config settings no matter what
179
-	 */
180
-	public function init($force = false) {
181
-		$this->readConfiguration($force);
182
-		$this->establishConnection();
183
-	}
184
-
185
-	/**
186
-	 * Returns the LDAP handler
187
-	 */
188
-	public function getConnectionResource() {
189
-		if(!$this->ldapConnectionRes) {
190
-			$this->init();
191
-		} else if(!$this->ldap->isResource($this->ldapConnectionRes)) {
192
-			$this->ldapConnectionRes = null;
193
-			$this->establishConnection();
194
-		}
195
-		if(is_null($this->ldapConnectionRes)) {
196
-			\OCP\Util::writeLog('user_ldap', 'No LDAP Connection to server ' . $this->configuration->ldapHost, ILogger::ERROR);
197
-			throw new ServerNotAvailableException('Connection to LDAP server could not be established');
198
-		}
199
-		return $this->ldapConnectionRes;
200
-	}
201
-
202
-	/**
203
-	 * resets the connection resource
204
-	 */
205
-	public function resetConnectionResource() {
206
-		if(!is_null($this->ldapConnectionRes)) {
207
-			@$this->ldap->unbind($this->ldapConnectionRes);
208
-			$this->ldapConnectionRes = null;
209
-			$this->bindResult = [];
210
-		}
211
-	}
212
-
213
-	/**
214
-	 * @param string|null $key
215
-	 * @return string
216
-	 */
217
-	private function getCacheKey($key) {
218
-		$prefix = 'LDAP-'.$this->configID.'-'.$this->configPrefix.'-';
219
-		if(is_null($key)) {
220
-			return $prefix;
221
-		}
222
-		return $prefix.hash('sha256', $key);
223
-	}
224
-
225
-	/**
226
-	 * @param string $key
227
-	 * @return mixed|null
228
-	 */
229
-	public function getFromCache($key) {
230
-		if(!$this->configured) {
231
-			$this->readConfiguration();
232
-		}
233
-		if(is_null($this->cache) || !$this->configuration->ldapCacheTTL) {
234
-			return null;
235
-		}
236
-		$key = $this->getCacheKey($key);
237
-
238
-		return json_decode(base64_decode($this->cache->get($key)), true);
239
-	}
240
-
241
-	/**
242
-	 * @param string $key
243
-	 * @param mixed $value
244
-	 *
245
-	 * @return string
246
-	 */
247
-	public function writeToCache($key, $value) {
248
-		if(!$this->configured) {
249
-			$this->readConfiguration();
250
-		}
251
-		if(is_null($this->cache)
252
-			|| !$this->configuration->ldapCacheTTL
253
-			|| !$this->configuration->ldapConfigurationActive) {
254
-			return null;
255
-		}
256
-		$key   = $this->getCacheKey($key);
257
-		$value = base64_encode(json_encode($value));
258
-		$this->cache->set($key, $value, $this->configuration->ldapCacheTTL);
259
-	}
260
-
261
-	public function clearCache() {
262
-		if(!is_null($this->cache)) {
263
-			$this->cache->clear($this->getCacheKey(null));
264
-		}
265
-	}
266
-
267
-	/**
268
-	 * Caches the general LDAP configuration.
269
-	 * @param bool $force optional. true, if the re-read should be forced. defaults
270
-	 * to false.
271
-	 * @return null
272
-	 */
273
-	private function readConfiguration($force = false) {
274
-		if((!$this->configured || $force) && !is_null($this->configID)) {
275
-			$this->configuration->readConfiguration();
276
-			$this->configured = $this->validateConfiguration();
277
-		}
278
-	}
279
-
280
-	/**
281
-	 * set LDAP configuration with values delivered by an array, not read from configuration
282
-	 * @param array $config array that holds the config parameters in an associated array
283
-	 * @param array &$setParameters optional; array where the set fields will be given to
284
-	 * @return boolean true if config validates, false otherwise. Check with $setParameters for detailed success on single parameters
285
-	 */
286
-	public function setConfiguration($config, &$setParameters = null) {
287
-		if(is_null($setParameters)) {
288
-			$setParameters = array();
289
-		}
290
-		$this->doNotValidate = false;
291
-		$this->configuration->setConfiguration($config, $setParameters);
292
-		if(count($setParameters) > 0) {
293
-			$this->configured = $this->validateConfiguration();
294
-		}
295
-
296
-
297
-		return $this->configured;
298
-	}
299
-
300
-	/**
301
-	 * saves the current Configuration in the database and empties the
302
-	 * cache
303
-	 * @return null
304
-	 */
305
-	public function saveConfiguration() {
306
-		$this->configuration->saveConfiguration();
307
-		$this->clearCache();
308
-	}
309
-
310
-	/**
311
-	 * get the current LDAP configuration
312
-	 * @return array
313
-	 */
314
-	public function getConfiguration() {
315
-		$this->readConfiguration();
316
-		$config = $this->configuration->getConfiguration();
317
-		$cta = $this->configuration->getConfigTranslationArray();
318
-		$result = array();
319
-		foreach($cta as $dbkey => $configkey) {
320
-			switch($configkey) {
321
-				case 'homeFolderNamingRule':
322
-					if(strpos($config[$configkey], 'attr:') === 0) {
323
-						$result[$dbkey] = substr($config[$configkey], 5);
324
-					} else {
325
-						$result[$dbkey] = '';
326
-					}
327
-					break;
328
-				case 'ldapBase':
329
-				case 'ldapBaseUsers':
330
-				case 'ldapBaseGroups':
331
-				case 'ldapAttributesForUserSearch':
332
-				case 'ldapAttributesForGroupSearch':
333
-					if(is_array($config[$configkey])) {
334
-						$result[$dbkey] = implode("\n", $config[$configkey]);
335
-						break;
336
-					} //else follows default
337
-				default:
338
-					$result[$dbkey] = $config[$configkey];
339
-			}
340
-		}
341
-		return $result;
342
-	}
343
-
344
-	private function doSoftValidation() {
345
-		//if User or Group Base are not set, take over Base DN setting
346
-		foreach(array('ldapBaseUsers', 'ldapBaseGroups') as $keyBase) {
347
-			$val = $this->configuration->$keyBase;
348
-			if(empty($val)) {
349
-				$this->configuration->$keyBase = $this->configuration->ldapBase;
350
-			}
351
-		}
352
-
353
-		foreach(array('ldapExpertUUIDUserAttr'  => 'ldapUuidUserAttribute',
354
-					  'ldapExpertUUIDGroupAttr' => 'ldapUuidGroupAttribute')
355
-				as $expertSetting => $effectiveSetting) {
356
-			$uuidOverride = $this->configuration->$expertSetting;
357
-			if(!empty($uuidOverride)) {
358
-				$this->configuration->$effectiveSetting = $uuidOverride;
359
-			} else {
360
-				$uuidAttributes = Access::UUID_ATTRIBUTES;
361
-				array_unshift($uuidAttributes, 'auto');
362
-				if(!in_array($this->configuration->$effectiveSetting,
363
-							$uuidAttributes)
364
-					&& (!is_null($this->configID))) {
365
-					$this->configuration->$effectiveSetting = 'auto';
366
-					$this->configuration->saveConfiguration();
367
-					\OCP\Util::writeLog('user_ldap',
368
-										'Illegal value for the '.
369
-										$effectiveSetting.', '.'reset to '.
370
-										'autodetect.', ILogger::INFO);
371
-				}
372
-
373
-			}
374
-		}
375
-
376
-		$backupPort = (int)$this->configuration->ldapBackupPort;
377
-		if ($backupPort <= 0) {
378
-			$this->configuration->backupPort = $this->configuration->ldapPort;
379
-		}
380
-
381
-		//make sure empty search attributes are saved as simple, empty array
382
-		$saKeys = array('ldapAttributesForUserSearch',
383
-						'ldapAttributesForGroupSearch');
384
-		foreach($saKeys as $key) {
385
-			$val = $this->configuration->$key;
386
-			if(is_array($val) && count($val) === 1 && empty($val[0])) {
387
-				$this->configuration->$key = array();
388
-			}
389
-		}
390
-
391
-		if((stripos($this->configuration->ldapHost, 'ldaps://') === 0)
392
-			&& $this->configuration->ldapTLS) {
393
-			$this->configuration->ldapTLS = false;
394
-			\OCP\Util::writeLog(
395
-				'user_ldap',
396
-				'LDAPS (already using secure connection) and TLS do not work together. Switched off TLS.',
397
-				ILogger::INFO
398
-			);
399
-		}
400
-	}
401
-
402
-	/**
403
-	 * @return bool
404
-	 */
405
-	private function doCriticalValidation() {
406
-		$configurationOK = true;
407
-		$errorStr = 'Configuration Error (prefix '.
408
-			(string)$this->configPrefix .'): ';
409
-
410
-		//options that shall not be empty
411
-		$options = array('ldapHost', 'ldapPort', 'ldapUserDisplayName',
412
-						 'ldapGroupDisplayName', 'ldapLoginFilter');
413
-		foreach($options as $key) {
414
-			$val = $this->configuration->$key;
415
-			if(empty($val)) {
416
-				switch($key) {
417
-					case 'ldapHost':
418
-						$subj = 'LDAP Host';
419
-						break;
420
-					case 'ldapPort':
421
-						$subj = 'LDAP Port';
422
-						break;
423
-					case 'ldapUserDisplayName':
424
-						$subj = 'LDAP User Display Name';
425
-						break;
426
-					case 'ldapGroupDisplayName':
427
-						$subj = 'LDAP Group Display Name';
428
-						break;
429
-					case 'ldapLoginFilter':
430
-						$subj = 'LDAP Login Filter';
431
-						break;
432
-					default:
433
-						$subj = $key;
434
-						break;
435
-				}
436
-				$configurationOK = false;
437
-				\OCP\Util::writeLog(
438
-					'user_ldap',
439
-					$errorStr.'No '.$subj.' given!',
440
-					ILogger::WARN
441
-				);
442
-			}
443
-		}
444
-
445
-		//combinations
446
-		$agent = $this->configuration->ldapAgentName;
447
-		$pwd = $this->configuration->ldapAgentPassword;
448
-		if (
449
-			($agent === ''  && $pwd !== '')
450
-			|| ($agent !== '' && $pwd === '')
451
-		) {
452
-			\OCP\Util::writeLog(
453
-				'user_ldap',
454
-				$errorStr.'either no password is given for the user ' .
455
-					'agent or a password is given, but not an LDAP agent.',
456
-				ILogger::WARN);
457
-			$configurationOK = false;
458
-		}
459
-
460
-		$base = $this->configuration->ldapBase;
461
-		$baseUsers = $this->configuration->ldapBaseUsers;
462
-		$baseGroups = $this->configuration->ldapBaseGroups;
463
-
464
-		if(empty($base) && empty($baseUsers) && empty($baseGroups)) {
465
-			\OCP\Util::writeLog(
466
-				'user_ldap',
467
-				$errorStr.'Not a single Base DN given.',
468
-				ILogger::WARN
469
-			);
470
-			$configurationOK = false;
471
-		}
472
-
473
-		if(mb_strpos($this->configuration->ldapLoginFilter, '%uid', 0, 'UTF-8')
474
-		   === false) {
475
-			\OCP\Util::writeLog(
476
-				'user_ldap',
477
-				$errorStr.'login filter does not contain %uid place holder.',
478
-				ILogger::WARN
479
-			);
480
-			$configurationOK = false;
481
-		}
482
-
483
-		return $configurationOK;
484
-	}
485
-
486
-	/**
487
-	 * Validates the user specified configuration
488
-	 * @return bool true if configuration seems OK, false otherwise
489
-	 */
490
-	private function validateConfiguration() {
491
-
492
-		if($this->doNotValidate) {
493
-			//don't do a validation if it is a new configuration with pure
494
-			//default values. Will be allowed on changes via __set or
495
-			//setConfiguration
496
-			return false;
497
-		}
498
-
499
-		// first step: "soft" checks: settings that are not really
500
-		// necessary, but advisable. If left empty, give an info message
501
-		$this->doSoftValidation();
502
-
503
-		//second step: critical checks. If left empty or filled wrong, mark as
504
-		//not configured and give a warning.
505
-		return $this->doCriticalValidation();
506
-	}
507
-
508
-
509
-	/**
510
-	 * Connects and Binds to LDAP
511
-	 */
512
-	private function establishConnection() {
513
-		if(!$this->configuration->ldapConfigurationActive) {
514
-			return null;
515
-		}
516
-		static $phpLDAPinstalled = true;
517
-		if(!$phpLDAPinstalled) {
518
-			return false;
519
-		}
520
-		if(!$this->ignoreValidation && !$this->configured) {
521
-			\OCP\Util::writeLog(
522
-				'user_ldap',
523
-				'Configuration is invalid, cannot connect',
524
-				ILogger::WARN
525
-			);
526
-			return false;
527
-		}
528
-		if(!$this->ldapConnectionRes) {
529
-			if(!$this->ldap->areLDAPFunctionsAvailable()) {
530
-				$phpLDAPinstalled = false;
531
-				\OCP\Util::writeLog(
532
-					'user_ldap',
533
-					'function ldap_connect is not available. Make sure that the PHP ldap module is installed.',
534
-					ILogger::ERROR
535
-				);
536
-
537
-				return false;
538
-			}
539
-			if($this->configuration->turnOffCertCheck) {
540
-				if(putenv('LDAPTLS_REQCERT=never')) {
541
-					\OCP\Util::writeLog('user_ldap',
542
-						'Turned off SSL certificate validation successfully.',
543
-						ILogger::DEBUG);
544
-				} else {
545
-					\OCP\Util::writeLog(
546
-						'user_ldap',
547
-						'Could not turn off SSL certificate validation.',
548
-						ILogger::WARN
549
-					);
550
-				}
551
-			}
552
-
553
-			$isOverrideMainServer = ($this->configuration->ldapOverrideMainServer
554
-				|| $this->getFromCache('overrideMainServer'));
555
-			$isBackupHost = (trim($this->configuration->ldapBackupHost) !== "");
556
-			$bindStatus = false;
557
-			$error = -1;
558
-			try {
559
-				if (!$isOverrideMainServer) {
560
-					$this->doConnect($this->configuration->ldapHost,
561
-						$this->configuration->ldapPort);
562
-					$bindStatus = $this->bind();
563
-					$error = $this->ldap->isResource($this->ldapConnectionRes) ?
564
-						$this->ldap->errno($this->ldapConnectionRes) : -1;
565
-				}
566
-				if($bindStatus === true) {
567
-					return $bindStatus;
568
-				}
569
-			} catch (ServerNotAvailableException $e) {
570
-				if(!$isBackupHost) {
571
-					throw $e;
572
-				}
573
-			}
574
-
575
-			//if LDAP server is not reachable, try the Backup (Replica!) Server
576
-			if($isBackupHost && ($error !== 0 || $isOverrideMainServer)) {
577
-				$this->doConnect($this->configuration->ldapBackupHost,
578
-								 $this->configuration->ldapBackupPort);
579
-				$this->bindResult = [];
580
-				$bindStatus = $this->bind();
581
-				$error = $this->ldap->isResource($this->ldapConnectionRes) ?
582
-					$this->ldap->errno($this->ldapConnectionRes) : -1;
583
-				if($bindStatus && $error === 0 && !$this->getFromCache('overrideMainServer')) {
584
-					//when bind to backup server succeeded and failed to main server,
585
-					//skip contacting him until next cache refresh
586
-					$this->writeToCache('overrideMainServer', true);
587
-				}
588
-			}
589
-
590
-			return $bindStatus;
591
-		}
592
-		return null;
593
-	}
594
-
595
-	/**
596
-	 * @param string $host
597
-	 * @param string $port
598
-	 * @return bool
599
-	 * @throws \OC\ServerNotAvailableException
600
-	 */
601
-	private function doConnect($host, $port) {
602
-		if ($host === '') {
603
-			return false;
604
-		}
605
-
606
-		$this->ldapConnectionRes = $this->ldap->connect($host, $port);
607
-
608
-		if(!$this->ldap->setOption($this->ldapConnectionRes, LDAP_OPT_PROTOCOL_VERSION, 3)) {
609
-			throw new ServerNotAvailableException('Could not set required LDAP Protocol version.');
610
-		}
611
-
612
-		if(!$this->ldap->setOption($this->ldapConnectionRes, LDAP_OPT_REFERRALS, 0)) {
613
-			throw new ServerNotAvailableException('Could not disable LDAP referrals.');
614
-		}
615
-
616
-		if($this->configuration->ldapTLS) {
617
-			if(!$this->ldap->startTls($this->ldapConnectionRes)) {
618
-				throw new ServerNotAvailableException('Start TLS failed, when connecting to LDAP host ' . $host . '.');
619
-			}
620
-		}
621
-
622
-		return true;
623
-	}
624
-
625
-	/**
626
-	 * Binds to LDAP
627
-	 */
628
-	public function bind() {
629
-		if(!$this->configuration->ldapConfigurationActive) {
630
-			return false;
631
-		}
632
-		$cr = $this->ldapConnectionRes;
633
-		if(!$this->ldap->isResource($cr)) {
634
-			$cr = $this->getConnectionResource();
635
-		}
636
-
637
-		if(
638
-			count($this->bindResult) !== 0
639
-			&& $this->bindResult['dn'] === $this->configuration->ldapAgentName
640
-			&& \OC::$server->getHasher()->verify(
641
-				$this->configPrefix . $this->configuration->ldapAgentPassword,
642
-				$this->bindResult['hash']
643
-			)
644
-		) {
645
-			// don't attempt to bind again with the same data as before
646
-			// bind might have been invoked via getConnectionResource(),
647
-			// but we need results specifically for e.g. user login
648
-			return $this->bindResult['result'];
649
-		}
650
-
651
-		$ldapLogin = @$this->ldap->bind($cr,
652
-										$this->configuration->ldapAgentName,
653
-										$this->configuration->ldapAgentPassword);
654
-
655
-		$this->bindResult = [
656
-			'dn' => $this->configuration->ldapAgentName,
657
-			'hash' => \OC::$server->getHasher()->hash($this->configPrefix . $this->configuration->ldapAgentPassword),
658
-			'result' => $ldapLogin,
659
-		];
660
-
661
-		if(!$ldapLogin) {
662
-			$errno = $this->ldap->errno($cr);
663
-
664
-			\OCP\Util::writeLog('user_ldap',
665
-				'Bind failed: ' . $errno . ': ' . $this->ldap->error($cr),
666
-				ILogger::WARN);
667
-
668
-			// Set to failure mode, if LDAP error code is not LDAP_SUCCESS or LDAP_INVALID_CREDENTIALS
669
-			if($errno !== 0x00 && $errno !== 0x31) {
670
-				$this->ldapConnectionRes = null;
671
-			}
672
-
673
-			return false;
674
-		}
675
-		return true;
676
-	}
62
+    private $ldapConnectionRes = null;
63
+    private $configPrefix;
64
+    private $configID;
65
+    private $configured = false;
66
+    private $hasPagedResultSupport = true;
67
+    //whether connection should be kept on __destruct
68
+    private $dontDestruct = false;
69
+
70
+    /**
71
+     * @var bool runtime flag that indicates whether supported primary groups are available
72
+     */
73
+    public $hasPrimaryGroups = true;
74
+
75
+    /**
76
+     * @var bool runtime flag that indicates whether supported POSIX gidNumber are available
77
+     */
78
+    public $hasGidNumber = true;
79
+
80
+    //cache handler
81
+    protected $cache;
82
+
83
+    /** @var Configuration settings handler **/
84
+    protected $configuration;
85
+
86
+    protected $doNotValidate = false;
87
+
88
+    protected $ignoreValidation = false;
89
+
90
+    protected $bindResult = [];
91
+
92
+    /**
93
+     * Constructor
94
+     * @param ILDAPWrapper $ldap
95
+     * @param string $configPrefix a string with the prefix for the configkey column (appconfig table)
96
+     * @param string|null $configID a string with the value for the appid column (appconfig table) or null for on-the-fly connections
97
+     */
98
+    public function __construct(ILDAPWrapper $ldap, $configPrefix = '', $configID = 'user_ldap') {
99
+        parent::__construct($ldap);
100
+        $this->configPrefix = $configPrefix;
101
+        $this->configID = $configID;
102
+        $this->configuration = new Configuration($configPrefix,
103
+                                                    !is_null($configID));
104
+        $memcache = \OC::$server->getMemCacheFactory();
105
+        if($memcache->isAvailable()) {
106
+            $this->cache = $memcache->createDistributed();
107
+        }
108
+        $helper = new Helper(\OC::$server->getConfig());
109
+        $this->doNotValidate = !in_array($this->configPrefix,
110
+            $helper->getServerConfigurationPrefixes());
111
+        $this->hasPagedResultSupport =
112
+            (int)$this->configuration->ldapPagingSize !== 0
113
+            || $this->ldap->hasPagedResultSupport();
114
+    }
115
+
116
+    public function __destruct() {
117
+        if(!$this->dontDestruct && $this->ldap->isResource($this->ldapConnectionRes)) {
118
+            @$this->ldap->unbind($this->ldapConnectionRes);
119
+            $this->bindResult = [];
120
+        }
121
+    }
122
+
123
+    /**
124
+     * defines behaviour when the instance is cloned
125
+     */
126
+    public function __clone() {
127
+        $this->configuration = new Configuration($this->configPrefix,
128
+                                                    !is_null($this->configID));
129
+        $this->ldapConnectionRes = null;
130
+        $this->dontDestruct = true;
131
+    }
132
+
133
+    /**
134
+     * @param string $name
135
+     * @return bool|mixed
136
+     */
137
+    public function __get($name) {
138
+        if(!$this->configured) {
139
+            $this->readConfiguration();
140
+        }
141
+
142
+        if($name === 'hasPagedResultSupport') {
143
+            return $this->hasPagedResultSupport;
144
+        }
145
+
146
+        return $this->configuration->$name;
147
+    }
148
+
149
+    /**
150
+     * @param string $name
151
+     * @param mixed $value
152
+     */
153
+    public function __set($name, $value) {
154
+        $this->doNotValidate = false;
155
+        $before = $this->configuration->$name;
156
+        $this->configuration->$name = $value;
157
+        $after = $this->configuration->$name;
158
+        if($before !== $after) {
159
+            if ($this->configID !== '' && $this->configID !== null) {
160
+                $this->configuration->saveConfiguration();
161
+            }
162
+            $this->validateConfiguration();
163
+        }
164
+    }
165
+
166
+    /**
167
+     * sets whether the result of the configuration validation shall
168
+     * be ignored when establishing the connection. Used by the Wizard
169
+     * in early configuration state.
170
+     * @param bool $state
171
+     */
172
+    public function setIgnoreValidation($state) {
173
+        $this->ignoreValidation = (bool)$state;
174
+    }
175
+
176
+    /**
177
+     * initializes the LDAP backend
178
+     * @param bool $force read the config settings no matter what
179
+     */
180
+    public function init($force = false) {
181
+        $this->readConfiguration($force);
182
+        $this->establishConnection();
183
+    }
184
+
185
+    /**
186
+     * Returns the LDAP handler
187
+     */
188
+    public function getConnectionResource() {
189
+        if(!$this->ldapConnectionRes) {
190
+            $this->init();
191
+        } else if(!$this->ldap->isResource($this->ldapConnectionRes)) {
192
+            $this->ldapConnectionRes = null;
193
+            $this->establishConnection();
194
+        }
195
+        if(is_null($this->ldapConnectionRes)) {
196
+            \OCP\Util::writeLog('user_ldap', 'No LDAP Connection to server ' . $this->configuration->ldapHost, ILogger::ERROR);
197
+            throw new ServerNotAvailableException('Connection to LDAP server could not be established');
198
+        }
199
+        return $this->ldapConnectionRes;
200
+    }
201
+
202
+    /**
203
+     * resets the connection resource
204
+     */
205
+    public function resetConnectionResource() {
206
+        if(!is_null($this->ldapConnectionRes)) {
207
+            @$this->ldap->unbind($this->ldapConnectionRes);
208
+            $this->ldapConnectionRes = null;
209
+            $this->bindResult = [];
210
+        }
211
+    }
212
+
213
+    /**
214
+     * @param string|null $key
215
+     * @return string
216
+     */
217
+    private function getCacheKey($key) {
218
+        $prefix = 'LDAP-'.$this->configID.'-'.$this->configPrefix.'-';
219
+        if(is_null($key)) {
220
+            return $prefix;
221
+        }
222
+        return $prefix.hash('sha256', $key);
223
+    }
224
+
225
+    /**
226
+     * @param string $key
227
+     * @return mixed|null
228
+     */
229
+    public function getFromCache($key) {
230
+        if(!$this->configured) {
231
+            $this->readConfiguration();
232
+        }
233
+        if(is_null($this->cache) || !$this->configuration->ldapCacheTTL) {
234
+            return null;
235
+        }
236
+        $key = $this->getCacheKey($key);
237
+
238
+        return json_decode(base64_decode($this->cache->get($key)), true);
239
+    }
240
+
241
+    /**
242
+     * @param string $key
243
+     * @param mixed $value
244
+     *
245
+     * @return string
246
+     */
247
+    public function writeToCache($key, $value) {
248
+        if(!$this->configured) {
249
+            $this->readConfiguration();
250
+        }
251
+        if(is_null($this->cache)
252
+            || !$this->configuration->ldapCacheTTL
253
+            || !$this->configuration->ldapConfigurationActive) {
254
+            return null;
255
+        }
256
+        $key   = $this->getCacheKey($key);
257
+        $value = base64_encode(json_encode($value));
258
+        $this->cache->set($key, $value, $this->configuration->ldapCacheTTL);
259
+    }
260
+
261
+    public function clearCache() {
262
+        if(!is_null($this->cache)) {
263
+            $this->cache->clear($this->getCacheKey(null));
264
+        }
265
+    }
266
+
267
+    /**
268
+     * Caches the general LDAP configuration.
269
+     * @param bool $force optional. true, if the re-read should be forced. defaults
270
+     * to false.
271
+     * @return null
272
+     */
273
+    private function readConfiguration($force = false) {
274
+        if((!$this->configured || $force) && !is_null($this->configID)) {
275
+            $this->configuration->readConfiguration();
276
+            $this->configured = $this->validateConfiguration();
277
+        }
278
+    }
279
+
280
+    /**
281
+     * set LDAP configuration with values delivered by an array, not read from configuration
282
+     * @param array $config array that holds the config parameters in an associated array
283
+     * @param array &$setParameters optional; array where the set fields will be given to
284
+     * @return boolean true if config validates, false otherwise. Check with $setParameters for detailed success on single parameters
285
+     */
286
+    public function setConfiguration($config, &$setParameters = null) {
287
+        if(is_null($setParameters)) {
288
+            $setParameters = array();
289
+        }
290
+        $this->doNotValidate = false;
291
+        $this->configuration->setConfiguration($config, $setParameters);
292
+        if(count($setParameters) > 0) {
293
+            $this->configured = $this->validateConfiguration();
294
+        }
295
+
296
+
297
+        return $this->configured;
298
+    }
299
+
300
+    /**
301
+     * saves the current Configuration in the database and empties the
302
+     * cache
303
+     * @return null
304
+     */
305
+    public function saveConfiguration() {
306
+        $this->configuration->saveConfiguration();
307
+        $this->clearCache();
308
+    }
309
+
310
+    /**
311
+     * get the current LDAP configuration
312
+     * @return array
313
+     */
314
+    public function getConfiguration() {
315
+        $this->readConfiguration();
316
+        $config = $this->configuration->getConfiguration();
317
+        $cta = $this->configuration->getConfigTranslationArray();
318
+        $result = array();
319
+        foreach($cta as $dbkey => $configkey) {
320
+            switch($configkey) {
321
+                case 'homeFolderNamingRule':
322
+                    if(strpos($config[$configkey], 'attr:') === 0) {
323
+                        $result[$dbkey] = substr($config[$configkey], 5);
324
+                    } else {
325
+                        $result[$dbkey] = '';
326
+                    }
327
+                    break;
328
+                case 'ldapBase':
329
+                case 'ldapBaseUsers':
330
+                case 'ldapBaseGroups':
331
+                case 'ldapAttributesForUserSearch':
332
+                case 'ldapAttributesForGroupSearch':
333
+                    if(is_array($config[$configkey])) {
334
+                        $result[$dbkey] = implode("\n", $config[$configkey]);
335
+                        break;
336
+                    } //else follows default
337
+                default:
338
+                    $result[$dbkey] = $config[$configkey];
339
+            }
340
+        }
341
+        return $result;
342
+    }
343
+
344
+    private function doSoftValidation() {
345
+        //if User or Group Base are not set, take over Base DN setting
346
+        foreach(array('ldapBaseUsers', 'ldapBaseGroups') as $keyBase) {
347
+            $val = $this->configuration->$keyBase;
348
+            if(empty($val)) {
349
+                $this->configuration->$keyBase = $this->configuration->ldapBase;
350
+            }
351
+        }
352
+
353
+        foreach(array('ldapExpertUUIDUserAttr'  => 'ldapUuidUserAttribute',
354
+                        'ldapExpertUUIDGroupAttr' => 'ldapUuidGroupAttribute')
355
+                as $expertSetting => $effectiveSetting) {
356
+            $uuidOverride = $this->configuration->$expertSetting;
357
+            if(!empty($uuidOverride)) {
358
+                $this->configuration->$effectiveSetting = $uuidOverride;
359
+            } else {
360
+                $uuidAttributes = Access::UUID_ATTRIBUTES;
361
+                array_unshift($uuidAttributes, 'auto');
362
+                if(!in_array($this->configuration->$effectiveSetting,
363
+                            $uuidAttributes)
364
+                    && (!is_null($this->configID))) {
365
+                    $this->configuration->$effectiveSetting = 'auto';
366
+                    $this->configuration->saveConfiguration();
367
+                    \OCP\Util::writeLog('user_ldap',
368
+                                        'Illegal value for the '.
369
+                                        $effectiveSetting.', '.'reset to '.
370
+                                        'autodetect.', ILogger::INFO);
371
+                }
372
+
373
+            }
374
+        }
375
+
376
+        $backupPort = (int)$this->configuration->ldapBackupPort;
377
+        if ($backupPort <= 0) {
378
+            $this->configuration->backupPort = $this->configuration->ldapPort;
379
+        }
380
+
381
+        //make sure empty search attributes are saved as simple, empty array
382
+        $saKeys = array('ldapAttributesForUserSearch',
383
+                        'ldapAttributesForGroupSearch');
384
+        foreach($saKeys as $key) {
385
+            $val = $this->configuration->$key;
386
+            if(is_array($val) && count($val) === 1 && empty($val[0])) {
387
+                $this->configuration->$key = array();
388
+            }
389
+        }
390
+
391
+        if((stripos($this->configuration->ldapHost, 'ldaps://') === 0)
392
+            && $this->configuration->ldapTLS) {
393
+            $this->configuration->ldapTLS = false;
394
+            \OCP\Util::writeLog(
395
+                'user_ldap',
396
+                'LDAPS (already using secure connection) and TLS do not work together. Switched off TLS.',
397
+                ILogger::INFO
398
+            );
399
+        }
400
+    }
401
+
402
+    /**
403
+     * @return bool
404
+     */
405
+    private function doCriticalValidation() {
406
+        $configurationOK = true;
407
+        $errorStr = 'Configuration Error (prefix '.
408
+            (string)$this->configPrefix .'): ';
409
+
410
+        //options that shall not be empty
411
+        $options = array('ldapHost', 'ldapPort', 'ldapUserDisplayName',
412
+                            'ldapGroupDisplayName', 'ldapLoginFilter');
413
+        foreach($options as $key) {
414
+            $val = $this->configuration->$key;
415
+            if(empty($val)) {
416
+                switch($key) {
417
+                    case 'ldapHost':
418
+                        $subj = 'LDAP Host';
419
+                        break;
420
+                    case 'ldapPort':
421
+                        $subj = 'LDAP Port';
422
+                        break;
423
+                    case 'ldapUserDisplayName':
424
+                        $subj = 'LDAP User Display Name';
425
+                        break;
426
+                    case 'ldapGroupDisplayName':
427
+                        $subj = 'LDAP Group Display Name';
428
+                        break;
429
+                    case 'ldapLoginFilter':
430
+                        $subj = 'LDAP Login Filter';
431
+                        break;
432
+                    default:
433
+                        $subj = $key;
434
+                        break;
435
+                }
436
+                $configurationOK = false;
437
+                \OCP\Util::writeLog(
438
+                    'user_ldap',
439
+                    $errorStr.'No '.$subj.' given!',
440
+                    ILogger::WARN
441
+                );
442
+            }
443
+        }
444
+
445
+        //combinations
446
+        $agent = $this->configuration->ldapAgentName;
447
+        $pwd = $this->configuration->ldapAgentPassword;
448
+        if (
449
+            ($agent === ''  && $pwd !== '')
450
+            || ($agent !== '' && $pwd === '')
451
+        ) {
452
+            \OCP\Util::writeLog(
453
+                'user_ldap',
454
+                $errorStr.'either no password is given for the user ' .
455
+                    'agent or a password is given, but not an LDAP agent.',
456
+                ILogger::WARN);
457
+            $configurationOK = false;
458
+        }
459
+
460
+        $base = $this->configuration->ldapBase;
461
+        $baseUsers = $this->configuration->ldapBaseUsers;
462
+        $baseGroups = $this->configuration->ldapBaseGroups;
463
+
464
+        if(empty($base) && empty($baseUsers) && empty($baseGroups)) {
465
+            \OCP\Util::writeLog(
466
+                'user_ldap',
467
+                $errorStr.'Not a single Base DN given.',
468
+                ILogger::WARN
469
+            );
470
+            $configurationOK = false;
471
+        }
472
+
473
+        if(mb_strpos($this->configuration->ldapLoginFilter, '%uid', 0, 'UTF-8')
474
+            === false) {
475
+            \OCP\Util::writeLog(
476
+                'user_ldap',
477
+                $errorStr.'login filter does not contain %uid place holder.',
478
+                ILogger::WARN
479
+            );
480
+            $configurationOK = false;
481
+        }
482
+
483
+        return $configurationOK;
484
+    }
485
+
486
+    /**
487
+     * Validates the user specified configuration
488
+     * @return bool true if configuration seems OK, false otherwise
489
+     */
490
+    private function validateConfiguration() {
491
+
492
+        if($this->doNotValidate) {
493
+            //don't do a validation if it is a new configuration with pure
494
+            //default values. Will be allowed on changes via __set or
495
+            //setConfiguration
496
+            return false;
497
+        }
498
+
499
+        // first step: "soft" checks: settings that are not really
500
+        // necessary, but advisable. If left empty, give an info message
501
+        $this->doSoftValidation();
502
+
503
+        //second step: critical checks. If left empty or filled wrong, mark as
504
+        //not configured and give a warning.
505
+        return $this->doCriticalValidation();
506
+    }
507
+
508
+
509
+    /**
510
+     * Connects and Binds to LDAP
511
+     */
512
+    private function establishConnection() {
513
+        if(!$this->configuration->ldapConfigurationActive) {
514
+            return null;
515
+        }
516
+        static $phpLDAPinstalled = true;
517
+        if(!$phpLDAPinstalled) {
518
+            return false;
519
+        }
520
+        if(!$this->ignoreValidation && !$this->configured) {
521
+            \OCP\Util::writeLog(
522
+                'user_ldap',
523
+                'Configuration is invalid, cannot connect',
524
+                ILogger::WARN
525
+            );
526
+            return false;
527
+        }
528
+        if(!$this->ldapConnectionRes) {
529
+            if(!$this->ldap->areLDAPFunctionsAvailable()) {
530
+                $phpLDAPinstalled = false;
531
+                \OCP\Util::writeLog(
532
+                    'user_ldap',
533
+                    'function ldap_connect is not available. Make sure that the PHP ldap module is installed.',
534
+                    ILogger::ERROR
535
+                );
536
+
537
+                return false;
538
+            }
539
+            if($this->configuration->turnOffCertCheck) {
540
+                if(putenv('LDAPTLS_REQCERT=never')) {
541
+                    \OCP\Util::writeLog('user_ldap',
542
+                        'Turned off SSL certificate validation successfully.',
543
+                        ILogger::DEBUG);
544
+                } else {
545
+                    \OCP\Util::writeLog(
546
+                        'user_ldap',
547
+                        'Could not turn off SSL certificate validation.',
548
+                        ILogger::WARN
549
+                    );
550
+                }
551
+            }
552
+
553
+            $isOverrideMainServer = ($this->configuration->ldapOverrideMainServer
554
+                || $this->getFromCache('overrideMainServer'));
555
+            $isBackupHost = (trim($this->configuration->ldapBackupHost) !== "");
556
+            $bindStatus = false;
557
+            $error = -1;
558
+            try {
559
+                if (!$isOverrideMainServer) {
560
+                    $this->doConnect($this->configuration->ldapHost,
561
+                        $this->configuration->ldapPort);
562
+                    $bindStatus = $this->bind();
563
+                    $error = $this->ldap->isResource($this->ldapConnectionRes) ?
564
+                        $this->ldap->errno($this->ldapConnectionRes) : -1;
565
+                }
566
+                if($bindStatus === true) {
567
+                    return $bindStatus;
568
+                }
569
+            } catch (ServerNotAvailableException $e) {
570
+                if(!$isBackupHost) {
571
+                    throw $e;
572
+                }
573
+            }
574
+
575
+            //if LDAP server is not reachable, try the Backup (Replica!) Server
576
+            if($isBackupHost && ($error !== 0 || $isOverrideMainServer)) {
577
+                $this->doConnect($this->configuration->ldapBackupHost,
578
+                                    $this->configuration->ldapBackupPort);
579
+                $this->bindResult = [];
580
+                $bindStatus = $this->bind();
581
+                $error = $this->ldap->isResource($this->ldapConnectionRes) ?
582
+                    $this->ldap->errno($this->ldapConnectionRes) : -1;
583
+                if($bindStatus && $error === 0 && !$this->getFromCache('overrideMainServer')) {
584
+                    //when bind to backup server succeeded and failed to main server,
585
+                    //skip contacting him until next cache refresh
586
+                    $this->writeToCache('overrideMainServer', true);
587
+                }
588
+            }
589
+
590
+            return $bindStatus;
591
+        }
592
+        return null;
593
+    }
594
+
595
+    /**
596
+     * @param string $host
597
+     * @param string $port
598
+     * @return bool
599
+     * @throws \OC\ServerNotAvailableException
600
+     */
601
+    private function doConnect($host, $port) {
602
+        if ($host === '') {
603
+            return false;
604
+        }
605
+
606
+        $this->ldapConnectionRes = $this->ldap->connect($host, $port);
607
+
608
+        if(!$this->ldap->setOption($this->ldapConnectionRes, LDAP_OPT_PROTOCOL_VERSION, 3)) {
609
+            throw new ServerNotAvailableException('Could not set required LDAP Protocol version.');
610
+        }
611
+
612
+        if(!$this->ldap->setOption($this->ldapConnectionRes, LDAP_OPT_REFERRALS, 0)) {
613
+            throw new ServerNotAvailableException('Could not disable LDAP referrals.');
614
+        }
615
+
616
+        if($this->configuration->ldapTLS) {
617
+            if(!$this->ldap->startTls($this->ldapConnectionRes)) {
618
+                throw new ServerNotAvailableException('Start TLS failed, when connecting to LDAP host ' . $host . '.');
619
+            }
620
+        }
621
+
622
+        return true;
623
+    }
624
+
625
+    /**
626
+     * Binds to LDAP
627
+     */
628
+    public function bind() {
629
+        if(!$this->configuration->ldapConfigurationActive) {
630
+            return false;
631
+        }
632
+        $cr = $this->ldapConnectionRes;
633
+        if(!$this->ldap->isResource($cr)) {
634
+            $cr = $this->getConnectionResource();
635
+        }
636
+
637
+        if(
638
+            count($this->bindResult) !== 0
639
+            && $this->bindResult['dn'] === $this->configuration->ldapAgentName
640
+            && \OC::$server->getHasher()->verify(
641
+                $this->configPrefix . $this->configuration->ldapAgentPassword,
642
+                $this->bindResult['hash']
643
+            )
644
+        ) {
645
+            // don't attempt to bind again with the same data as before
646
+            // bind might have been invoked via getConnectionResource(),
647
+            // but we need results specifically for e.g. user login
648
+            return $this->bindResult['result'];
649
+        }
650
+
651
+        $ldapLogin = @$this->ldap->bind($cr,
652
+                                        $this->configuration->ldapAgentName,
653
+                                        $this->configuration->ldapAgentPassword);
654
+
655
+        $this->bindResult = [
656
+            'dn' => $this->configuration->ldapAgentName,
657
+            'hash' => \OC::$server->getHasher()->hash($this->configPrefix . $this->configuration->ldapAgentPassword),
658
+            'result' => $ldapLogin,
659
+        ];
660
+
661
+        if(!$ldapLogin) {
662
+            $errno = $this->ldap->errno($cr);
663
+
664
+            \OCP\Util::writeLog('user_ldap',
665
+                'Bind failed: ' . $errno . ': ' . $this->ldap->error($cr),
666
+                ILogger::WARN);
667
+
668
+            // Set to failure mode, if LDAP error code is not LDAP_SUCCESS or LDAP_INVALID_CREDENTIALS
669
+            if($errno !== 0x00 && $errno !== 0x31) {
670
+                $this->ldapConnectionRes = null;
671
+            }
672
+
673
+            return false;
674
+        }
675
+        return true;
676
+    }
677 677
 
678 678
 }
Please login to merge, or discard this patch.
apps/user_ldap/lib/Wizard.php 1 patch
Indentation   +1314 added lines, -1314 removed lines patch added patch discarded remove patch
@@ -42,1320 +42,1320 @@
 block discarded – undo
42 42
 use OCP\ILogger;
43 43
 
44 44
 class Wizard extends LDAPUtility {
45
-	/** @var \OCP\IL10N */
46
-	static protected $l;
47
-	protected $access;
48
-	protected $cr;
49
-	protected $configuration;
50
-	protected $result;
51
-	protected $resultCache = array();
52
-
53
-	const LRESULT_PROCESSED_OK = 2;
54
-	const LRESULT_PROCESSED_INVALID = 3;
55
-	const LRESULT_PROCESSED_SKIP = 4;
56
-
57
-	const LFILTER_LOGIN      = 2;
58
-	const LFILTER_USER_LIST  = 3;
59
-	const LFILTER_GROUP_LIST = 4;
60
-
61
-	const LFILTER_MODE_ASSISTED = 2;
62
-	const LFILTER_MODE_RAW = 1;
63
-
64
-	const LDAP_NW_TIMEOUT = 4;
65
-
66
-	/**
67
-	 * Constructor
68
-	 * @param Configuration $configuration an instance of Configuration
69
-	 * @param ILDAPWrapper $ldap an instance of ILDAPWrapper
70
-	 * @param Access $access
71
-	 */
72
-	public function __construct(Configuration $configuration, ILDAPWrapper $ldap, Access $access) {
73
-		parent::__construct($ldap);
74
-		$this->configuration = $configuration;
75
-		if(is_null(Wizard::$l)) {
76
-			Wizard::$l = \OC::$server->getL10N('user_ldap');
77
-		}
78
-		$this->access = $access;
79
-		$this->result = new WizardResult();
80
-	}
81
-
82
-	public function  __destruct() {
83
-		if($this->result->hasChanges()) {
84
-			$this->configuration->saveConfiguration();
85
-		}
86
-	}
87
-
88
-	/**
89
-	 * counts entries in the LDAP directory
90
-	 *
91
-	 * @param string $filter the LDAP search filter
92
-	 * @param string $type a string being either 'users' or 'groups';
93
-	 * @return int
94
-	 * @throws \Exception
95
-	 */
96
-	public function countEntries(string $filter, string $type): int {
97
-		$reqs = ['ldapHost', 'ldapPort', 'ldapBase'];
98
-		if($type === 'users') {
99
-			$reqs[] = 'ldapUserFilter';
100
-		}
101
-		if(!$this->checkRequirements($reqs)) {
102
-			throw new \Exception('Requirements not met', 400);
103
-		}
104
-
105
-		$attr = ['dn']; // default
106
-		$limit = 1001;
107
-		if($type === 'groups') {
108
-			$result =  $this->access->countGroups($filter, $attr, $limit);
109
-		} else if($type === 'users') {
110
-			$result = $this->access->countUsers($filter, $attr, $limit);
111
-		} else if ($type === 'objects') {
112
-			$result = $this->access->countObjects($limit);
113
-		} else {
114
-			throw new \Exception('Internal error: Invalid object type', 500);
115
-		}
116
-
117
-		return (int)$result;
118
-	}
119
-
120
-	/**
121
-	 * formats the return value of a count operation to the string to be
122
-	 * inserted.
123
-	 *
124
-	 * @param int $count
125
-	 * @return string
126
-	 */
127
-	private function formatCountResult(int $count): string {
128
-		if($count > 1000) {
129
-			return '> 1000';
130
-		}
131
-		return (string)$count;
132
-	}
133
-
134
-	public function countGroups() {
135
-		$filter = $this->configuration->ldapGroupFilter;
136
-
137
-		if(empty($filter)) {
138
-			$output = self::$l->n('%s group found', '%s groups found', 0, array(0));
139
-			$this->result->addChange('ldap_group_count', $output);
140
-			return $this->result;
141
-		}
142
-
143
-		try {
144
-			$groupsTotal = $this->countEntries($filter, 'groups');
145
-		} catch (\Exception $e) {
146
-			//400 can be ignored, 500 is forwarded
147
-			if($e->getCode() === 500) {
148
-				throw $e;
149
-			}
150
-			return false;
151
-		}
152
-		$output = self::$l->n(
153
-			'%s group found',
154
-			'%s groups found',
155
-			$groupsTotal,
156
-			[$this->formatCountResult($groupsTotal)]
157
-		);
158
-		$this->result->addChange('ldap_group_count', $output);
159
-		return $this->result;
160
-	}
161
-
162
-	/**
163
-	 * @return WizardResult
164
-	 * @throws \Exception
165
-	 */
166
-	public function countUsers() {
167
-		$filter = $this->access->getFilterForUserCount();
168
-
169
-		$usersTotal = $this->countEntries($filter, 'users');
170
-		$output = self::$l->n(
171
-			'%s user found',
172
-			'%s users found',
173
-			$usersTotal,
174
-			[$this->formatCountResult($usersTotal)]
175
-		);
176
-		$this->result->addChange('ldap_user_count', $output);
177
-		return $this->result;
178
-	}
179
-
180
-	/**
181
-	 * counts any objects in the currently set base dn
182
-	 *
183
-	 * @return WizardResult
184
-	 * @throws \Exception
185
-	 */
186
-	public function countInBaseDN() {
187
-		// we don't need to provide a filter in this case
188
-		$total = $this->countEntries('', 'objects');
189
-		if($total === false) {
190
-			throw new \Exception('invalid results received');
191
-		}
192
-		$this->result->addChange('ldap_test_base', $total);
193
-		return $this->result;
194
-	}
195
-
196
-	/**
197
-	 * counts users with a specified attribute
198
-	 * @param string $attr
199
-	 * @param bool $existsCheck
200
-	 * @return int|bool
201
-	 */
202
-	public function countUsersWithAttribute($attr, $existsCheck = false) {
203
-		if(!$this->checkRequirements(array('ldapHost',
204
-										   'ldapPort',
205
-										   'ldapBase',
206
-										   'ldapUserFilter',
207
-										   ))) {
208
-			return  false;
209
-		}
210
-
211
-		$filter = $this->access->combineFilterWithAnd(array(
212
-			$this->configuration->ldapUserFilter,
213
-			$attr . '=*'
214
-		));
215
-
216
-		$limit = ($existsCheck === false) ? null : 1;
217
-
218
-		return $this->access->countUsers($filter, array('dn'), $limit);
219
-	}
220
-
221
-	/**
222
-	 * detects the display name attribute. If a setting is already present that
223
-	 * returns at least one hit, the detection will be canceled.
224
-	 * @return WizardResult|bool
225
-	 * @throws \Exception
226
-	 */
227
-	public function detectUserDisplayNameAttribute() {
228
-		if(!$this->checkRequirements(array('ldapHost',
229
-										'ldapPort',
230
-										'ldapBase',
231
-										'ldapUserFilter',
232
-										))) {
233
-			return  false;
234
-		}
235
-
236
-		$attr = $this->configuration->ldapUserDisplayName;
237
-		if ($attr !== '' && $attr !== 'displayName') {
238
-			// most likely not the default value with upper case N,
239
-			// verify it still produces a result
240
-			$count = (int)$this->countUsersWithAttribute($attr, true);
241
-			if($count > 0) {
242
-				//no change, but we sent it back to make sure the user interface
243
-				//is still correct, even if the ajax call was cancelled meanwhile
244
-				$this->result->addChange('ldap_display_name', $attr);
245
-				return $this->result;
246
-			}
247
-		}
248
-
249
-		// first attribute that has at least one result wins
250
-		$displayNameAttrs = array('displayname', 'cn');
251
-		foreach ($displayNameAttrs as $attr) {
252
-			$count = (int)$this->countUsersWithAttribute($attr, true);
253
-
254
-			if($count > 0) {
255
-				$this->applyFind('ldap_display_name', $attr);
256
-				return $this->result;
257
-			}
258
-		}
259
-
260
-		throw new \Exception(self::$l->t('Could not detect user display name attribute. Please specify it yourself in advanced LDAP settings.'));
261
-	}
262
-
263
-	/**
264
-	 * detects the most often used email attribute for users applying to the
265
-	 * user list filter. If a setting is already present that returns at least
266
-	 * one hit, the detection will be canceled.
267
-	 * @return WizardResult|bool
268
-	 */
269
-	public function detectEmailAttribute() {
270
-		if(!$this->checkRequirements(array('ldapHost',
271
-										   'ldapPort',
272
-										   'ldapBase',
273
-										   'ldapUserFilter',
274
-										   ))) {
275
-			return  false;
276
-		}
277
-
278
-		$attr = $this->configuration->ldapEmailAttribute;
279
-		if ($attr !== '') {
280
-			$count = (int)$this->countUsersWithAttribute($attr, true);
281
-			if($count > 0) {
282
-				return false;
283
-			}
284
-			$writeLog = true;
285
-		} else {
286
-			$writeLog = false;
287
-		}
288
-
289
-		$emailAttributes = array('mail', 'mailPrimaryAddress');
290
-		$winner = '';
291
-		$maxUsers = 0;
292
-		foreach($emailAttributes as $attr) {
293
-			$count = $this->countUsersWithAttribute($attr);
294
-			if($count > $maxUsers) {
295
-				$maxUsers = $count;
296
-				$winner = $attr;
297
-			}
298
-		}
299
-
300
-		if($winner !== '') {
301
-			$this->applyFind('ldap_email_attr', $winner);
302
-			if($writeLog) {
303
-				\OCP\Util::writeLog('user_ldap', 'The mail attribute has ' .
304
-					'automatically been reset, because the original value ' .
305
-					'did not return any results.', ILogger::INFO);
306
-			}
307
-		}
308
-
309
-		return $this->result;
310
-	}
311
-
312
-	/**
313
-	 * @return WizardResult
314
-	 * @throws \Exception
315
-	 */
316
-	public function determineAttributes() {
317
-		if(!$this->checkRequirements(array('ldapHost',
318
-										   'ldapPort',
319
-										   'ldapBase',
320
-										   'ldapUserFilter',
321
-										   ))) {
322
-			return  false;
323
-		}
324
-
325
-		$attributes = $this->getUserAttributes();
326
-
327
-		natcasesort($attributes);
328
-		$attributes = array_values($attributes);
329
-
330
-		$this->result->addOptions('ldap_loginfilter_attributes', $attributes);
331
-
332
-		$selected = $this->configuration->ldapLoginFilterAttributes;
333
-		if(is_array($selected) && !empty($selected)) {
334
-			$this->result->addChange('ldap_loginfilter_attributes', $selected);
335
-		}
336
-
337
-		return $this->result;
338
-	}
339
-
340
-	/**
341
-	 * detects the available LDAP attributes
342
-	 * @return array|false The instance's WizardResult instance
343
-	 * @throws \Exception
344
-	 */
345
-	private function getUserAttributes() {
346
-		if(!$this->checkRequirements(array('ldapHost',
347
-										   'ldapPort',
348
-										   'ldapBase',
349
-										   'ldapUserFilter',
350
-										   ))) {
351
-			return  false;
352
-		}
353
-		$cr = $this->getConnection();
354
-		if(!$cr) {
355
-			throw new \Exception('Could not connect to LDAP');
356
-		}
357
-
358
-		$base = $this->configuration->ldapBase[0];
359
-		$filter = $this->configuration->ldapUserFilter;
360
-		$rr = $this->ldap->search($cr, $base, $filter, array(), 1, 1);
361
-		if(!$this->ldap->isResource($rr)) {
362
-			return false;
363
-		}
364
-		$er = $this->ldap->firstEntry($cr, $rr);
365
-		$attributes = $this->ldap->getAttributes($cr, $er);
366
-		$pureAttributes = array();
367
-		for($i = 0; $i < $attributes['count']; $i++) {
368
-			$pureAttributes[] = $attributes[$i];
369
-		}
370
-
371
-		return $pureAttributes;
372
-	}
373
-
374
-	/**
375
-	 * detects the available LDAP groups
376
-	 * @return WizardResult|false the instance's WizardResult instance
377
-	 */
378
-	public function determineGroupsForGroups() {
379
-		return $this->determineGroups('ldap_groupfilter_groups',
380
-									  'ldapGroupFilterGroups',
381
-									  false);
382
-	}
383
-
384
-	/**
385
-	 * detects the available LDAP groups
386
-	 * @return WizardResult|false the instance's WizardResult instance
387
-	 */
388
-	public function determineGroupsForUsers() {
389
-		return $this->determineGroups('ldap_userfilter_groups',
390
-									  'ldapUserFilterGroups');
391
-	}
392
-
393
-	/**
394
-	 * detects the available LDAP groups
395
-	 * @param string $dbKey
396
-	 * @param string $confKey
397
-	 * @param bool $testMemberOf
398
-	 * @return WizardResult|false the instance's WizardResult instance
399
-	 * @throws \Exception
400
-	 */
401
-	private function determineGroups($dbKey, $confKey, $testMemberOf = true) {
402
-		if(!$this->checkRequirements(array('ldapHost',
403
-										   'ldapPort',
404
-										   'ldapBase',
405
-										   ))) {
406
-			return  false;
407
-		}
408
-		$cr = $this->getConnection();
409
-		if(!$cr) {
410
-			throw new \Exception('Could not connect to LDAP');
411
-		}
412
-
413
-		$this->fetchGroups($dbKey, $confKey);
414
-
415
-		if($testMemberOf) {
416
-			$this->configuration->hasMemberOfFilterSupport = $this->testMemberOf();
417
-			$this->result->markChange();
418
-			if(!$this->configuration->hasMemberOfFilterSupport) {
419
-				throw new \Exception('memberOf is not supported by the server');
420
-			}
421
-		}
422
-
423
-		return $this->result;
424
-	}
425
-
426
-	/**
427
-	 * fetches all groups from LDAP and adds them to the result object
428
-	 *
429
-	 * @param string $dbKey
430
-	 * @param string $confKey
431
-	 * @return array $groupEntries
432
-	 * @throws \Exception
433
-	 */
434
-	public function fetchGroups($dbKey, $confKey) {
435
-		$obclasses = array('posixGroup', 'group', 'zimbraDistributionList', 'groupOfNames', 'groupOfUniqueNames');
436
-
437
-		$filterParts = array();
438
-		foreach($obclasses as $obclass) {
439
-			$filterParts[] = 'objectclass='.$obclass;
440
-		}
441
-		//we filter for everything
442
-		//- that looks like a group and
443
-		//- has the group display name set
444
-		$filter = $this->access->combineFilterWithOr($filterParts);
445
-		$filter = $this->access->combineFilterWithAnd(array($filter, 'cn=*'));
446
-
447
-		$groupNames = array();
448
-		$groupEntries = array();
449
-		$limit = 400;
450
-		$offset = 0;
451
-		do {
452
-			// we need to request dn additionally here, otherwise memberOf
453
-			// detection will fail later
454
-			$result = $this->access->searchGroups($filter, array('cn', 'dn'), $limit, $offset);
455
-			foreach($result as $item) {
456
-				if(!isset($item['cn']) && !is_array($item['cn']) && !isset($item['cn'][0])) {
457
-					// just in case - no issue known
458
-					continue;
459
-				}
460
-				$groupNames[] = $item['cn'][0];
461
-				$groupEntries[] = $item;
462
-			}
463
-			$offset += $limit;
464
-		} while ($this->access->hasMoreResults());
465
-
466
-		if(count($groupNames) > 0) {
467
-			natsort($groupNames);
468
-			$this->result->addOptions($dbKey, array_values($groupNames));
469
-		} else {
470
-			throw new \Exception(self::$l->t('Could not find the desired feature'));
471
-		}
472
-
473
-		$setFeatures = $this->configuration->$confKey;
474
-		if(is_array($setFeatures) && !empty($setFeatures)) {
475
-			//something is already configured? pre-select it.
476
-			$this->result->addChange($dbKey, $setFeatures);
477
-		}
478
-		return $groupEntries;
479
-	}
480
-
481
-	public function determineGroupMemberAssoc() {
482
-		if(!$this->checkRequirements(array('ldapHost',
483
-										   'ldapPort',
484
-										   'ldapGroupFilter',
485
-										   ))) {
486
-			return  false;
487
-		}
488
-		$attribute = $this->detectGroupMemberAssoc();
489
-		if($attribute === false) {
490
-			return false;
491
-		}
492
-		$this->configuration->setConfiguration(array('ldapGroupMemberAssocAttr' => $attribute));
493
-		$this->result->addChange('ldap_group_member_assoc_attribute', $attribute);
494
-
495
-		return $this->result;
496
-	}
497
-
498
-	/**
499
-	 * Detects the available object classes
500
-	 * @return WizardResult|false the instance's WizardResult instance
501
-	 * @throws \Exception
502
-	 */
503
-	public function determineGroupObjectClasses() {
504
-		if(!$this->checkRequirements(array('ldapHost',
505
-										   'ldapPort',
506
-										   'ldapBase',
507
-										   ))) {
508
-			return  false;
509
-		}
510
-		$cr = $this->getConnection();
511
-		if(!$cr) {
512
-			throw new \Exception('Could not connect to LDAP');
513
-		}
514
-
515
-		$obclasses = array('groupOfNames', 'groupOfUniqueNames', 'group', 'posixGroup', '*');
516
-		$this->determineFeature($obclasses,
517
-								'objectclass',
518
-								'ldap_groupfilter_objectclass',
519
-								'ldapGroupFilterObjectclass',
520
-								false);
521
-
522
-		return $this->result;
523
-	}
524
-
525
-	/**
526
-	 * detects the available object classes
527
-	 * @return WizardResult
528
-	 * @throws \Exception
529
-	 */
530
-	public function determineUserObjectClasses() {
531
-		if(!$this->checkRequirements(array('ldapHost',
532
-										   'ldapPort',
533
-										   'ldapBase',
534
-										   ))) {
535
-			return  false;
536
-		}
537
-		$cr = $this->getConnection();
538
-		if(!$cr) {
539
-			throw new \Exception('Could not connect to LDAP');
540
-		}
541
-
542
-		$obclasses = array('inetOrgPerson', 'person', 'organizationalPerson',
543
-						   'user', 'posixAccount', '*');
544
-		$filter = $this->configuration->ldapUserFilter;
545
-		//if filter is empty, it is probably the first time the wizard is called
546
-		//then, apply suggestions.
547
-		$this->determineFeature($obclasses,
548
-								'objectclass',
549
-								'ldap_userfilter_objectclass',
550
-								'ldapUserFilterObjectclass',
551
-								empty($filter));
552
-
553
-		return $this->result;
554
-	}
555
-
556
-	/**
557
-	 * @return WizardResult|false
558
-	 * @throws \Exception
559
-	 */
560
-	public function getGroupFilter() {
561
-		if(!$this->checkRequirements(array('ldapHost',
562
-										   'ldapPort',
563
-										   'ldapBase',
564
-										   ))) {
565
-			return false;
566
-		}
567
-		//make sure the use display name is set
568
-		$displayName = $this->configuration->ldapGroupDisplayName;
569
-		if ($displayName === '') {
570
-			$d = $this->configuration->getDefaults();
571
-			$this->applyFind('ldap_group_display_name',
572
-							 $d['ldap_group_display_name']);
573
-		}
574
-		$filter = $this->composeLdapFilter(self::LFILTER_GROUP_LIST);
575
-
576
-		$this->applyFind('ldap_group_filter', $filter);
577
-		return $this->result;
578
-	}
579
-
580
-	/**
581
-	 * @return WizardResult|false
582
-	 * @throws \Exception
583
-	 */
584
-	public function getUserListFilter() {
585
-		if(!$this->checkRequirements(array('ldapHost',
586
-										   'ldapPort',
587
-										   'ldapBase',
588
-										   ))) {
589
-			return false;
590
-		}
591
-		//make sure the use display name is set
592
-		$displayName = $this->configuration->ldapUserDisplayName;
593
-		if ($displayName === '') {
594
-			$d = $this->configuration->getDefaults();
595
-			$this->applyFind('ldap_display_name', $d['ldap_display_name']);
596
-		}
597
-		$filter = $this->composeLdapFilter(self::LFILTER_USER_LIST);
598
-		if(!$filter) {
599
-			throw new \Exception('Cannot create filter');
600
-		}
601
-
602
-		$this->applyFind('ldap_userlist_filter', $filter);
603
-		return $this->result;
604
-	}
605
-
606
-	/**
607
-	 * @return bool|WizardResult
608
-	 * @throws \Exception
609
-	 */
610
-	public function getUserLoginFilter() {
611
-		if(!$this->checkRequirements(array('ldapHost',
612
-										   'ldapPort',
613
-										   'ldapBase',
614
-										   'ldapUserFilter',
615
-										   ))) {
616
-			return false;
617
-		}
618
-
619
-		$filter = $this->composeLdapFilter(self::LFILTER_LOGIN);
620
-		if(!$filter) {
621
-			throw new \Exception('Cannot create filter');
622
-		}
623
-
624
-		$this->applyFind('ldap_login_filter', $filter);
625
-		return $this->result;
626
-	}
627
-
628
-	/**
629
-	 * @return bool|WizardResult
630
-	 * @param string $loginName
631
-	 * @throws \Exception
632
-	 */
633
-	public function testLoginName($loginName) {
634
-		if(!$this->checkRequirements(array('ldapHost',
635
-			'ldapPort',
636
-			'ldapBase',
637
-			'ldapLoginFilter',
638
-		))) {
639
-			return false;
640
-		}
641
-
642
-		$cr = $this->access->connection->getConnectionResource();
643
-		if(!$this->ldap->isResource($cr)) {
644
-			throw new \Exception('connection error');
645
-		}
646
-
647
-		if(mb_strpos($this->access->connection->ldapLoginFilter, '%uid', 0, 'UTF-8')
648
-			=== false) {
649
-			throw new \Exception('missing placeholder');
650
-		}
651
-
652
-		$users = $this->access->countUsersByLoginName($loginName);
653
-		if($this->ldap->errno($cr) !== 0) {
654
-			throw new \Exception($this->ldap->error($cr));
655
-		}
656
-		$filter = str_replace('%uid', $loginName, $this->access->connection->ldapLoginFilter);
657
-		$this->result->addChange('ldap_test_loginname', $users);
658
-		$this->result->addChange('ldap_test_effective_filter', $filter);
659
-		return $this->result;
660
-	}
661
-
662
-	/**
663
-	 * Tries to determine the port, requires given Host, User DN and Password
664
-	 * @return WizardResult|false WizardResult on success, false otherwise
665
-	 * @throws \Exception
666
-	 */
667
-	public function guessPortAndTLS() {
668
-		if(!$this->checkRequirements(array('ldapHost',
669
-										   ))) {
670
-			return false;
671
-		}
672
-		$this->checkHost();
673
-		$portSettings = $this->getPortSettingsToTry();
674
-
675
-		if(!is_array($portSettings)) {
676
-			throw new \Exception(print_r($portSettings, true));
677
-		}
678
-
679
-		//proceed from the best configuration and return on first success
680
-		foreach($portSettings as $setting) {
681
-			$p = $setting['port'];
682
-			$t = $setting['tls'];
683
-			\OCP\Util::writeLog('user_ldap', 'Wiz: trying port '. $p . ', TLS '. $t, ILogger::DEBUG);
684
-			//connectAndBind may throw Exception, it needs to be catched by the
685
-			//callee of this method
686
-
687
-			try {
688
-				$settingsFound = $this->connectAndBind($p, $t);
689
-			} catch (\Exception $e) {
690
-				// any reply other than -1 (= cannot connect) is already okay,
691
-				// because then we found the server
692
-				// unavailable startTLS returns -11
693
-				if($e->getCode() > 0) {
694
-					$settingsFound = true;
695
-				} else {
696
-					throw $e;
697
-				}
698
-			}
699
-
700
-			if ($settingsFound === true) {
701
-				$config = array(
702
-					'ldapPort' => $p,
703
-					'ldapTLS' => (int)$t
704
-				);
705
-				$this->configuration->setConfiguration($config);
706
-				\OCP\Util::writeLog('user_ldap', 'Wiz: detected Port ' . $p, ILogger::DEBUG);
707
-				$this->result->addChange('ldap_port', $p);
708
-				return $this->result;
709
-			}
710
-		}
711
-
712
-		//custom port, undetected (we do not brute force)
713
-		return false;
714
-	}
715
-
716
-	/**
717
-	 * tries to determine a base dn from User DN or LDAP Host
718
-	 * @return WizardResult|false WizardResult on success, false otherwise
719
-	 */
720
-	public function guessBaseDN() {
721
-		if(!$this->checkRequirements(array('ldapHost',
722
-										   'ldapPort',
723
-										   ))) {
724
-			return false;
725
-		}
726
-
727
-		//check whether a DN is given in the agent name (99.9% of all cases)
728
-		$base = null;
729
-		$i = stripos($this->configuration->ldapAgentName, 'dc=');
730
-		if($i !== false) {
731
-			$base = substr($this->configuration->ldapAgentName, $i);
732
-			if($this->testBaseDN($base)) {
733
-				$this->applyFind('ldap_base', $base);
734
-				return $this->result;
735
-			}
736
-		}
737
-
738
-		//this did not help :(
739
-		//Let's see whether we can parse the Host URL and convert the domain to
740
-		//a base DN
741
-		$helper = new Helper(\OC::$server->getConfig());
742
-		$domain = $helper->getDomainFromURL($this->configuration->ldapHost);
743
-		if(!$domain) {
744
-			return false;
745
-		}
746
-
747
-		$dparts = explode('.', $domain);
748
-		while(count($dparts) > 0) {
749
-			$base2 = 'dc=' . implode(',dc=', $dparts);
750
-			if ($base !== $base2 && $this->testBaseDN($base2)) {
751
-				$this->applyFind('ldap_base', $base2);
752
-				return $this->result;
753
-			}
754
-			array_shift($dparts);
755
-		}
756
-
757
-		return false;
758
-	}
759
-
760
-	/**
761
-	 * sets the found value for the configuration key in the WizardResult
762
-	 * as well as in the Configuration instance
763
-	 * @param string $key the configuration key
764
-	 * @param string $value the (detected) value
765
-	 *
766
-	 */
767
-	private function applyFind($key, $value) {
768
-		$this->result->addChange($key, $value);
769
-		$this->configuration->setConfiguration(array($key => $value));
770
-	}
771
-
772
-	/**
773
-	 * Checks, whether a port was entered in the Host configuration
774
-	 * field. In this case the port will be stripped off, but also stored as
775
-	 * setting.
776
-	 */
777
-	private function checkHost() {
778
-		$host = $this->configuration->ldapHost;
779
-		$hostInfo = parse_url($host);
780
-
781
-		//removes Port from Host
782
-		if(is_array($hostInfo) && isset($hostInfo['port'])) {
783
-			$port = $hostInfo['port'];
784
-			$host = str_replace(':'.$port, '', $host);
785
-			$this->applyFind('ldap_host', $host);
786
-			$this->applyFind('ldap_port', $port);
787
-		}
788
-	}
789
-
790
-	/**
791
-	 * tries to detect the group member association attribute which is
792
-	 * one of 'uniqueMember', 'memberUid', 'member', 'gidNumber'
793
-	 * @return string|false, string with the attribute name, false on error
794
-	 * @throws \Exception
795
-	 */
796
-	private function detectGroupMemberAssoc() {
797
-		$possibleAttrs = array('uniqueMember', 'memberUid', 'member', 'gidNumber');
798
-		$filter = $this->configuration->ldapGroupFilter;
799
-		if(empty($filter)) {
800
-			return false;
801
-		}
802
-		$cr = $this->getConnection();
803
-		if(!$cr) {
804
-			throw new \Exception('Could not connect to LDAP');
805
-		}
806
-		$base = $this->configuration->ldapBase[0];
807
-		$rr = $this->ldap->search($cr, $base, $filter, $possibleAttrs, 0, 1000);
808
-		if(!$this->ldap->isResource($rr)) {
809
-			return false;
810
-		}
811
-		$er = $this->ldap->firstEntry($cr, $rr);
812
-		while(is_resource($er)) {
813
-			$this->ldap->getDN($cr, $er);
814
-			$attrs = $this->ldap->getAttributes($cr, $er);
815
-			$result = array();
816
-			$possibleAttrsCount = count($possibleAttrs);
817
-			for($i = 0; $i < $possibleAttrsCount; $i++) {
818
-				if(isset($attrs[$possibleAttrs[$i]])) {
819
-					$result[$possibleAttrs[$i]] = $attrs[$possibleAttrs[$i]]['count'];
820
-				}
821
-			}
822
-			if(!empty($result)) {
823
-				natsort($result);
824
-				return key($result);
825
-			}
826
-
827
-			$er = $this->ldap->nextEntry($cr, $er);
828
-		}
829
-
830
-		return false;
831
-	}
832
-
833
-	/**
834
-	 * Checks whether for a given BaseDN results will be returned
835
-	 * @param string $base the BaseDN to test
836
-	 * @return bool true on success, false otherwise
837
-	 * @throws \Exception
838
-	 */
839
-	private function testBaseDN($base) {
840
-		$cr = $this->getConnection();
841
-		if(!$cr) {
842
-			throw new \Exception('Could not connect to LDAP');
843
-		}
844
-
845
-		//base is there, let's validate it. If we search for anything, we should
846
-		//get a result set > 0 on a proper base
847
-		$rr = $this->ldap->search($cr, $base, 'objectClass=*', array('dn'), 0, 1);
848
-		if(!$this->ldap->isResource($rr)) {
849
-			$errorNo  = $this->ldap->errno($cr);
850
-			$errorMsg = $this->ldap->error($cr);
851
-			\OCP\Util::writeLog('user_ldap', 'Wiz: Could not search base '.$base.
852
-							' Error '.$errorNo.': '.$errorMsg, ILogger::INFO);
853
-			return false;
854
-		}
855
-		$entries = $this->ldap->countEntries($cr, $rr);
856
-		return ($entries !== false) && ($entries > 0);
857
-	}
858
-
859
-	/**
860
-	 * Checks whether the server supports memberOf in LDAP Filter.
861
-	 * Note: at least in OpenLDAP, availability of memberOf is dependent on
862
-	 * a configured objectClass. I.e. not necessarily for all available groups
863
-	 * memberOf does work.
864
-	 *
865
-	 * @return bool true if it does, false otherwise
866
-	 * @throws \Exception
867
-	 */
868
-	private function testMemberOf() {
869
-		$cr = $this->getConnection();
870
-		if(!$cr) {
871
-			throw new \Exception('Could not connect to LDAP');
872
-		}
873
-		$result = $this->access->countUsers('memberOf=*', array('memberOf'), 1);
874
-		if(is_int($result) &&  $result > 0) {
875
-			return true;
876
-		}
877
-		return false;
878
-	}
879
-
880
-	/**
881
-	 * creates an LDAP Filter from given configuration
882
-	 * @param integer $filterType int, for which use case the filter shall be created
883
-	 * can be any of self::LFILTER_USER_LIST, self::LFILTER_LOGIN or
884
-	 * self::LFILTER_GROUP_LIST
885
-	 * @return string|false string with the filter on success, false otherwise
886
-	 * @throws \Exception
887
-	 */
888
-	private function composeLdapFilter($filterType) {
889
-		$filter = '';
890
-		$parts = 0;
891
-		switch ($filterType) {
892
-			case self::LFILTER_USER_LIST:
893
-				$objcs = $this->configuration->ldapUserFilterObjectclass;
894
-				//glue objectclasses
895
-				if(is_array($objcs) && count($objcs) > 0) {
896
-					$filter .= '(|';
897
-					foreach($objcs as $objc) {
898
-						$filter .= '(objectclass=' . $objc . ')';
899
-					}
900
-					$filter .= ')';
901
-					$parts++;
902
-				}
903
-				//glue group memberships
904
-				if($this->configuration->hasMemberOfFilterSupport) {
905
-					$cns = $this->configuration->ldapUserFilterGroups;
906
-					if(is_array($cns) && count($cns) > 0) {
907
-						$filter .= '(|';
908
-						$cr = $this->getConnection();
909
-						if(!$cr) {
910
-							throw new \Exception('Could not connect to LDAP');
911
-						}
912
-						$base = $this->configuration->ldapBase[0];
913
-						foreach($cns as $cn) {
914
-							$rr = $this->ldap->search($cr, $base, 'cn=' . $cn, array('dn', 'primaryGroupToken'));
915
-							if(!$this->ldap->isResource($rr)) {
916
-								continue;
917
-							}
918
-							$er = $this->ldap->firstEntry($cr, $rr);
919
-							$attrs = $this->ldap->getAttributes($cr, $er);
920
-							$dn = $this->ldap->getDN($cr, $er);
921
-							if ($dn === false || $dn === '') {
922
-								continue;
923
-							}
924
-							$filterPart = '(memberof=' . $dn . ')';
925
-							if(isset($attrs['primaryGroupToken'])) {
926
-								$pgt = $attrs['primaryGroupToken'][0];
927
-								$primaryFilterPart = '(primaryGroupID=' . $pgt .')';
928
-								$filterPart = '(|' . $filterPart . $primaryFilterPart . ')';
929
-							}
930
-							$filter .= $filterPart;
931
-						}
932
-						$filter .= ')';
933
-					}
934
-					$parts++;
935
-				}
936
-				//wrap parts in AND condition
937
-				if($parts > 1) {
938
-					$filter = '(&' . $filter . ')';
939
-				}
940
-				if ($filter === '') {
941
-					$filter = '(objectclass=*)';
942
-				}
943
-				break;
944
-
945
-			case self::LFILTER_GROUP_LIST:
946
-				$objcs = $this->configuration->ldapGroupFilterObjectclass;
947
-				//glue objectclasses
948
-				if(is_array($objcs) && count($objcs) > 0) {
949
-					$filter .= '(|';
950
-					foreach($objcs as $objc) {
951
-						$filter .= '(objectclass=' . $objc . ')';
952
-					}
953
-					$filter .= ')';
954
-					$parts++;
955
-				}
956
-				//glue group memberships
957
-				$cns = $this->configuration->ldapGroupFilterGroups;
958
-				if(is_array($cns) && count($cns) > 0) {
959
-					$filter .= '(|';
960
-					foreach($cns as $cn) {
961
-						$filter .= '(cn=' . $cn . ')';
962
-					}
963
-					$filter .= ')';
964
-				}
965
-				$parts++;
966
-				//wrap parts in AND condition
967
-				if($parts > 1) {
968
-					$filter = '(&' . $filter . ')';
969
-				}
970
-				break;
971
-
972
-			case self::LFILTER_LOGIN:
973
-				$ulf = $this->configuration->ldapUserFilter;
974
-				$loginpart = '=%uid';
975
-				$filterUsername = '';
976
-				$userAttributes = $this->getUserAttributes();
977
-				$userAttributes = array_change_key_case(array_flip($userAttributes));
978
-				$parts = 0;
979
-
980
-				if($this->configuration->ldapLoginFilterUsername === '1') {
981
-					$attr = '';
982
-					if(isset($userAttributes['uid'])) {
983
-						$attr = 'uid';
984
-					} else if(isset($userAttributes['samaccountname'])) {
985
-						$attr = 'samaccountname';
986
-					} else if(isset($userAttributes['cn'])) {
987
-						//fallback
988
-						$attr = 'cn';
989
-					}
990
-					if ($attr !== '') {
991
-						$filterUsername = '(' . $attr . $loginpart . ')';
992
-						$parts++;
993
-					}
994
-				}
995
-
996
-				$filterEmail = '';
997
-				if($this->configuration->ldapLoginFilterEmail === '1') {
998
-					$filterEmail = '(|(mailPrimaryAddress=%uid)(mail=%uid))';
999
-					$parts++;
1000
-				}
1001
-
1002
-				$filterAttributes = '';
1003
-				$attrsToFilter = $this->configuration->ldapLoginFilterAttributes;
1004
-				if(is_array($attrsToFilter) && count($attrsToFilter) > 0) {
1005
-					$filterAttributes = '(|';
1006
-					foreach($attrsToFilter as $attribute) {
1007
-						$filterAttributes .= '(' . $attribute . $loginpart . ')';
1008
-					}
1009
-					$filterAttributes .= ')';
1010
-					$parts++;
1011
-				}
1012
-
1013
-				$filterLogin = '';
1014
-				if($parts > 1) {
1015
-					$filterLogin = '(|';
1016
-				}
1017
-				$filterLogin .= $filterUsername;
1018
-				$filterLogin .= $filterEmail;
1019
-				$filterLogin .= $filterAttributes;
1020
-				if($parts > 1) {
1021
-					$filterLogin .= ')';
1022
-				}
1023
-
1024
-				$filter = '(&'.$ulf.$filterLogin.')';
1025
-				break;
1026
-		}
1027
-
1028
-		\OCP\Util::writeLog('user_ldap', 'Wiz: Final filter '.$filter, ILogger::DEBUG);
1029
-
1030
-		return $filter;
1031
-	}
1032
-
1033
-	/**
1034
-	 * Connects and Binds to an LDAP Server
1035
-	 *
1036
-	 * @param int $port the port to connect with
1037
-	 * @param bool $tls whether startTLS is to be used
1038
-	 * @return bool
1039
-	 * @throws \Exception
1040
-	 */
1041
-	private function connectAndBind($port, $tls) {
1042
-		//connect, does not really trigger any server communication
1043
-		$host = $this->configuration->ldapHost;
1044
-		$hostInfo = parse_url($host);
1045
-		if(!$hostInfo) {
1046
-			throw new \Exception(self::$l->t('Invalid Host'));
1047
-		}
1048
-		\OCP\Util::writeLog('user_ldap', 'Wiz: Attempting to connect ', ILogger::DEBUG);
1049
-		$cr = $this->ldap->connect($host, $port);
1050
-		if(!is_resource($cr)) {
1051
-			throw new \Exception(self::$l->t('Invalid Host'));
1052
-		}
1053
-
1054
-		//set LDAP options
1055
-		$this->ldap->setOption($cr, LDAP_OPT_PROTOCOL_VERSION, 3);
1056
-		$this->ldap->setOption($cr, LDAP_OPT_REFERRALS, 0);
1057
-		$this->ldap->setOption($cr, LDAP_OPT_NETWORK_TIMEOUT, self::LDAP_NW_TIMEOUT);
1058
-
1059
-		try {
1060
-			if($tls) {
1061
-				$isTlsWorking = @$this->ldap->startTls($cr);
1062
-				if(!$isTlsWorking) {
1063
-					return false;
1064
-				}
1065
-			}
1066
-
1067
-			\OCP\Util::writeLog('user_ldap', 'Wiz: Attemping to Bind ', ILogger::DEBUG);
1068
-			//interesting part: do the bind!
1069
-			$login = $this->ldap->bind($cr,
1070
-				$this->configuration->ldapAgentName,
1071
-				$this->configuration->ldapAgentPassword
1072
-			);
1073
-			$errNo = $this->ldap->errno($cr);
1074
-			$error = ldap_error($cr);
1075
-			$this->ldap->unbind($cr);
1076
-		} catch(ServerNotAvailableException $e) {
1077
-			return false;
1078
-		}
1079
-
1080
-		if($login === true) {
1081
-			$this->ldap->unbind($cr);
1082
-			\OCP\Util::writeLog('user_ldap', 'Wiz: Bind successful to Port '. $port . ' TLS ' . (int)$tls, ILogger::DEBUG);
1083
-			return true;
1084
-		}
1085
-
1086
-		if($errNo === -1) {
1087
-			//host, port or TLS wrong
1088
-			return false;
1089
-		}
1090
-		throw new \Exception($error, $errNo);
1091
-	}
1092
-
1093
-	/**
1094
-	 * checks whether a valid combination of agent and password has been
1095
-	 * provided (either two values or nothing for anonymous connect)
1096
-	 * @return bool, true if everything is fine, false otherwise
1097
-	 */
1098
-	private function checkAgentRequirements() {
1099
-		$agent = $this->configuration->ldapAgentName;
1100
-		$pwd = $this->configuration->ldapAgentPassword;
1101
-
1102
-		return
1103
-			($agent !== '' && $pwd !== '')
1104
-			||  ($agent === '' && $pwd === '')
1105
-		;
1106
-	}
1107
-
1108
-	/**
1109
-	 * @param array $reqs
1110
-	 * @return bool
1111
-	 */
1112
-	private function checkRequirements($reqs) {
1113
-		$this->checkAgentRequirements();
1114
-		foreach($reqs as $option) {
1115
-			$value = $this->configuration->$option;
1116
-			if(empty($value)) {
1117
-				return false;
1118
-			}
1119
-		}
1120
-		return true;
1121
-	}
1122
-
1123
-	/**
1124
-	 * does a cumulativeSearch on LDAP to get different values of a
1125
-	 * specified attribute
1126
-	 * @param string[] $filters array, the filters that shall be used in the search
1127
-	 * @param string $attr the attribute of which a list of values shall be returned
1128
-	 * @param int $dnReadLimit the amount of how many DNs should be analyzed.
1129
-	 * The lower, the faster
1130
-	 * @param string $maxF string. if not null, this variable will have the filter that
1131
-	 * yields most result entries
1132
-	 * @return array|false an array with the values on success, false otherwise
1133
-	 */
1134
-	public function cumulativeSearchOnAttribute($filters, $attr, $dnReadLimit = 3, &$maxF = null) {
1135
-		$dnRead = array();
1136
-		$foundItems = array();
1137
-		$maxEntries = 0;
1138
-		if(!is_array($this->configuration->ldapBase)
1139
-		   || !isset($this->configuration->ldapBase[0])) {
1140
-			return false;
1141
-		}
1142
-		$base = $this->configuration->ldapBase[0];
1143
-		$cr = $this->getConnection();
1144
-		if(!$this->ldap->isResource($cr)) {
1145
-			return false;
1146
-		}
1147
-		$lastFilter = null;
1148
-		if(isset($filters[count($filters)-1])) {
1149
-			$lastFilter = $filters[count($filters)-1];
1150
-		}
1151
-		foreach($filters as $filter) {
1152
-			if($lastFilter === $filter && count($foundItems) > 0) {
1153
-				//skip when the filter is a wildcard and results were found
1154
-				continue;
1155
-			}
1156
-			// 20k limit for performance and reason
1157
-			$rr = $this->ldap->search($cr, $base, $filter, array($attr), 0, 20000);
1158
-			if(!$this->ldap->isResource($rr)) {
1159
-				continue;
1160
-			}
1161
-			$entries = $this->ldap->countEntries($cr, $rr);
1162
-			$getEntryFunc = 'firstEntry';
1163
-			if(($entries !== false) && ($entries > 0)) {
1164
-				if(!is_null($maxF) && $entries > $maxEntries) {
1165
-					$maxEntries = $entries;
1166
-					$maxF = $filter;
1167
-				}
1168
-				$dnReadCount = 0;
1169
-				do {
1170
-					$entry = $this->ldap->$getEntryFunc($cr, $rr);
1171
-					$getEntryFunc = 'nextEntry';
1172
-					if(!$this->ldap->isResource($entry)) {
1173
-						continue 2;
1174
-					}
1175
-					$rr = $entry; //will be expected by nextEntry next round
1176
-					$attributes = $this->ldap->getAttributes($cr, $entry);
1177
-					$dn = $this->ldap->getDN($cr, $entry);
1178
-					if($dn === false || in_array($dn, $dnRead)) {
1179
-						continue;
1180
-					}
1181
-					$newItems = array();
1182
-					$state = $this->getAttributeValuesFromEntry($attributes,
1183
-																$attr,
1184
-																$newItems);
1185
-					$dnReadCount++;
1186
-					$foundItems = array_merge($foundItems, $newItems);
1187
-					$this->resultCache[$dn][$attr] = $newItems;
1188
-					$dnRead[] = $dn;
1189
-				} while(($state === self::LRESULT_PROCESSED_SKIP
1190
-						|| $this->ldap->isResource($entry))
1191
-						&& ($dnReadLimit === 0 || $dnReadCount < $dnReadLimit));
1192
-			}
1193
-		}
1194
-
1195
-		return array_unique($foundItems);
1196
-	}
1197
-
1198
-	/**
1199
-	 * determines if and which $attr are available on the LDAP server
1200
-	 * @param string[] $objectclasses the objectclasses to use as search filter
1201
-	 * @param string $attr the attribute to look for
1202
-	 * @param string $dbkey the dbkey of the setting the feature is connected to
1203
-	 * @param string $confkey the confkey counterpart for the $dbkey as used in the
1204
-	 * Configuration class
1205
-	 * @param bool $po whether the objectClass with most result entries
1206
-	 * shall be pre-selected via the result
1207
-	 * @return array|false list of found items.
1208
-	 * @throws \Exception
1209
-	 */
1210
-	private function determineFeature($objectclasses, $attr, $dbkey, $confkey, $po = false) {
1211
-		$cr = $this->getConnection();
1212
-		if(!$cr) {
1213
-			throw new \Exception('Could not connect to LDAP');
1214
-		}
1215
-		$p = 'objectclass=';
1216
-		foreach($objectclasses as $key => $value) {
1217
-			$objectclasses[$key] = $p.$value;
1218
-		}
1219
-		$maxEntryObjC = '';
1220
-
1221
-		//how deep to dig?
1222
-		//When looking for objectclasses, testing few entries is sufficient,
1223
-		$dig = 3;
1224
-
1225
-		$availableFeatures =
1226
-			$this->cumulativeSearchOnAttribute($objectclasses, $attr,
1227
-											   $dig, $maxEntryObjC);
1228
-		if(is_array($availableFeatures)
1229
-		   && count($availableFeatures) > 0) {
1230
-			natcasesort($availableFeatures);
1231
-			//natcasesort keeps indices, but we must get rid of them for proper
1232
-			//sorting in the web UI. Therefore: array_values
1233
-			$this->result->addOptions($dbkey, array_values($availableFeatures));
1234
-		} else {
1235
-			throw new \Exception(self::$l->t('Could not find the desired feature'));
1236
-		}
1237
-
1238
-		$setFeatures = $this->configuration->$confkey;
1239
-		if(is_array($setFeatures) && !empty($setFeatures)) {
1240
-			//something is already configured? pre-select it.
1241
-			$this->result->addChange($dbkey, $setFeatures);
1242
-		} else if ($po && $maxEntryObjC !== '') {
1243
-			//pre-select objectclass with most result entries
1244
-			$maxEntryObjC = str_replace($p, '', $maxEntryObjC);
1245
-			$this->applyFind($dbkey, $maxEntryObjC);
1246
-			$this->result->addChange($dbkey, $maxEntryObjC);
1247
-		}
1248
-
1249
-		return $availableFeatures;
1250
-	}
1251
-
1252
-	/**
1253
-	 * appends a list of values fr
1254
-	 * @param resource $result the return value from ldap_get_attributes
1255
-	 * @param string $attribute the attribute values to look for
1256
-	 * @param array &$known new values will be appended here
1257
-	 * @return int, state on of the class constants LRESULT_PROCESSED_OK,
1258
-	 * LRESULT_PROCESSED_INVALID or LRESULT_PROCESSED_SKIP
1259
-	 */
1260
-	private function getAttributeValuesFromEntry($result, $attribute, &$known) {
1261
-		if(!is_array($result)
1262
-		   || !isset($result['count'])
1263
-		   || !$result['count'] > 0) {
1264
-			return self::LRESULT_PROCESSED_INVALID;
1265
-		}
1266
-
1267
-		// strtolower on all keys for proper comparison
1268
-		$result = \OCP\Util::mb_array_change_key_case($result);
1269
-		$attribute = strtolower($attribute);
1270
-		if(isset($result[$attribute])) {
1271
-			foreach($result[$attribute] as $key => $val) {
1272
-				if($key === 'count') {
1273
-					continue;
1274
-				}
1275
-				if(!in_array($val, $known)) {
1276
-					$known[] = $val;
1277
-				}
1278
-			}
1279
-			return self::LRESULT_PROCESSED_OK;
1280
-		} else {
1281
-			return self::LRESULT_PROCESSED_SKIP;
1282
-		}
1283
-	}
1284
-
1285
-	/**
1286
-	 * @return bool|mixed
1287
-	 */
1288
-	private function getConnection() {
1289
-		if(!is_null($this->cr)) {
1290
-			return $this->cr;
1291
-		}
1292
-
1293
-		$cr = $this->ldap->connect(
1294
-			$this->configuration->ldapHost,
1295
-			$this->configuration->ldapPort
1296
-		);
1297
-
1298
-		$this->ldap->setOption($cr, LDAP_OPT_PROTOCOL_VERSION, 3);
1299
-		$this->ldap->setOption($cr, LDAP_OPT_REFERRALS, 0);
1300
-		$this->ldap->setOption($cr, LDAP_OPT_NETWORK_TIMEOUT, self::LDAP_NW_TIMEOUT);
1301
-		if($this->configuration->ldapTLS === 1) {
1302
-			$this->ldap->startTls($cr);
1303
-		}
1304
-
1305
-		$lo = @$this->ldap->bind($cr,
1306
-								 $this->configuration->ldapAgentName,
1307
-								 $this->configuration->ldapAgentPassword);
1308
-		if($lo === true) {
1309
-			$this->$cr = $cr;
1310
-			return $cr;
1311
-		}
1312
-
1313
-		return false;
1314
-	}
1315
-
1316
-	/**
1317
-	 * @return array
1318
-	 */
1319
-	private function getDefaultLdapPortSettings() {
1320
-		static $settings = array(
1321
-								array('port' => 7636, 'tls' => false),
1322
-								array('port' =>  636, 'tls' => false),
1323
-								array('port' => 7389, 'tls' => true),
1324
-								array('port' =>  389, 'tls' => true),
1325
-								array('port' => 7389, 'tls' => false),
1326
-								array('port' =>  389, 'tls' => false),
1327
-						  );
1328
-		return $settings;
1329
-	}
1330
-
1331
-	/**
1332
-	 * @return array
1333
-	 */
1334
-	private function getPortSettingsToTry() {
1335
-		//389 ← LDAP / Unencrypted or StartTLS
1336
-		//636 ← LDAPS / SSL
1337
-		//7xxx ← UCS. need to be checked first, because both ports may be open
1338
-		$host = $this->configuration->ldapHost;
1339
-		$port = (int)$this->configuration->ldapPort;
1340
-		$portSettings = array();
1341
-
1342
-		//In case the port is already provided, we will check this first
1343
-		if($port > 0) {
1344
-			$hostInfo = parse_url($host);
1345
-			if(!(is_array($hostInfo)
1346
-				&& isset($hostInfo['scheme'])
1347
-				&& stripos($hostInfo['scheme'], 'ldaps') !== false)) {
1348
-				$portSettings[] = array('port' => $port, 'tls' => true);
1349
-			}
1350
-			$portSettings[] =array('port' => $port, 'tls' => false);
1351
-		}
1352
-
1353
-		//default ports
1354
-		$portSettings = array_merge($portSettings,
1355
-		                            $this->getDefaultLdapPortSettings());
1356
-
1357
-		return $portSettings;
1358
-	}
45
+    /** @var \OCP\IL10N */
46
+    static protected $l;
47
+    protected $access;
48
+    protected $cr;
49
+    protected $configuration;
50
+    protected $result;
51
+    protected $resultCache = array();
52
+
53
+    const LRESULT_PROCESSED_OK = 2;
54
+    const LRESULT_PROCESSED_INVALID = 3;
55
+    const LRESULT_PROCESSED_SKIP = 4;
56
+
57
+    const LFILTER_LOGIN      = 2;
58
+    const LFILTER_USER_LIST  = 3;
59
+    const LFILTER_GROUP_LIST = 4;
60
+
61
+    const LFILTER_MODE_ASSISTED = 2;
62
+    const LFILTER_MODE_RAW = 1;
63
+
64
+    const LDAP_NW_TIMEOUT = 4;
65
+
66
+    /**
67
+     * Constructor
68
+     * @param Configuration $configuration an instance of Configuration
69
+     * @param ILDAPWrapper $ldap an instance of ILDAPWrapper
70
+     * @param Access $access
71
+     */
72
+    public function __construct(Configuration $configuration, ILDAPWrapper $ldap, Access $access) {
73
+        parent::__construct($ldap);
74
+        $this->configuration = $configuration;
75
+        if(is_null(Wizard::$l)) {
76
+            Wizard::$l = \OC::$server->getL10N('user_ldap');
77
+        }
78
+        $this->access = $access;
79
+        $this->result = new WizardResult();
80
+    }
81
+
82
+    public function  __destruct() {
83
+        if($this->result->hasChanges()) {
84
+            $this->configuration->saveConfiguration();
85
+        }
86
+    }
87
+
88
+    /**
89
+     * counts entries in the LDAP directory
90
+     *
91
+     * @param string $filter the LDAP search filter
92
+     * @param string $type a string being either 'users' or 'groups';
93
+     * @return int
94
+     * @throws \Exception
95
+     */
96
+    public function countEntries(string $filter, string $type): int {
97
+        $reqs = ['ldapHost', 'ldapPort', 'ldapBase'];
98
+        if($type === 'users') {
99
+            $reqs[] = 'ldapUserFilter';
100
+        }
101
+        if(!$this->checkRequirements($reqs)) {
102
+            throw new \Exception('Requirements not met', 400);
103
+        }
104
+
105
+        $attr = ['dn']; // default
106
+        $limit = 1001;
107
+        if($type === 'groups') {
108
+            $result =  $this->access->countGroups($filter, $attr, $limit);
109
+        } else if($type === 'users') {
110
+            $result = $this->access->countUsers($filter, $attr, $limit);
111
+        } else if ($type === 'objects') {
112
+            $result = $this->access->countObjects($limit);
113
+        } else {
114
+            throw new \Exception('Internal error: Invalid object type', 500);
115
+        }
116
+
117
+        return (int)$result;
118
+    }
119
+
120
+    /**
121
+     * formats the return value of a count operation to the string to be
122
+     * inserted.
123
+     *
124
+     * @param int $count
125
+     * @return string
126
+     */
127
+    private function formatCountResult(int $count): string {
128
+        if($count > 1000) {
129
+            return '> 1000';
130
+        }
131
+        return (string)$count;
132
+    }
133
+
134
+    public function countGroups() {
135
+        $filter = $this->configuration->ldapGroupFilter;
136
+
137
+        if(empty($filter)) {
138
+            $output = self::$l->n('%s group found', '%s groups found', 0, array(0));
139
+            $this->result->addChange('ldap_group_count', $output);
140
+            return $this->result;
141
+        }
142
+
143
+        try {
144
+            $groupsTotal = $this->countEntries($filter, 'groups');
145
+        } catch (\Exception $e) {
146
+            //400 can be ignored, 500 is forwarded
147
+            if($e->getCode() === 500) {
148
+                throw $e;
149
+            }
150
+            return false;
151
+        }
152
+        $output = self::$l->n(
153
+            '%s group found',
154
+            '%s groups found',
155
+            $groupsTotal,
156
+            [$this->formatCountResult($groupsTotal)]
157
+        );
158
+        $this->result->addChange('ldap_group_count', $output);
159
+        return $this->result;
160
+    }
161
+
162
+    /**
163
+     * @return WizardResult
164
+     * @throws \Exception
165
+     */
166
+    public function countUsers() {
167
+        $filter = $this->access->getFilterForUserCount();
168
+
169
+        $usersTotal = $this->countEntries($filter, 'users');
170
+        $output = self::$l->n(
171
+            '%s user found',
172
+            '%s users found',
173
+            $usersTotal,
174
+            [$this->formatCountResult($usersTotal)]
175
+        );
176
+        $this->result->addChange('ldap_user_count', $output);
177
+        return $this->result;
178
+    }
179
+
180
+    /**
181
+     * counts any objects in the currently set base dn
182
+     *
183
+     * @return WizardResult
184
+     * @throws \Exception
185
+     */
186
+    public function countInBaseDN() {
187
+        // we don't need to provide a filter in this case
188
+        $total = $this->countEntries('', 'objects');
189
+        if($total === false) {
190
+            throw new \Exception('invalid results received');
191
+        }
192
+        $this->result->addChange('ldap_test_base', $total);
193
+        return $this->result;
194
+    }
195
+
196
+    /**
197
+     * counts users with a specified attribute
198
+     * @param string $attr
199
+     * @param bool $existsCheck
200
+     * @return int|bool
201
+     */
202
+    public function countUsersWithAttribute($attr, $existsCheck = false) {
203
+        if(!$this->checkRequirements(array('ldapHost',
204
+                                            'ldapPort',
205
+                                            'ldapBase',
206
+                                            'ldapUserFilter',
207
+                                            ))) {
208
+            return  false;
209
+        }
210
+
211
+        $filter = $this->access->combineFilterWithAnd(array(
212
+            $this->configuration->ldapUserFilter,
213
+            $attr . '=*'
214
+        ));
215
+
216
+        $limit = ($existsCheck === false) ? null : 1;
217
+
218
+        return $this->access->countUsers($filter, array('dn'), $limit);
219
+    }
220
+
221
+    /**
222
+     * detects the display name attribute. If a setting is already present that
223
+     * returns at least one hit, the detection will be canceled.
224
+     * @return WizardResult|bool
225
+     * @throws \Exception
226
+     */
227
+    public function detectUserDisplayNameAttribute() {
228
+        if(!$this->checkRequirements(array('ldapHost',
229
+                                        'ldapPort',
230
+                                        'ldapBase',
231
+                                        'ldapUserFilter',
232
+                                        ))) {
233
+            return  false;
234
+        }
235
+
236
+        $attr = $this->configuration->ldapUserDisplayName;
237
+        if ($attr !== '' && $attr !== 'displayName') {
238
+            // most likely not the default value with upper case N,
239
+            // verify it still produces a result
240
+            $count = (int)$this->countUsersWithAttribute($attr, true);
241
+            if($count > 0) {
242
+                //no change, but we sent it back to make sure the user interface
243
+                //is still correct, even if the ajax call was cancelled meanwhile
244
+                $this->result->addChange('ldap_display_name', $attr);
245
+                return $this->result;
246
+            }
247
+        }
248
+
249
+        // first attribute that has at least one result wins
250
+        $displayNameAttrs = array('displayname', 'cn');
251
+        foreach ($displayNameAttrs as $attr) {
252
+            $count = (int)$this->countUsersWithAttribute($attr, true);
253
+
254
+            if($count > 0) {
255
+                $this->applyFind('ldap_display_name', $attr);
256
+                return $this->result;
257
+            }
258
+        }
259
+
260
+        throw new \Exception(self::$l->t('Could not detect user display name attribute. Please specify it yourself in advanced LDAP settings.'));
261
+    }
262
+
263
+    /**
264
+     * detects the most often used email attribute for users applying to the
265
+     * user list filter. If a setting is already present that returns at least
266
+     * one hit, the detection will be canceled.
267
+     * @return WizardResult|bool
268
+     */
269
+    public function detectEmailAttribute() {
270
+        if(!$this->checkRequirements(array('ldapHost',
271
+                                            'ldapPort',
272
+                                            'ldapBase',
273
+                                            'ldapUserFilter',
274
+                                            ))) {
275
+            return  false;
276
+        }
277
+
278
+        $attr = $this->configuration->ldapEmailAttribute;
279
+        if ($attr !== '') {
280
+            $count = (int)$this->countUsersWithAttribute($attr, true);
281
+            if($count > 0) {
282
+                return false;
283
+            }
284
+            $writeLog = true;
285
+        } else {
286
+            $writeLog = false;
287
+        }
288
+
289
+        $emailAttributes = array('mail', 'mailPrimaryAddress');
290
+        $winner = '';
291
+        $maxUsers = 0;
292
+        foreach($emailAttributes as $attr) {
293
+            $count = $this->countUsersWithAttribute($attr);
294
+            if($count > $maxUsers) {
295
+                $maxUsers = $count;
296
+                $winner = $attr;
297
+            }
298
+        }
299
+
300
+        if($winner !== '') {
301
+            $this->applyFind('ldap_email_attr', $winner);
302
+            if($writeLog) {
303
+                \OCP\Util::writeLog('user_ldap', 'The mail attribute has ' .
304
+                    'automatically been reset, because the original value ' .
305
+                    'did not return any results.', ILogger::INFO);
306
+            }
307
+        }
308
+
309
+        return $this->result;
310
+    }
311
+
312
+    /**
313
+     * @return WizardResult
314
+     * @throws \Exception
315
+     */
316
+    public function determineAttributes() {
317
+        if(!$this->checkRequirements(array('ldapHost',
318
+                                            'ldapPort',
319
+                                            'ldapBase',
320
+                                            'ldapUserFilter',
321
+                                            ))) {
322
+            return  false;
323
+        }
324
+
325
+        $attributes = $this->getUserAttributes();
326
+
327
+        natcasesort($attributes);
328
+        $attributes = array_values($attributes);
329
+
330
+        $this->result->addOptions('ldap_loginfilter_attributes', $attributes);
331
+
332
+        $selected = $this->configuration->ldapLoginFilterAttributes;
333
+        if(is_array($selected) && !empty($selected)) {
334
+            $this->result->addChange('ldap_loginfilter_attributes', $selected);
335
+        }
336
+
337
+        return $this->result;
338
+    }
339
+
340
+    /**
341
+     * detects the available LDAP attributes
342
+     * @return array|false The instance's WizardResult instance
343
+     * @throws \Exception
344
+     */
345
+    private function getUserAttributes() {
346
+        if(!$this->checkRequirements(array('ldapHost',
347
+                                            'ldapPort',
348
+                                            'ldapBase',
349
+                                            'ldapUserFilter',
350
+                                            ))) {
351
+            return  false;
352
+        }
353
+        $cr = $this->getConnection();
354
+        if(!$cr) {
355
+            throw new \Exception('Could not connect to LDAP');
356
+        }
357
+
358
+        $base = $this->configuration->ldapBase[0];
359
+        $filter = $this->configuration->ldapUserFilter;
360
+        $rr = $this->ldap->search($cr, $base, $filter, array(), 1, 1);
361
+        if(!$this->ldap->isResource($rr)) {
362
+            return false;
363
+        }
364
+        $er = $this->ldap->firstEntry($cr, $rr);
365
+        $attributes = $this->ldap->getAttributes($cr, $er);
366
+        $pureAttributes = array();
367
+        for($i = 0; $i < $attributes['count']; $i++) {
368
+            $pureAttributes[] = $attributes[$i];
369
+        }
370
+
371
+        return $pureAttributes;
372
+    }
373
+
374
+    /**
375
+     * detects the available LDAP groups
376
+     * @return WizardResult|false the instance's WizardResult instance
377
+     */
378
+    public function determineGroupsForGroups() {
379
+        return $this->determineGroups('ldap_groupfilter_groups',
380
+                                        'ldapGroupFilterGroups',
381
+                                        false);
382
+    }
383
+
384
+    /**
385
+     * detects the available LDAP groups
386
+     * @return WizardResult|false the instance's WizardResult instance
387
+     */
388
+    public function determineGroupsForUsers() {
389
+        return $this->determineGroups('ldap_userfilter_groups',
390
+                                        'ldapUserFilterGroups');
391
+    }
392
+
393
+    /**
394
+     * detects the available LDAP groups
395
+     * @param string $dbKey
396
+     * @param string $confKey
397
+     * @param bool $testMemberOf
398
+     * @return WizardResult|false the instance's WizardResult instance
399
+     * @throws \Exception
400
+     */
401
+    private function determineGroups($dbKey, $confKey, $testMemberOf = true) {
402
+        if(!$this->checkRequirements(array('ldapHost',
403
+                                            'ldapPort',
404
+                                            'ldapBase',
405
+                                            ))) {
406
+            return  false;
407
+        }
408
+        $cr = $this->getConnection();
409
+        if(!$cr) {
410
+            throw new \Exception('Could not connect to LDAP');
411
+        }
412
+
413
+        $this->fetchGroups($dbKey, $confKey);
414
+
415
+        if($testMemberOf) {
416
+            $this->configuration->hasMemberOfFilterSupport = $this->testMemberOf();
417
+            $this->result->markChange();
418
+            if(!$this->configuration->hasMemberOfFilterSupport) {
419
+                throw new \Exception('memberOf is not supported by the server');
420
+            }
421
+        }
422
+
423
+        return $this->result;
424
+    }
425
+
426
+    /**
427
+     * fetches all groups from LDAP and adds them to the result object
428
+     *
429
+     * @param string $dbKey
430
+     * @param string $confKey
431
+     * @return array $groupEntries
432
+     * @throws \Exception
433
+     */
434
+    public function fetchGroups($dbKey, $confKey) {
435
+        $obclasses = array('posixGroup', 'group', 'zimbraDistributionList', 'groupOfNames', 'groupOfUniqueNames');
436
+
437
+        $filterParts = array();
438
+        foreach($obclasses as $obclass) {
439
+            $filterParts[] = 'objectclass='.$obclass;
440
+        }
441
+        //we filter for everything
442
+        //- that looks like a group and
443
+        //- has the group display name set
444
+        $filter = $this->access->combineFilterWithOr($filterParts);
445
+        $filter = $this->access->combineFilterWithAnd(array($filter, 'cn=*'));
446
+
447
+        $groupNames = array();
448
+        $groupEntries = array();
449
+        $limit = 400;
450
+        $offset = 0;
451
+        do {
452
+            // we need to request dn additionally here, otherwise memberOf
453
+            // detection will fail later
454
+            $result = $this->access->searchGroups($filter, array('cn', 'dn'), $limit, $offset);
455
+            foreach($result as $item) {
456
+                if(!isset($item['cn']) && !is_array($item['cn']) && !isset($item['cn'][0])) {
457
+                    // just in case - no issue known
458
+                    continue;
459
+                }
460
+                $groupNames[] = $item['cn'][0];
461
+                $groupEntries[] = $item;
462
+            }
463
+            $offset += $limit;
464
+        } while ($this->access->hasMoreResults());
465
+
466
+        if(count($groupNames) > 0) {
467
+            natsort($groupNames);
468
+            $this->result->addOptions($dbKey, array_values($groupNames));
469
+        } else {
470
+            throw new \Exception(self::$l->t('Could not find the desired feature'));
471
+        }
472
+
473
+        $setFeatures = $this->configuration->$confKey;
474
+        if(is_array($setFeatures) && !empty($setFeatures)) {
475
+            //something is already configured? pre-select it.
476
+            $this->result->addChange($dbKey, $setFeatures);
477
+        }
478
+        return $groupEntries;
479
+    }
480
+
481
+    public function determineGroupMemberAssoc() {
482
+        if(!$this->checkRequirements(array('ldapHost',
483
+                                            'ldapPort',
484
+                                            'ldapGroupFilter',
485
+                                            ))) {
486
+            return  false;
487
+        }
488
+        $attribute = $this->detectGroupMemberAssoc();
489
+        if($attribute === false) {
490
+            return false;
491
+        }
492
+        $this->configuration->setConfiguration(array('ldapGroupMemberAssocAttr' => $attribute));
493
+        $this->result->addChange('ldap_group_member_assoc_attribute', $attribute);
494
+
495
+        return $this->result;
496
+    }
497
+
498
+    /**
499
+     * Detects the available object classes
500
+     * @return WizardResult|false the instance's WizardResult instance
501
+     * @throws \Exception
502
+     */
503
+    public function determineGroupObjectClasses() {
504
+        if(!$this->checkRequirements(array('ldapHost',
505
+                                            'ldapPort',
506
+                                            'ldapBase',
507
+                                            ))) {
508
+            return  false;
509
+        }
510
+        $cr = $this->getConnection();
511
+        if(!$cr) {
512
+            throw new \Exception('Could not connect to LDAP');
513
+        }
514
+
515
+        $obclasses = array('groupOfNames', 'groupOfUniqueNames', 'group', 'posixGroup', '*');
516
+        $this->determineFeature($obclasses,
517
+                                'objectclass',
518
+                                'ldap_groupfilter_objectclass',
519
+                                'ldapGroupFilterObjectclass',
520
+                                false);
521
+
522
+        return $this->result;
523
+    }
524
+
525
+    /**
526
+     * detects the available object classes
527
+     * @return WizardResult
528
+     * @throws \Exception
529
+     */
530
+    public function determineUserObjectClasses() {
531
+        if(!$this->checkRequirements(array('ldapHost',
532
+                                            'ldapPort',
533
+                                            'ldapBase',
534
+                                            ))) {
535
+            return  false;
536
+        }
537
+        $cr = $this->getConnection();
538
+        if(!$cr) {
539
+            throw new \Exception('Could not connect to LDAP');
540
+        }
541
+
542
+        $obclasses = array('inetOrgPerson', 'person', 'organizationalPerson',
543
+                            'user', 'posixAccount', '*');
544
+        $filter = $this->configuration->ldapUserFilter;
545
+        //if filter is empty, it is probably the first time the wizard is called
546
+        //then, apply suggestions.
547
+        $this->determineFeature($obclasses,
548
+                                'objectclass',
549
+                                'ldap_userfilter_objectclass',
550
+                                'ldapUserFilterObjectclass',
551
+                                empty($filter));
552
+
553
+        return $this->result;
554
+    }
555
+
556
+    /**
557
+     * @return WizardResult|false
558
+     * @throws \Exception
559
+     */
560
+    public function getGroupFilter() {
561
+        if(!$this->checkRequirements(array('ldapHost',
562
+                                            'ldapPort',
563
+                                            'ldapBase',
564
+                                            ))) {
565
+            return false;
566
+        }
567
+        //make sure the use display name is set
568
+        $displayName = $this->configuration->ldapGroupDisplayName;
569
+        if ($displayName === '') {
570
+            $d = $this->configuration->getDefaults();
571
+            $this->applyFind('ldap_group_display_name',
572
+                                $d['ldap_group_display_name']);
573
+        }
574
+        $filter = $this->composeLdapFilter(self::LFILTER_GROUP_LIST);
575
+
576
+        $this->applyFind('ldap_group_filter', $filter);
577
+        return $this->result;
578
+    }
579
+
580
+    /**
581
+     * @return WizardResult|false
582
+     * @throws \Exception
583
+     */
584
+    public function getUserListFilter() {
585
+        if(!$this->checkRequirements(array('ldapHost',
586
+                                            'ldapPort',
587
+                                            'ldapBase',
588
+                                            ))) {
589
+            return false;
590
+        }
591
+        //make sure the use display name is set
592
+        $displayName = $this->configuration->ldapUserDisplayName;
593
+        if ($displayName === '') {
594
+            $d = $this->configuration->getDefaults();
595
+            $this->applyFind('ldap_display_name', $d['ldap_display_name']);
596
+        }
597
+        $filter = $this->composeLdapFilter(self::LFILTER_USER_LIST);
598
+        if(!$filter) {
599
+            throw new \Exception('Cannot create filter');
600
+        }
601
+
602
+        $this->applyFind('ldap_userlist_filter', $filter);
603
+        return $this->result;
604
+    }
605
+
606
+    /**
607
+     * @return bool|WizardResult
608
+     * @throws \Exception
609
+     */
610
+    public function getUserLoginFilter() {
611
+        if(!$this->checkRequirements(array('ldapHost',
612
+                                            'ldapPort',
613
+                                            'ldapBase',
614
+                                            'ldapUserFilter',
615
+                                            ))) {
616
+            return false;
617
+        }
618
+
619
+        $filter = $this->composeLdapFilter(self::LFILTER_LOGIN);
620
+        if(!$filter) {
621
+            throw new \Exception('Cannot create filter');
622
+        }
623
+
624
+        $this->applyFind('ldap_login_filter', $filter);
625
+        return $this->result;
626
+    }
627
+
628
+    /**
629
+     * @return bool|WizardResult
630
+     * @param string $loginName
631
+     * @throws \Exception
632
+     */
633
+    public function testLoginName($loginName) {
634
+        if(!$this->checkRequirements(array('ldapHost',
635
+            'ldapPort',
636
+            'ldapBase',
637
+            'ldapLoginFilter',
638
+        ))) {
639
+            return false;
640
+        }
641
+
642
+        $cr = $this->access->connection->getConnectionResource();
643
+        if(!$this->ldap->isResource($cr)) {
644
+            throw new \Exception('connection error');
645
+        }
646
+
647
+        if(mb_strpos($this->access->connection->ldapLoginFilter, '%uid', 0, 'UTF-8')
648
+            === false) {
649
+            throw new \Exception('missing placeholder');
650
+        }
651
+
652
+        $users = $this->access->countUsersByLoginName($loginName);
653
+        if($this->ldap->errno($cr) !== 0) {
654
+            throw new \Exception($this->ldap->error($cr));
655
+        }
656
+        $filter = str_replace('%uid', $loginName, $this->access->connection->ldapLoginFilter);
657
+        $this->result->addChange('ldap_test_loginname', $users);
658
+        $this->result->addChange('ldap_test_effective_filter', $filter);
659
+        return $this->result;
660
+    }
661
+
662
+    /**
663
+     * Tries to determine the port, requires given Host, User DN and Password
664
+     * @return WizardResult|false WizardResult on success, false otherwise
665
+     * @throws \Exception
666
+     */
667
+    public function guessPortAndTLS() {
668
+        if(!$this->checkRequirements(array('ldapHost',
669
+                                            ))) {
670
+            return false;
671
+        }
672
+        $this->checkHost();
673
+        $portSettings = $this->getPortSettingsToTry();
674
+
675
+        if(!is_array($portSettings)) {
676
+            throw new \Exception(print_r($portSettings, true));
677
+        }
678
+
679
+        //proceed from the best configuration and return on first success
680
+        foreach($portSettings as $setting) {
681
+            $p = $setting['port'];
682
+            $t = $setting['tls'];
683
+            \OCP\Util::writeLog('user_ldap', 'Wiz: trying port '. $p . ', TLS '. $t, ILogger::DEBUG);
684
+            //connectAndBind may throw Exception, it needs to be catched by the
685
+            //callee of this method
686
+
687
+            try {
688
+                $settingsFound = $this->connectAndBind($p, $t);
689
+            } catch (\Exception $e) {
690
+                // any reply other than -1 (= cannot connect) is already okay,
691
+                // because then we found the server
692
+                // unavailable startTLS returns -11
693
+                if($e->getCode() > 0) {
694
+                    $settingsFound = true;
695
+                } else {
696
+                    throw $e;
697
+                }
698
+            }
699
+
700
+            if ($settingsFound === true) {
701
+                $config = array(
702
+                    'ldapPort' => $p,
703
+                    'ldapTLS' => (int)$t
704
+                );
705
+                $this->configuration->setConfiguration($config);
706
+                \OCP\Util::writeLog('user_ldap', 'Wiz: detected Port ' . $p, ILogger::DEBUG);
707
+                $this->result->addChange('ldap_port', $p);
708
+                return $this->result;
709
+            }
710
+        }
711
+
712
+        //custom port, undetected (we do not brute force)
713
+        return false;
714
+    }
715
+
716
+    /**
717
+     * tries to determine a base dn from User DN or LDAP Host
718
+     * @return WizardResult|false WizardResult on success, false otherwise
719
+     */
720
+    public function guessBaseDN() {
721
+        if(!$this->checkRequirements(array('ldapHost',
722
+                                            'ldapPort',
723
+                                            ))) {
724
+            return false;
725
+        }
726
+
727
+        //check whether a DN is given in the agent name (99.9% of all cases)
728
+        $base = null;
729
+        $i = stripos($this->configuration->ldapAgentName, 'dc=');
730
+        if($i !== false) {
731
+            $base = substr($this->configuration->ldapAgentName, $i);
732
+            if($this->testBaseDN($base)) {
733
+                $this->applyFind('ldap_base', $base);
734
+                return $this->result;
735
+            }
736
+        }
737
+
738
+        //this did not help :(
739
+        //Let's see whether we can parse the Host URL and convert the domain to
740
+        //a base DN
741
+        $helper = new Helper(\OC::$server->getConfig());
742
+        $domain = $helper->getDomainFromURL($this->configuration->ldapHost);
743
+        if(!$domain) {
744
+            return false;
745
+        }
746
+
747
+        $dparts = explode('.', $domain);
748
+        while(count($dparts) > 0) {
749
+            $base2 = 'dc=' . implode(',dc=', $dparts);
750
+            if ($base !== $base2 && $this->testBaseDN($base2)) {
751
+                $this->applyFind('ldap_base', $base2);
752
+                return $this->result;
753
+            }
754
+            array_shift($dparts);
755
+        }
756
+
757
+        return false;
758
+    }
759
+
760
+    /**
761
+     * sets the found value for the configuration key in the WizardResult
762
+     * as well as in the Configuration instance
763
+     * @param string $key the configuration key
764
+     * @param string $value the (detected) value
765
+     *
766
+     */
767
+    private function applyFind($key, $value) {
768
+        $this->result->addChange($key, $value);
769
+        $this->configuration->setConfiguration(array($key => $value));
770
+    }
771
+
772
+    /**
773
+     * Checks, whether a port was entered in the Host configuration
774
+     * field. In this case the port will be stripped off, but also stored as
775
+     * setting.
776
+     */
777
+    private function checkHost() {
778
+        $host = $this->configuration->ldapHost;
779
+        $hostInfo = parse_url($host);
780
+
781
+        //removes Port from Host
782
+        if(is_array($hostInfo) && isset($hostInfo['port'])) {
783
+            $port = $hostInfo['port'];
784
+            $host = str_replace(':'.$port, '', $host);
785
+            $this->applyFind('ldap_host', $host);
786
+            $this->applyFind('ldap_port', $port);
787
+        }
788
+    }
789
+
790
+    /**
791
+     * tries to detect the group member association attribute which is
792
+     * one of 'uniqueMember', 'memberUid', 'member', 'gidNumber'
793
+     * @return string|false, string with the attribute name, false on error
794
+     * @throws \Exception
795
+     */
796
+    private function detectGroupMemberAssoc() {
797
+        $possibleAttrs = array('uniqueMember', 'memberUid', 'member', 'gidNumber');
798
+        $filter = $this->configuration->ldapGroupFilter;
799
+        if(empty($filter)) {
800
+            return false;
801
+        }
802
+        $cr = $this->getConnection();
803
+        if(!$cr) {
804
+            throw new \Exception('Could not connect to LDAP');
805
+        }
806
+        $base = $this->configuration->ldapBase[0];
807
+        $rr = $this->ldap->search($cr, $base, $filter, $possibleAttrs, 0, 1000);
808
+        if(!$this->ldap->isResource($rr)) {
809
+            return false;
810
+        }
811
+        $er = $this->ldap->firstEntry($cr, $rr);
812
+        while(is_resource($er)) {
813
+            $this->ldap->getDN($cr, $er);
814
+            $attrs = $this->ldap->getAttributes($cr, $er);
815
+            $result = array();
816
+            $possibleAttrsCount = count($possibleAttrs);
817
+            for($i = 0; $i < $possibleAttrsCount; $i++) {
818
+                if(isset($attrs[$possibleAttrs[$i]])) {
819
+                    $result[$possibleAttrs[$i]] = $attrs[$possibleAttrs[$i]]['count'];
820
+                }
821
+            }
822
+            if(!empty($result)) {
823
+                natsort($result);
824
+                return key($result);
825
+            }
826
+
827
+            $er = $this->ldap->nextEntry($cr, $er);
828
+        }
829
+
830
+        return false;
831
+    }
832
+
833
+    /**
834
+     * Checks whether for a given BaseDN results will be returned
835
+     * @param string $base the BaseDN to test
836
+     * @return bool true on success, false otherwise
837
+     * @throws \Exception
838
+     */
839
+    private function testBaseDN($base) {
840
+        $cr = $this->getConnection();
841
+        if(!$cr) {
842
+            throw new \Exception('Could not connect to LDAP');
843
+        }
844
+
845
+        //base is there, let's validate it. If we search for anything, we should
846
+        //get a result set > 0 on a proper base
847
+        $rr = $this->ldap->search($cr, $base, 'objectClass=*', array('dn'), 0, 1);
848
+        if(!$this->ldap->isResource($rr)) {
849
+            $errorNo  = $this->ldap->errno($cr);
850
+            $errorMsg = $this->ldap->error($cr);
851
+            \OCP\Util::writeLog('user_ldap', 'Wiz: Could not search base '.$base.
852
+                            ' Error '.$errorNo.': '.$errorMsg, ILogger::INFO);
853
+            return false;
854
+        }
855
+        $entries = $this->ldap->countEntries($cr, $rr);
856
+        return ($entries !== false) && ($entries > 0);
857
+    }
858
+
859
+    /**
860
+     * Checks whether the server supports memberOf in LDAP Filter.
861
+     * Note: at least in OpenLDAP, availability of memberOf is dependent on
862
+     * a configured objectClass. I.e. not necessarily for all available groups
863
+     * memberOf does work.
864
+     *
865
+     * @return bool true if it does, false otherwise
866
+     * @throws \Exception
867
+     */
868
+    private function testMemberOf() {
869
+        $cr = $this->getConnection();
870
+        if(!$cr) {
871
+            throw new \Exception('Could not connect to LDAP');
872
+        }
873
+        $result = $this->access->countUsers('memberOf=*', array('memberOf'), 1);
874
+        if(is_int($result) &&  $result > 0) {
875
+            return true;
876
+        }
877
+        return false;
878
+    }
879
+
880
+    /**
881
+     * creates an LDAP Filter from given configuration
882
+     * @param integer $filterType int, for which use case the filter shall be created
883
+     * can be any of self::LFILTER_USER_LIST, self::LFILTER_LOGIN or
884
+     * self::LFILTER_GROUP_LIST
885
+     * @return string|false string with the filter on success, false otherwise
886
+     * @throws \Exception
887
+     */
888
+    private function composeLdapFilter($filterType) {
889
+        $filter = '';
890
+        $parts = 0;
891
+        switch ($filterType) {
892
+            case self::LFILTER_USER_LIST:
893
+                $objcs = $this->configuration->ldapUserFilterObjectclass;
894
+                //glue objectclasses
895
+                if(is_array($objcs) && count($objcs) > 0) {
896
+                    $filter .= '(|';
897
+                    foreach($objcs as $objc) {
898
+                        $filter .= '(objectclass=' . $objc . ')';
899
+                    }
900
+                    $filter .= ')';
901
+                    $parts++;
902
+                }
903
+                //glue group memberships
904
+                if($this->configuration->hasMemberOfFilterSupport) {
905
+                    $cns = $this->configuration->ldapUserFilterGroups;
906
+                    if(is_array($cns) && count($cns) > 0) {
907
+                        $filter .= '(|';
908
+                        $cr = $this->getConnection();
909
+                        if(!$cr) {
910
+                            throw new \Exception('Could not connect to LDAP');
911
+                        }
912
+                        $base = $this->configuration->ldapBase[0];
913
+                        foreach($cns as $cn) {
914
+                            $rr = $this->ldap->search($cr, $base, 'cn=' . $cn, array('dn', 'primaryGroupToken'));
915
+                            if(!$this->ldap->isResource($rr)) {
916
+                                continue;
917
+                            }
918
+                            $er = $this->ldap->firstEntry($cr, $rr);
919
+                            $attrs = $this->ldap->getAttributes($cr, $er);
920
+                            $dn = $this->ldap->getDN($cr, $er);
921
+                            if ($dn === false || $dn === '') {
922
+                                continue;
923
+                            }
924
+                            $filterPart = '(memberof=' . $dn . ')';
925
+                            if(isset($attrs['primaryGroupToken'])) {
926
+                                $pgt = $attrs['primaryGroupToken'][0];
927
+                                $primaryFilterPart = '(primaryGroupID=' . $pgt .')';
928
+                                $filterPart = '(|' . $filterPart . $primaryFilterPart . ')';
929
+                            }
930
+                            $filter .= $filterPart;
931
+                        }
932
+                        $filter .= ')';
933
+                    }
934
+                    $parts++;
935
+                }
936
+                //wrap parts in AND condition
937
+                if($parts > 1) {
938
+                    $filter = '(&' . $filter . ')';
939
+                }
940
+                if ($filter === '') {
941
+                    $filter = '(objectclass=*)';
942
+                }
943
+                break;
944
+
945
+            case self::LFILTER_GROUP_LIST:
946
+                $objcs = $this->configuration->ldapGroupFilterObjectclass;
947
+                //glue objectclasses
948
+                if(is_array($objcs) && count($objcs) > 0) {
949
+                    $filter .= '(|';
950
+                    foreach($objcs as $objc) {
951
+                        $filter .= '(objectclass=' . $objc . ')';
952
+                    }
953
+                    $filter .= ')';
954
+                    $parts++;
955
+                }
956
+                //glue group memberships
957
+                $cns = $this->configuration->ldapGroupFilterGroups;
958
+                if(is_array($cns) && count($cns) > 0) {
959
+                    $filter .= '(|';
960
+                    foreach($cns as $cn) {
961
+                        $filter .= '(cn=' . $cn . ')';
962
+                    }
963
+                    $filter .= ')';
964
+                }
965
+                $parts++;
966
+                //wrap parts in AND condition
967
+                if($parts > 1) {
968
+                    $filter = '(&' . $filter . ')';
969
+                }
970
+                break;
971
+
972
+            case self::LFILTER_LOGIN:
973
+                $ulf = $this->configuration->ldapUserFilter;
974
+                $loginpart = '=%uid';
975
+                $filterUsername = '';
976
+                $userAttributes = $this->getUserAttributes();
977
+                $userAttributes = array_change_key_case(array_flip($userAttributes));
978
+                $parts = 0;
979
+
980
+                if($this->configuration->ldapLoginFilterUsername === '1') {
981
+                    $attr = '';
982
+                    if(isset($userAttributes['uid'])) {
983
+                        $attr = 'uid';
984
+                    } else if(isset($userAttributes['samaccountname'])) {
985
+                        $attr = 'samaccountname';
986
+                    } else if(isset($userAttributes['cn'])) {
987
+                        //fallback
988
+                        $attr = 'cn';
989
+                    }
990
+                    if ($attr !== '') {
991
+                        $filterUsername = '(' . $attr . $loginpart . ')';
992
+                        $parts++;
993
+                    }
994
+                }
995
+
996
+                $filterEmail = '';
997
+                if($this->configuration->ldapLoginFilterEmail === '1') {
998
+                    $filterEmail = '(|(mailPrimaryAddress=%uid)(mail=%uid))';
999
+                    $parts++;
1000
+                }
1001
+
1002
+                $filterAttributes = '';
1003
+                $attrsToFilter = $this->configuration->ldapLoginFilterAttributes;
1004
+                if(is_array($attrsToFilter) && count($attrsToFilter) > 0) {
1005
+                    $filterAttributes = '(|';
1006
+                    foreach($attrsToFilter as $attribute) {
1007
+                        $filterAttributes .= '(' . $attribute . $loginpart . ')';
1008
+                    }
1009
+                    $filterAttributes .= ')';
1010
+                    $parts++;
1011
+                }
1012
+
1013
+                $filterLogin = '';
1014
+                if($parts > 1) {
1015
+                    $filterLogin = '(|';
1016
+                }
1017
+                $filterLogin .= $filterUsername;
1018
+                $filterLogin .= $filterEmail;
1019
+                $filterLogin .= $filterAttributes;
1020
+                if($parts > 1) {
1021
+                    $filterLogin .= ')';
1022
+                }
1023
+
1024
+                $filter = '(&'.$ulf.$filterLogin.')';
1025
+                break;
1026
+        }
1027
+
1028
+        \OCP\Util::writeLog('user_ldap', 'Wiz: Final filter '.$filter, ILogger::DEBUG);
1029
+
1030
+        return $filter;
1031
+    }
1032
+
1033
+    /**
1034
+     * Connects and Binds to an LDAP Server
1035
+     *
1036
+     * @param int $port the port to connect with
1037
+     * @param bool $tls whether startTLS is to be used
1038
+     * @return bool
1039
+     * @throws \Exception
1040
+     */
1041
+    private function connectAndBind($port, $tls) {
1042
+        //connect, does not really trigger any server communication
1043
+        $host = $this->configuration->ldapHost;
1044
+        $hostInfo = parse_url($host);
1045
+        if(!$hostInfo) {
1046
+            throw new \Exception(self::$l->t('Invalid Host'));
1047
+        }
1048
+        \OCP\Util::writeLog('user_ldap', 'Wiz: Attempting to connect ', ILogger::DEBUG);
1049
+        $cr = $this->ldap->connect($host, $port);
1050
+        if(!is_resource($cr)) {
1051
+            throw new \Exception(self::$l->t('Invalid Host'));
1052
+        }
1053
+
1054
+        //set LDAP options
1055
+        $this->ldap->setOption($cr, LDAP_OPT_PROTOCOL_VERSION, 3);
1056
+        $this->ldap->setOption($cr, LDAP_OPT_REFERRALS, 0);
1057
+        $this->ldap->setOption($cr, LDAP_OPT_NETWORK_TIMEOUT, self::LDAP_NW_TIMEOUT);
1058
+
1059
+        try {
1060
+            if($tls) {
1061
+                $isTlsWorking = @$this->ldap->startTls($cr);
1062
+                if(!$isTlsWorking) {
1063
+                    return false;
1064
+                }
1065
+            }
1066
+
1067
+            \OCP\Util::writeLog('user_ldap', 'Wiz: Attemping to Bind ', ILogger::DEBUG);
1068
+            //interesting part: do the bind!
1069
+            $login = $this->ldap->bind($cr,
1070
+                $this->configuration->ldapAgentName,
1071
+                $this->configuration->ldapAgentPassword
1072
+            );
1073
+            $errNo = $this->ldap->errno($cr);
1074
+            $error = ldap_error($cr);
1075
+            $this->ldap->unbind($cr);
1076
+        } catch(ServerNotAvailableException $e) {
1077
+            return false;
1078
+        }
1079
+
1080
+        if($login === true) {
1081
+            $this->ldap->unbind($cr);
1082
+            \OCP\Util::writeLog('user_ldap', 'Wiz: Bind successful to Port '. $port . ' TLS ' . (int)$tls, ILogger::DEBUG);
1083
+            return true;
1084
+        }
1085
+
1086
+        if($errNo === -1) {
1087
+            //host, port or TLS wrong
1088
+            return false;
1089
+        }
1090
+        throw new \Exception($error, $errNo);
1091
+    }
1092
+
1093
+    /**
1094
+     * checks whether a valid combination of agent and password has been
1095
+     * provided (either two values or nothing for anonymous connect)
1096
+     * @return bool, true if everything is fine, false otherwise
1097
+     */
1098
+    private function checkAgentRequirements() {
1099
+        $agent = $this->configuration->ldapAgentName;
1100
+        $pwd = $this->configuration->ldapAgentPassword;
1101
+
1102
+        return
1103
+            ($agent !== '' && $pwd !== '')
1104
+            ||  ($agent === '' && $pwd === '')
1105
+        ;
1106
+    }
1107
+
1108
+    /**
1109
+     * @param array $reqs
1110
+     * @return bool
1111
+     */
1112
+    private function checkRequirements($reqs) {
1113
+        $this->checkAgentRequirements();
1114
+        foreach($reqs as $option) {
1115
+            $value = $this->configuration->$option;
1116
+            if(empty($value)) {
1117
+                return false;
1118
+            }
1119
+        }
1120
+        return true;
1121
+    }
1122
+
1123
+    /**
1124
+     * does a cumulativeSearch on LDAP to get different values of a
1125
+     * specified attribute
1126
+     * @param string[] $filters array, the filters that shall be used in the search
1127
+     * @param string $attr the attribute of which a list of values shall be returned
1128
+     * @param int $dnReadLimit the amount of how many DNs should be analyzed.
1129
+     * The lower, the faster
1130
+     * @param string $maxF string. if not null, this variable will have the filter that
1131
+     * yields most result entries
1132
+     * @return array|false an array with the values on success, false otherwise
1133
+     */
1134
+    public function cumulativeSearchOnAttribute($filters, $attr, $dnReadLimit = 3, &$maxF = null) {
1135
+        $dnRead = array();
1136
+        $foundItems = array();
1137
+        $maxEntries = 0;
1138
+        if(!is_array($this->configuration->ldapBase)
1139
+           || !isset($this->configuration->ldapBase[0])) {
1140
+            return false;
1141
+        }
1142
+        $base = $this->configuration->ldapBase[0];
1143
+        $cr = $this->getConnection();
1144
+        if(!$this->ldap->isResource($cr)) {
1145
+            return false;
1146
+        }
1147
+        $lastFilter = null;
1148
+        if(isset($filters[count($filters)-1])) {
1149
+            $lastFilter = $filters[count($filters)-1];
1150
+        }
1151
+        foreach($filters as $filter) {
1152
+            if($lastFilter === $filter && count($foundItems) > 0) {
1153
+                //skip when the filter is a wildcard and results were found
1154
+                continue;
1155
+            }
1156
+            // 20k limit for performance and reason
1157
+            $rr = $this->ldap->search($cr, $base, $filter, array($attr), 0, 20000);
1158
+            if(!$this->ldap->isResource($rr)) {
1159
+                continue;
1160
+            }
1161
+            $entries = $this->ldap->countEntries($cr, $rr);
1162
+            $getEntryFunc = 'firstEntry';
1163
+            if(($entries !== false) && ($entries > 0)) {
1164
+                if(!is_null($maxF) && $entries > $maxEntries) {
1165
+                    $maxEntries = $entries;
1166
+                    $maxF = $filter;
1167
+                }
1168
+                $dnReadCount = 0;
1169
+                do {
1170
+                    $entry = $this->ldap->$getEntryFunc($cr, $rr);
1171
+                    $getEntryFunc = 'nextEntry';
1172
+                    if(!$this->ldap->isResource($entry)) {
1173
+                        continue 2;
1174
+                    }
1175
+                    $rr = $entry; //will be expected by nextEntry next round
1176
+                    $attributes = $this->ldap->getAttributes($cr, $entry);
1177
+                    $dn = $this->ldap->getDN($cr, $entry);
1178
+                    if($dn === false || in_array($dn, $dnRead)) {
1179
+                        continue;
1180
+                    }
1181
+                    $newItems = array();
1182
+                    $state = $this->getAttributeValuesFromEntry($attributes,
1183
+                                                                $attr,
1184
+                                                                $newItems);
1185
+                    $dnReadCount++;
1186
+                    $foundItems = array_merge($foundItems, $newItems);
1187
+                    $this->resultCache[$dn][$attr] = $newItems;
1188
+                    $dnRead[] = $dn;
1189
+                } while(($state === self::LRESULT_PROCESSED_SKIP
1190
+                        || $this->ldap->isResource($entry))
1191
+                        && ($dnReadLimit === 0 || $dnReadCount < $dnReadLimit));
1192
+            }
1193
+        }
1194
+
1195
+        return array_unique($foundItems);
1196
+    }
1197
+
1198
+    /**
1199
+     * determines if and which $attr are available on the LDAP server
1200
+     * @param string[] $objectclasses the objectclasses to use as search filter
1201
+     * @param string $attr the attribute to look for
1202
+     * @param string $dbkey the dbkey of the setting the feature is connected to
1203
+     * @param string $confkey the confkey counterpart for the $dbkey as used in the
1204
+     * Configuration class
1205
+     * @param bool $po whether the objectClass with most result entries
1206
+     * shall be pre-selected via the result
1207
+     * @return array|false list of found items.
1208
+     * @throws \Exception
1209
+     */
1210
+    private function determineFeature($objectclasses, $attr, $dbkey, $confkey, $po = false) {
1211
+        $cr = $this->getConnection();
1212
+        if(!$cr) {
1213
+            throw new \Exception('Could not connect to LDAP');
1214
+        }
1215
+        $p = 'objectclass=';
1216
+        foreach($objectclasses as $key => $value) {
1217
+            $objectclasses[$key] = $p.$value;
1218
+        }
1219
+        $maxEntryObjC = '';
1220
+
1221
+        //how deep to dig?
1222
+        //When looking for objectclasses, testing few entries is sufficient,
1223
+        $dig = 3;
1224
+
1225
+        $availableFeatures =
1226
+            $this->cumulativeSearchOnAttribute($objectclasses, $attr,
1227
+                                                $dig, $maxEntryObjC);
1228
+        if(is_array($availableFeatures)
1229
+           && count($availableFeatures) > 0) {
1230
+            natcasesort($availableFeatures);
1231
+            //natcasesort keeps indices, but we must get rid of them for proper
1232
+            //sorting in the web UI. Therefore: array_values
1233
+            $this->result->addOptions($dbkey, array_values($availableFeatures));
1234
+        } else {
1235
+            throw new \Exception(self::$l->t('Could not find the desired feature'));
1236
+        }
1237
+
1238
+        $setFeatures = $this->configuration->$confkey;
1239
+        if(is_array($setFeatures) && !empty($setFeatures)) {
1240
+            //something is already configured? pre-select it.
1241
+            $this->result->addChange($dbkey, $setFeatures);
1242
+        } else if ($po && $maxEntryObjC !== '') {
1243
+            //pre-select objectclass with most result entries
1244
+            $maxEntryObjC = str_replace($p, '', $maxEntryObjC);
1245
+            $this->applyFind($dbkey, $maxEntryObjC);
1246
+            $this->result->addChange($dbkey, $maxEntryObjC);
1247
+        }
1248
+
1249
+        return $availableFeatures;
1250
+    }
1251
+
1252
+    /**
1253
+     * appends a list of values fr
1254
+     * @param resource $result the return value from ldap_get_attributes
1255
+     * @param string $attribute the attribute values to look for
1256
+     * @param array &$known new values will be appended here
1257
+     * @return int, state on of the class constants LRESULT_PROCESSED_OK,
1258
+     * LRESULT_PROCESSED_INVALID or LRESULT_PROCESSED_SKIP
1259
+     */
1260
+    private function getAttributeValuesFromEntry($result, $attribute, &$known) {
1261
+        if(!is_array($result)
1262
+           || !isset($result['count'])
1263
+           || !$result['count'] > 0) {
1264
+            return self::LRESULT_PROCESSED_INVALID;
1265
+        }
1266
+
1267
+        // strtolower on all keys for proper comparison
1268
+        $result = \OCP\Util::mb_array_change_key_case($result);
1269
+        $attribute = strtolower($attribute);
1270
+        if(isset($result[$attribute])) {
1271
+            foreach($result[$attribute] as $key => $val) {
1272
+                if($key === 'count') {
1273
+                    continue;
1274
+                }
1275
+                if(!in_array($val, $known)) {
1276
+                    $known[] = $val;
1277
+                }
1278
+            }
1279
+            return self::LRESULT_PROCESSED_OK;
1280
+        } else {
1281
+            return self::LRESULT_PROCESSED_SKIP;
1282
+        }
1283
+    }
1284
+
1285
+    /**
1286
+     * @return bool|mixed
1287
+     */
1288
+    private function getConnection() {
1289
+        if(!is_null($this->cr)) {
1290
+            return $this->cr;
1291
+        }
1292
+
1293
+        $cr = $this->ldap->connect(
1294
+            $this->configuration->ldapHost,
1295
+            $this->configuration->ldapPort
1296
+        );
1297
+
1298
+        $this->ldap->setOption($cr, LDAP_OPT_PROTOCOL_VERSION, 3);
1299
+        $this->ldap->setOption($cr, LDAP_OPT_REFERRALS, 0);
1300
+        $this->ldap->setOption($cr, LDAP_OPT_NETWORK_TIMEOUT, self::LDAP_NW_TIMEOUT);
1301
+        if($this->configuration->ldapTLS === 1) {
1302
+            $this->ldap->startTls($cr);
1303
+        }
1304
+
1305
+        $lo = @$this->ldap->bind($cr,
1306
+                                    $this->configuration->ldapAgentName,
1307
+                                    $this->configuration->ldapAgentPassword);
1308
+        if($lo === true) {
1309
+            $this->$cr = $cr;
1310
+            return $cr;
1311
+        }
1312
+
1313
+        return false;
1314
+    }
1315
+
1316
+    /**
1317
+     * @return array
1318
+     */
1319
+    private function getDefaultLdapPortSettings() {
1320
+        static $settings = array(
1321
+                                array('port' => 7636, 'tls' => false),
1322
+                                array('port' =>  636, 'tls' => false),
1323
+                                array('port' => 7389, 'tls' => true),
1324
+                                array('port' =>  389, 'tls' => true),
1325
+                                array('port' => 7389, 'tls' => false),
1326
+                                array('port' =>  389, 'tls' => false),
1327
+                            );
1328
+        return $settings;
1329
+    }
1330
+
1331
+    /**
1332
+     * @return array
1333
+     */
1334
+    private function getPortSettingsToTry() {
1335
+        //389 ← LDAP / Unencrypted or StartTLS
1336
+        //636 ← LDAPS / SSL
1337
+        //7xxx ← UCS. need to be checked first, because both ports may be open
1338
+        $host = $this->configuration->ldapHost;
1339
+        $port = (int)$this->configuration->ldapPort;
1340
+        $portSettings = array();
1341
+
1342
+        //In case the port is already provided, we will check this first
1343
+        if($port > 0) {
1344
+            $hostInfo = parse_url($host);
1345
+            if(!(is_array($hostInfo)
1346
+                && isset($hostInfo['scheme'])
1347
+                && stripos($hostInfo['scheme'], 'ldaps') !== false)) {
1348
+                $portSettings[] = array('port' => $port, 'tls' => true);
1349
+            }
1350
+            $portSettings[] =array('port' => $port, 'tls' => false);
1351
+        }
1352
+
1353
+        //default ports
1354
+        $portSettings = array_merge($portSettings,
1355
+                                    $this->getDefaultLdapPortSettings());
1356
+
1357
+        return $portSettings;
1358
+    }
1359 1359
 
1360 1360
 
1361 1361
 }
Please login to merge, or discard this patch.