userscontroller.php 13 KB
Newer Older
1
2
3
<?php
/**
 * @author Lukas Reschke
Lukas Reschke's avatar
Lukas Reschke committed
4
 * @copyright 2014-2015 Lukas Reschke lukas@owncloud.com
5
6
7
8
9
10
11
12
 *
 * This file is licensed under the Affero General Public License version 3 or
 * later.
 * See the COPYING-README file.
 */

namespace OC\Settings\Controller;

Lukas Reschke's avatar
Lukas Reschke committed
13
use OC\AppFramework\Http;
14
use OC\Settings\Factory\SubAdminFactory;
15
use OC\User\User;
16
17
use OCP\App\IAppManager;
use OCP\AppFramework\Controller;
18
use OCP\AppFramework\Http\DataResponse;
Morris Jobke's avatar
Morris Jobke committed
19
use OCP\AppFramework\Http\TemplateResponse;
20
21
22
use OCP\IConfig;
use OCP\IGroupManager;
use OCP\IL10N;
Morris Jobke's avatar
Morris Jobke committed
23
use OCP\ILogger;
24
use OCP\IRequest;
Morris Jobke's avatar
Morris Jobke committed
25
use OCP\IURLGenerator;
26
use OCP\IUser;
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
use OCP\IUserManager;
use OCP\IUserSession;

/**
 * @package OC\Settings\Controller
 */
class UsersController extends Controller {
	/** @var IL10N */
	private $l10n;
	/** @var IUserSession */
	private $userSession;
	/** @var bool */
	private $isAdmin;
	/** @var IUserManager */
	private $userManager;
	/** @var IGroupManager */
	private $groupManager;
	/** @var IConfig */
	private $config;
Morris Jobke's avatar
Morris Jobke committed
46
47
48
49
50
51
52
53
54
55
	/** @var ILogger */
	private $log;
	/** @var \OC_Defaults */
	private $defaults;
	/** @var \OC_Mail */
	private $mail;
	/** @var string */
	private $fromMailAddress;
	/** @var IURLGenerator */
	private $urlGenerator;
56
57
58
59
	/** @var bool contains the state of the encryption app */
	private $isEncryptionAppEnabled;
	/** @var bool contains the state of the admin recovery setting */
	private $isRestoreEnabled = false;
60
61
	/** @var SubAdminFactory */
	private $subAdminFactory;
62
63
64
65
66
67
68
69
70
71

