Completed
Pull Request — master (#9293)
by Blizzz
20:13
created
apps/files_trashbin/ajax/delete.php 1 patch
Indentation   +40 added lines, -40 removed lines patch added patch discarded remove patch
@@ -35,19 +35,19 @@  discard block
 block discarded – undo
35 35
 
36 36
 // "empty trash" command
37 37
 if (isset($_POST['allfiles']) && (string)$_POST['allfiles'] === 'true'){
38
-	$deleteAll = true;
39
-	if ($folder === '/' || $folder === '') {
40
-		OCA\Files_Trashbin\Trashbin::deleteAll();
41
-		$list = array();
42
-	} else {
43
-		$list[] = $folder;
44
-		$folder = dirname($folder);
45
-	}
38
+    $deleteAll = true;
39
+    if ($folder === '/' || $folder === '') {
40
+        OCA\Files_Trashbin\Trashbin::deleteAll();
41
+        $list = array();
42
+    } else {
43
+        $list[] = $folder;
44
+        $folder = dirname($folder);
45
+    }
46 46
 }
47 47
 else {
48
-	$deleteAll = false;
49
-	$files = (string)$_POST['files'];
50
-	$list = json_decode($files);
48
+    $deleteAll = false;
49
+    $files = (string)$_POST['files'];
50
+    $list = json_decode($files);
51 51
 }
52 52
 
53 53
 $folder = rtrim($folder, '/') . '/';
@@ -56,38 +56,38 @@  discard block
 block discarded – undo
56 56
 
57 57
 $i = 0;
58 58
 foreach ($list as $file) {
59
-	if ($folder === '/') {
60
-		$file = ltrim($file, '/');
61
-		$delimiter = strrpos($file, '.d');
62
-		$filename = substr($file, 0, $delimiter);
63
-		$timestamp =  substr($file, $delimiter+2);
64
-	} else {
65
-		$filename = $folder . '/' . $file;
66
-		$timestamp = null;
67
-	}
59
+    if ($folder === '/') {
60
+        $file = ltrim($file, '/');
61
+        $delimiter = strrpos($file, '.d');
62
+        $filename = substr($file, 0, $delimiter);
63
+        $timestamp =  substr($file, $delimiter+2);
64
+    } else {
65
+        $filename = $folder . '/' . $file;
66
+        $timestamp = null;
67
+    }
68 68
 
69
-	OCA\Files_Trashbin\Trashbin::delete($filename, \OCP\User::getUser(), $timestamp);
70
-	if (OCA\Files_Trashbin\Trashbin::file_exists($filename, $timestamp)) {
71
-		$error[] = $filename;
72
-		\OCP\Util::writeLog('trashbin','can\'t delete ' . $filename . ' permanently.', ILogger::ERROR);
73
-	}
74
-	// only list deleted files if not deleting everything
75
-	else if (!$deleteAll) {
76
-		$success[$i]['filename'] = $file;
77
-		$success[$i]['timestamp'] = $timestamp;
78
-		$i++;
79
-	}
69
+    OCA\Files_Trashbin\Trashbin::delete($filename, \OCP\User::getUser(), $timestamp);
70
+    if (OCA\Files_Trashbin\Trashbin::file_exists($filename, $timestamp)) {
71
+        $error[] = $filename;
72
+        \OCP\Util::writeLog('trashbin','can\'t delete ' . $filename . ' permanently.', ILogger::ERROR);
73
+    }
74
+    // only list deleted files if not deleting everything
75
+    else if (!$deleteAll) {
76
+        $success[$i]['filename'] = $file;
77
+        $success[$i]['timestamp'] = $timestamp;
78
+        $i++;
79
+    }
80 80
 }
81 81
 
82 82
 if ( $error ) {
83
-	$filelist = '';
84
-	foreach ( $error as $e ) {
85
-		$filelist .= $e.', ';
86
-	}
87
-	$l = \OC::$server->getL10N('files_trashbin');
88
-	$message = $l->t("Couldn't delete %s permanently", array(rtrim($filelist, ', ')));
89
-	\OC_JSON::error(array("data" => array("message" => $message,
90
-			                               "success" => $success, "error" => $error)));
83
+    $filelist = '';
84
+    foreach ( $error as $e ) {
85
+        $filelist .= $e.', ';
86
+    }
87
+    $l = \OC::$server->getL10N('files_trashbin');
88
+    $message = $l->t("Couldn't delete %s permanently", array(rtrim($filelist, ', ')));
89
+    \OC_JSON::error(array("data" => array("message" => $message,
90
+                                            "success" => $success, "error" => $error)));
91 91
 } else {
92
-	\OC_JSON::success(array("data" => array("success" => $success)));
92
+    \OC_JSON::success(array("data" => array("success" => $success)));
93 93
 }
Please login to merge, or discard this patch.
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.