	/**
	 * @param string $appName
	 * @param IRequest $request
	 * @param IUserManager $userManager
	 * @param IGroupManager $groupManager
	 * @param IUserSession $userSession
	 * @param IConfig $config
	 * @param bool $isAdmin
	 * @param IL10N $l10n
Morris Jobke's avatar
Morris Jobke committed
72
73
74
75
	 * @param ILogger $log
	 * @param \OC_Defaults $defaults
	 * @param \OC_Mail $mail
	 * @param string $fromMailAddress
Lukas Reschke's avatar
Lukas Reschke committed
76
	 * @param IURLGenerator $urlGenerator
77
	 * @param IAppManager $appManager
78
	 * @param SubAdminFactory $subAdminFactory
79
80
81
82
83
84
85
86
	 */
	public function __construct($appName,
								IRequest $request,
								IUserManager $userManager,
								IGroupManager $groupManager,
								IUserSession $userSession,
								IConfig $config,
								$isAdmin,
Morris Jobke's avatar
Morris Jobke committed
87
88
89
90
91
								IL10N $l10n,
								ILogger $log,
								\OC_Defaults $defaults,
								\OC_Mail $mail,
								$fromMailAddress,
92
								IURLGenerator $urlGenerator,
Lukas Reschke's avatar
Lukas Reschke committed
93
								IAppManager $appManager,
94
								SubAdminFactory $subAdminFactory) {
95
96
97
98
99
100
101
		parent::__construct($appName, $request);
		$this->userManager = $userManager;
		$this->groupManager = $groupManager;
		$this->userSession = $userSession;
		$this->config = $config;
		$this->isAdmin = $isAdmin;
		$this->l10n = $l10n;
Morris Jobke's avatar
Morris Jobke committed
102
103
104
105
106
		$this->log = $log;
		$this->defaults = $defaults;
		$this->mail = $mail;
		$this->fromMailAddress = $fromMailAddress;
		$this->urlGenerator = $urlGenerator;
107
		$this->subAdminFactory = $subAdminFactory;
108
109
110
111
112
113
114
115

		// check for encryption state - TODO see formatUserForIndex
		$this->isEncryptionAppEnabled = $appManager->isEnabledForUser('files_encryption');
		if($this->isEncryptionAppEnabled) {
			// putting this directly in empty is possible in PHP 5.5+
			$result = $config->getAppValue('files_encryption', 'recoveryAdminEnabled', 0);
			$this->isRestoreEnabled = !empty($result);
		}
116
117
	}

118
119
120
121
122
123
	/**
	 * @param IUser $user
	 * @param array $userGroups
	 * @return array
	 */
	private function formatUserForIndex(IUser $user, array $userGroups = null) {
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150

		// TODO: eliminate this encryption specific code below and somehow
		// hook in additional user info from other apps

		// recovery isn't possible if admin or user has it disabled and encryption
		// is enabled - so we eliminate the else paths in the conditional tree
		// below
		$restorePossible = false;

		if ($this->isEncryptionAppEnabled) {
			if ($this->isRestoreEnabled) {
				// check for the users recovery setting
				$recoveryMode = $this->config->getUserValue($user->getUID(), 'files_encryption', 'recovery_enabled', '0');
				// method call inside empty is possible with PHP 5.5+
				$recoveryModeEnabled = !empty($recoveryMode);
				if ($recoveryModeEnabled) {
					// user also has recovery mode enabled
					$restorePossible = true;
				}
			}
		} else {
			// recovery is possible if encryption is disabled (plain files are
			// available)
			$restorePossible = true;
		}

		return [
151
152
153
154
155
156
			'name' => $user->getUID(),
			'displayname' => $user->getDisplayName(),
			'groups' => (empty($userGroups)) ? $this->groupManager->getUserGroupIds($user) : $userGroups,
			'subadmin' => \OC_SubAdmin::getSubAdminsGroups($user->getUID()),
			'quota' => $this->config->getUserValue($user->getUID(), 'files', 'quota', 'default'),
			'storageLocation' => $user->getHome(),
157
			'lastLogin' => $user->getLastLogin() * 1000,
158
			'backend' => $user->getBackendClassName(),
159
160
161
			'email' => $this->config->getUserValue($user->getUID(), 'settings', 'email', ''),
			'isRestoreDisabled' => !$restorePossible,
		];
162
163
	}

164
	/**
Lukas Reschke's avatar
Lukas Reschke committed
165
	 * @param array $userIDs Array with schema [$uid => $displayName]
166
167
168
169
	 * @return IUser[]
	 */
	private function getUsersForUID(array $userIDs) {
		$users = [];
Lukas Reschke's avatar
Lukas Reschke committed
170
		foreach ($userIDs as $uid => $displayName) {
Lukas Reschke's avatar
Lukas Reschke committed
171
			$users[$uid] = $this->userManager->get($uid);
172
173
174
175
		}
		return $users;
	}

176
177
	/**
	 * @NoAdminRequired
178
	 *
179
180
	 * @param int $offset
	 * @param int $limit
181
182
183
	 * @param string $gid GID to filter for
	 * @param string $pattern Pattern to search for in the username
	 * @param string $backend Backend to filter for (class-name)
184
185
186
187
	 * @return DataResponse
	 *
	 * TODO: Tidy up and write unit tests - code is mainly static method calls
	 */
188
	public function index($offset = 0, $limit = 10, $gid = '', $pattern = '', $backend = '') {
189
190
191
192
		// FIXME: The JS sends the group '_everyone' instead of no GID for the "all users" group.
		if($gid === '_everyone') {
			$gid = '';
		}
193
194
195
196
197
198
199
200

		// Remove backends
		if(!empty($backend)) {
			$activeBackends = $this->userManager->getBackends();
			$this->userManager->clearBackends();
			foreach($activeBackends as $singleActiveBackend) {
				if($backend === get_class($singleActiveBackend)) {
					$this->userManager->registerBackend($singleActiveBackend);
Lukas Reschke's avatar
Lukas Reschke committed
201
					break;
202
203
204
205
				}
			}
		}

Lukas Reschke's avatar
Lukas Reschke committed
206
		$users = [];
207
		if ($this->isAdmin) {
208

209
			if($gid !== '') {
210
				$batch = $this->getUsersForUID($this->groupManager->displayNamesInGroup($gid, $pattern, $limit, $offset));
211
			} else {
212
				$batch = $this->userManager->search($pattern, $limit, $offset);
213
214
			}

215
216
			foreach ($batch as $user) {
				$users[] = $this->formatUserForIndex($user);
217
			}
218

219
		} else {
220
221
222
			$subAdminOfGroups = $this->subAdminFactory->getSubAdminsOfGroups(
				$this->userSession->getUser()->getUID()
			);
Lukas Reschke's avatar
Lukas Reschke committed
223
			// Set the $gid parameter to an empty value if the subadmin has no rights to access a specific group
224
			if($gid !== '' && !in_array($gid, $subAdminOfGroups)) {
225
				$gid = '';
226
227
			}

Lukas Reschke's avatar
Lukas Reschke committed
228
229
230
			// Batch all groups the user is subadmin of when a group is specified
			$batch = [];
			if($gid === '') {
231
				foreach($subAdminOfGroups as $group) {
Lukas Reschke's avatar
Lukas Reschke committed
232
233
234
235
236
237
238
239
240
241
					$groupUsers = $this->groupManager->displayNamesInGroup($group, $pattern, $limit, $offset);
					foreach($groupUsers as $uid => $displayName) {
						$batch[$uid] = $displayName;
					}
				}
			} else {
				$batch = $this->groupManager->displayNamesInGroup($gid, $pattern, $limit, $offset);
			}
			$batch = $this->getUsersForUID($batch);

242
			foreach ($batch as $user) {
243
				// Only add the groups, this user is a subadmin of
Lukas Reschke's avatar
Lukas Reschke committed
244
245
				$userGroups = array_values(array_intersect(
					$this->groupManager->getUserGroupIds($user),
246
					$subAdminOfGroups
Lukas Reschke's avatar
Lukas Reschke committed
247
				));
248
				$users[] = $this->formatUserForIndex($user, $userGroups);
249
250
251
			}
		}

252
		return new DataResponse($users);
253
254
255
256
257
258
259
260
	}

	/**
	 * @NoAdminRequired
	 *
	 * @param string $username
	 * @param string $password
	 * @param array $groups
Morris Jobke's avatar
Morris Jobke committed
261
	 * @param string $email
262
263
	 * @return DataResponse
	 */
Morris Jobke's avatar
Morris Jobke committed
264
	public function create($username, $password, array $groups=array(), $email='') {
265

Morris Jobke's avatar
Morris Jobke committed
266
267
268
269
270
271
272
273
274
		if($email !== '' && !$this->mail->validateAddress($email)) {
			return new DataResponse(
				array(
					'message' => (string)$this->l10n->t('Invalid mail address')
				),
				Http::STATUS_UNPROCESSABLE_ENTITY
			);
		}

275
		if (!$this->isAdmin) {
Lukas Reschke's avatar
Lukas Reschke committed
276
			$userId = $this->userSession->getUser()->getUID();
277
278
			if (!empty($groups)) {
				foreach ($groups as $key => $group) {
Lukas Reschke's avatar
Lukas Reschke committed
279
					if (!$this->subAdminFactory->isGroupAccessible($userId, $group)) {
280
281
282
283
284
						unset($groups[$key]);
					}
				}
			}
			if (empty($groups)) {
Lukas Reschke's avatar
Lukas Reschke committed
285
				$groups = $this->subAdminFactory->getSubAdminsOfGroups($userId);
286
287
288
289
290
291
292
293
			}
		}

		try {
			$user = $this->userManager->createUser($username, $password);
		} catch (\Exception $exception) {
			return new DataResponse(
				array(
294
					'message' => (string)$this->l10n->t('Unable to create user.')
Lukas Reschke's avatar
Lukas Reschke committed
295
296
				),
				Http::STATUS_FORBIDDEN
297
298
299
300
			);
		}

		if($user instanceof User) {
Morris Jobke's avatar
Morris Jobke committed
301
			if($groups !== null) {
302
				foreach($groups as $groupName) {
Morris Jobke's avatar
Morris Jobke committed
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
					$group = $this->groupManager->get($groupName);

					if(empty($group)) {
						$group = $this->groupManager->createGroup($groupName);
					}
					$group->addUser($user);
				}
			}
			/**
			 * Send new user mail only if a mail is set
			 */
			if($email !== '') {
				$this->config->setUserValue($username, 'settings', 'email', $email);

				// data for the mail template
				$mailData = array(
					'username' => $username,
					'url' => $this->urlGenerator->getAbsoluteURL('/')
				);

				$mail = new TemplateResponse('settings', 'email.new_user', $mailData, 'blank');
				$mailContent = $mail->render();

				$mail = new TemplateResponse('settings', 'email.new_user_plain_text', $mailData, 'blank');
				$plainTextMailContent = $mail->render();

				$subject = $this->l10n->t('Your %s account was created', [$this->defaults->getName()]);
330

Morris Jobke's avatar
Morris Jobke committed
331
332
333
334
335
336
337
338
339
340
341
342
				try {
					$this->mail->send(
						$email,
						$username,
						$subject,
						$mailContent,
						$this->fromMailAddress,
						$this->defaults->getName(),
						1,
						$plainTextMailContent);
				} catch(\Exception $e) {
					$this->log->error("Can't send new user mail to $email: " . $e->getMessage(), array('app' => 'settings'));
343
344
				}
			}
345
346
347
348
349
350
351
			// fetch users groups
			$userGroups = $this->groupManager->getUserGroupIds($user);

			return new DataResponse(
				$this->formatUserForIndex($user, $userGroups),
				Http::STATUS_CREATED
			);
352
353
354
355
		}

		return new DataResponse(
			array(
356
				'message' => (string)$this->l10n->t('Unable to create user.')
Lukas Reschke's avatar
Lukas Reschke committed
357
			),
358
			Http::STATUS_FORBIDDEN
359
360
361
362
363
364
365
366
367
368
369
		);

	}

	/**
	 * @NoAdminRequired
	 *
	 * @param string $id
	 * @return DataResponse
	 */
	public function destroy($id) {
Lukas Reschke's avatar
Lukas Reschke committed
370
371
		$userId = $this->userSession->getUser()->getUID();
		if($userId === $id) {
372
373
374
375
376
377
			return new DataResponse(
				array(
					'status' => 'error',
					'data' => array(
						'message' => (string)$this->l10n->t('Unable to delete user.')
					)
Lukas Reschke's avatar
Lukas Reschke committed
378
379
				),
				Http::STATUS_FORBIDDEN
380
381
382
			);
		}

Lukas Reschke's avatar
Lukas Reschke committed
383
		if(!$this->isAdmin && !$this->subAdminFactory->isUserAccessible($userId, $id)) {
384
385
386
387
			return new DataResponse(
				array(
					'status' => 'error',
					'data' => array(
Lukas Reschke's avatar
Lukas Reschke committed
388
389
390
391
						'message' => (string)$this->l10n->t('Authentication error')
					)
				),
				Http::STATUS_FORBIDDEN
392
393
394
395
396
397
398
399
400
401
402
403
			);
		}

		$user = $this->userManager->get($id);
		if($user) {
			if($user->delete()) {
				return new DataResponse(
					array(
						'status' => 'success',
						'data' => array(
							'username' => $id
						)
Lukas Reschke's avatar
Lukas Reschke committed
404
405
					),
					Http::STATUS_NO_CONTENT
406
407
408
409
410
411
412
413
414
415
				);
			}
		}

		return new DataResponse(
			array(
				'status' => 'error',
				'data' => array(
					'message' => (string)$this->l10n->t('Unable to delete user.')
				)
Lukas Reschke's avatar
Lukas Reschke committed
416
417
			),
			Http::STATUS_FORBIDDEN
418
419
420
		);
	}

421
422
423
424
425
426
427
428
429
430
431
	/**
	 * Set the mail address of a user
	 *
	 * @NoAdminRequired
	 * @NoSubadminRequired
	 *
	 * @param string $id
	 * @param string $mailAddress
	 * @return DataResponse
	 */
	public function setMailAddress($id, $mailAddress) {
Lukas Reschke's avatar
Lukas Reschke committed
432
433
		$userId = $this->userSession->getUser()->getUID();
		if($userId !== $id
434
			&& !$this->isAdmin
Lukas Reschke's avatar
Lukas Reschke committed
435
			&& !$this->subAdminFactory->isUserAccessible($userId, $id)) {
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
			return new DataResponse(
				array(
					'status' => 'error',
					'data' => array(
						'message' => (string)$this->l10n->t('Forbidden')
					)
				),
				Http::STATUS_FORBIDDEN
			);
		}

		if($mailAddress !== '' && !$this->mail->validateAddress($mailAddress)) {
			return new DataResponse(
				array(
					'status' => 'error',
					'data' => array(
						'message' => (string)$this->l10n->t('Invalid mail address')
					)
				),
				Http::STATUS_UNPROCESSABLE_ENTITY
			);
		}

		$user = $this->userManager->get($id);
		if(!$user){
			return new DataResponse(
				array(
					'status' => 'error',
					'data' => array(
						'message' => (string)$this->l10n->t('Invalid user')
					)
				),
				Http::STATUS_UNPROCESSABLE_ENTITY
			);
		}

		// this is the only permission a backend provides and is also used
		// for the permission of setting a email address
		if(!$user->canChangeDisplayName()){
			return new DataResponse(
				array(
					'status' => 'error',
					'data' => array(
						'message' => (string)$this->l10n->t('Unable to change mail address')
					)
				),
				Http::STATUS_FORBIDDEN
			);
		}

		$this->config->setUserValue($id, 'settings', 'email', $mailAddress);

		return new DataResponse(
			array(
				'status' => 'success',
				'data' => array(
					'username' => $id,
					'mailAddress' => $mailAddress,
					'message' => (string)$this->l10n->t('Email saved')
				)
			),
			Http::STATUS_OK
		);
	}

501
}