You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

UsersController.php 32KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * @copyright Copyright (c) 2016, ownCloud, Inc.
  5. *
  6. * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
  7. * @author Bjoern Schiessle <bjoern@schiessle.org>
  8. * @author Christoph Wurst <christoph@winzerhof-wurst.at>
  9. * @author Daniel Calviño Sánchez <danxuliu@gmail.com>
  10. * @author Daniel Kesselberg <mail@danielkesselberg.de>
  11. * @author Joas Schilling <coding@schilljs.com>
  12. * @author John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
  13. * @author Julius Härtl <jus@bitgrid.net>
  14. * @author Lukas Reschke <lukas@statuscode.ch>
  15. * @author michag86 <micha_g@arcor.de>
  16. * @author Mikael Hammarin <mikael@try2.se>
  17. * @author Morris Jobke <hey@morrisjobke.de>
  18. * @author Robin Appelman <robin@icewind.nl>
  19. * @author Roeland Jago Douma <roeland@famdouma.nl>
  20. * @author Sujith Haridasan <sujith.h@gmail.com>
  21. * @author Thomas Citharel <nextcloud@tcit.fr>
  22. * @author Thomas Müller <thomas.mueller@tmit.eu>
  23. * @author Tom Needham <tom@owncloud.com>
  24. *
  25. * @license AGPL-3.0
  26. *
  27. * This code is free software: you can redistribute it and/or modify
  28. * it under the terms of the GNU Affero General Public License, version 3,
  29. * as published by the Free Software Foundation.
  30. *
  31. * This program is distributed in the hope that it will be useful,
  32. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  33. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  34. * GNU Affero General Public License for more details.
  35. *
  36. * You should have received a copy of the GNU Affero General Public License, version 3,
  37. * along with this program. If not, see <http://www.gnu.org/licenses/>
  38. *
  39. */
  40. namespace OCA\Provisioning_API\Controller;
  41. use libphonenumber\NumberParseException;
  42. use libphonenumber\PhoneNumber;
  43. use libphonenumber\PhoneNumberFormat;
  44. use libphonenumber\PhoneNumberUtil;
  45. use OC\Accounts\AccountManager;
  46. use OC\Authentication\Token\RemoteWipe;
  47. use OC\HintException;
  48. use OCA\Provisioning_API\FederatedShareProviderFactory;
  49. use OCA\Settings\Mailer\NewUserMailHelper;
  50. use OCP\Accounts\IAccountManager;
  51. use OCP\App\IAppManager;
  52. use OCP\AppFramework\Http;
  53. use OCP\AppFramework\Http\DataResponse;
  54. use OCP\AppFramework\OCS\OCSException;
  55. use OCP\AppFramework\OCS\OCSForbiddenException;
  56. use OCP\AppFramework\OCSController;
  57. use OCP\IConfig;
  58. use OCP\IGroup;
  59. use OCP\IGroupManager;
  60. use OCP\IRequest;
  61. use OCP\IURLGenerator;
  62. use OCP\IUser;
  63. use OCP\IUserManager;
  64. use OCP\IUserSession;
  65. use OCP\L10N\IFactory;
  66. use OCP\Security\ISecureRandom;
  67. use OCP\Security\Events\GenerateSecurePasswordEvent;
  68. use OCP\EventDispatcher\IEventDispatcher;
  69. use Psr\Log\LoggerInterface;
  70. class UsersController extends AUserData {
  71. /** @var IAppManager */
  72. private $appManager;
  73. /** @var IURLGenerator */
  74. protected $urlGenerator;
  75. /** @var LoggerInterface */
  76. private $logger;
  77. /** @var IFactory */
  78. protected $l10nFactory;
  79. /** @var NewUserMailHelper */
  80. private $newUserMailHelper;
  81. /** @var FederatedShareProviderFactory */
  82. private $federatedShareProviderFactory;
  83. /** @var ISecureRandom */
  84. private $secureRandom;
  85. /** @var RemoteWipe */
  86. private $remoteWipe;
  87. /** @var IEventDispatcher */
  88. private $eventDispatcher;
  89. public function __construct(string $appName,
  90. IRequest $request,
  91. IUserManager $userManager,
  92. IConfig $config,
  93. IAppManager $appManager,
  94. IGroupManager $groupManager,
  95. IUserSession $userSession,
  96. AccountManager $accountManager,
  97. IURLGenerator $urlGenerator,
  98. LoggerInterface $logger,
  99. IFactory $l10nFactory,
  100. NewUserMailHelper $newUserMailHelper,
  101. FederatedShareProviderFactory $federatedShareProviderFactory,
  102. ISecureRandom $secureRandom,
  103. RemoteWipe $remoteWipe,
  104. IEventDispatcher $eventDispatcher) {
  105. parent::__construct($appName,
  106. $request,
  107. $userManager,
  108. $config,
  109. $groupManager,
  110. $userSession,
  111. $accountManager,
  112. $l10nFactory);
  113. $this->appManager = $appManager;
  114. $this->urlGenerator = $urlGenerator;
  115. $this->logger = $logger;
  116. $this->l10nFactory = $l10nFactory;
  117. $this->newUserMailHelper = $newUserMailHelper;
  118. $this->federatedShareProviderFactory = $federatedShareProviderFactory;
  119. $this->secureRandom = $secureRandom;
  120. $this->remoteWipe = $remoteWipe;
  121. $this->eventDispatcher = $eventDispatcher;
  122. }
  123. /**
  124. * @NoAdminRequired
  125. *
  126. * returns a list of users
  127. *
  128. * @param string $search
  129. * @param int $limit
  130. * @param int $offset
  131. * @return DataResponse
  132. */
  133. public function getUsers(string $search = '', int $limit = null, int $offset = 0): DataResponse {
  134. $user = $this->userSession->getUser();
  135. $users = [];
  136. // Admin? Or SubAdmin?
  137. $uid = $user->getUID();
  138. $subAdminManager = $this->groupManager->getSubAdmin();
  139. if ($this->groupManager->isAdmin($uid)) {
  140. $users = $this->userManager->search($search, $limit, $offset);
  141. } elseif ($subAdminManager->isSubAdmin($user)) {
  142. $subAdminOfGroups = $subAdminManager->getSubAdminsGroups($user);
  143. foreach ($subAdminOfGroups as $key => $group) {
  144. $subAdminOfGroups[$key] = $group->getGID();
  145. }
  146. $users = [];
  147. foreach ($subAdminOfGroups as $group) {
  148. $users = array_merge($users, $this->groupManager->displayNamesInGroup($group, $search, $limit, $offset));
  149. }
  150. }
  151. $users = array_keys($users);
  152. return new DataResponse([
  153. 'users' => $users
  154. ]);
  155. }
  156. /**
  157. * @NoAdminRequired
  158. *
  159. * returns a list of users and their data
  160. */
  161. public function getUsersDetails(string $search = '', int $limit = null, int $offset = 0): DataResponse {
  162. $currentUser = $this->userSession->getUser();
  163. $users = [];
  164. // Admin? Or SubAdmin?
  165. $uid = $currentUser->getUID();
  166. $subAdminManager = $this->groupManager->getSubAdmin();
  167. if ($this->groupManager->isAdmin($uid)) {
  168. $users = $this->userManager->search($search, $limit, $offset);
  169. $users = array_keys($users);
  170. } elseif ($subAdminManager->isSubAdmin($currentUser)) {
  171. $subAdminOfGroups = $subAdminManager->getSubAdminsGroups($currentUser);
  172. foreach ($subAdminOfGroups as $key => $group) {
  173. $subAdminOfGroups[$key] = $group->getGID();
  174. }
  175. $users = [];
  176. foreach ($subAdminOfGroups as $group) {
  177. $users[] = array_keys($this->groupManager->displayNamesInGroup($group, $search, $limit, $offset));
  178. }
  179. $users = array_merge(...$users);
  180. }
  181. $usersDetails = [];
  182. foreach ($users as $userId) {
  183. $userId = (string) $userId;
  184. $userData = $this->getUserData($userId);
  185. // Do not insert empty entry
  186. if (!empty($userData)) {
  187. $usersDetails[$userId] = $userData;
  188. } else {
  189. // Logged user does not have permissions to see this user
  190. // only showing its id
  191. $usersDetails[$userId] = ['id' => $userId];
  192. }
  193. }
  194. return new DataResponse([
  195. 'users' => $usersDetails
  196. ]);
  197. }
  198. /**
  199. * @NoAdminRequired
  200. * @NoSubAdminRequired
  201. *
  202. * @param string $location
  203. * @param array $search
  204. * @return DataResponse
  205. */
  206. public function searchByPhoneNumbers(string $location, array $search): DataResponse {
  207. $phoneUtil = PhoneNumberUtil::getInstance();
  208. if ($phoneUtil->getCountryCodeForRegion($location) === 0) {
  209. // Not a valid region code
  210. return new DataResponse([], Http::STATUS_BAD_REQUEST);
  211. }
  212. $normalizedNumberToKey = [];
  213. foreach ($search as $key => $phoneNumbers) {
  214. foreach ($phoneNumbers as $phone) {
  215. try {
  216. $phoneNumber = $phoneUtil->parse($phone, $location);
  217. if ($phoneNumber instanceof PhoneNumber && $phoneUtil->isValidNumber($phoneNumber)) {
  218. $normalizedNumber = $phoneUtil->format($phoneNumber, PhoneNumberFormat::E164);
  219. $normalizedNumberToKey[$normalizedNumber] = (string) $key;
  220. }
  221. } catch (NumberParseException $e) {
  222. }
  223. }
  224. }
  225. $phoneNumbers = array_keys($normalizedNumberToKey);
  226. if (empty($phoneNumbers)) {
  227. return new DataResponse();
  228. }
  229. $userMatches = $this->accountManager->searchUsers(IAccountManager::PROPERTY_PHONE, $phoneNumbers);
  230. if (empty($userMatches)) {
  231. return new DataResponse();
  232. }
  233. $cloudUrl = rtrim($this->urlGenerator->getAbsoluteURL('/'), '/');
  234. if (strpos($cloudUrl, 'http://') === 0) {
  235. $cloudUrl = substr($cloudUrl, strlen('http://'));
  236. } elseif (strpos($cloudUrl, 'https://') === 0) {
  237. $cloudUrl = substr($cloudUrl, strlen('https://'));
  238. }
  239. $matches = [];
  240. foreach ($userMatches as $phone => $userId) {
  241. // Not using the ICloudIdManager as that would run a search for each contact to find the display name in the address book
  242. $matches[$normalizedNumberToKey[$phone]] = $userId . '@' . $cloudUrl;
  243. }
  244. return new DataResponse($matches);
  245. }
  246. /**
  247. * @throws OCSException
  248. */
  249. private function createNewUserId(): string {
  250. $attempts = 0;
  251. do {
  252. $uidCandidate = $this->secureRandom->generate(10, ISecureRandom::CHAR_HUMAN_READABLE);
  253. if (!$this->userManager->userExists($uidCandidate)) {
  254. return $uidCandidate;
  255. }
  256. $attempts++;
  257. } while ($attempts < 10);
  258. throw new OCSException('Could not create non-existing user id', 111);
  259. }
  260. /**
  261. * @PasswordConfirmationRequired
  262. * @NoAdminRequired
  263. *
  264. * @param string $userid
  265. * @param string $password
  266. * @param string $displayName
  267. * @param string $email
  268. * @param array $groups
  269. * @param array $subadmin
  270. * @param string $quota
  271. * @param string $language
  272. * @return DataResponse
  273. * @throws OCSException
  274. */
  275. public function addUser(string $userid,
  276. string $password = '',
  277. string $displayName = '',
  278. string $email = '',
  279. array $groups = [],
  280. array $subadmin = [],
  281. string $quota = '',
  282. string $language = ''): DataResponse {
  283. $user = $this->userSession->getUser();
  284. $isAdmin = $this->groupManager->isAdmin($user->getUID());
  285. $subAdminManager = $this->groupManager->getSubAdmin();
  286. if (empty($userid) && $this->config->getAppValue('core', 'newUser.generateUserID', 'no') === 'yes') {
  287. $userid = $this->createNewUserId();
  288. }
  289. if ($this->userManager->userExists($userid)) {
  290. $this->logger->error('Failed addUser attempt: User already exists.', ['app' => 'ocs_api']);
  291. throw new OCSException('User already exists', 102);
  292. }
  293. if ($groups !== []) {
  294. foreach ($groups as $group) {
  295. if (!$this->groupManager->groupExists($group)) {
  296. throw new OCSException('group '.$group.' does not exist', 104);
  297. }
  298. if (!$isAdmin && !$subAdminManager->isSubAdminOfGroup($user, $this->groupManager->get($group))) {
  299. throw new OCSException('insufficient privileges for group '. $group, 105);
  300. }
  301. }
  302. } else {
  303. if (!$isAdmin) {
  304. throw new OCSException('no group specified (required for subadmins)', 106);
  305. }
  306. }
  307. $subadminGroups = [];
  308. if ($subadmin !== []) {
  309. foreach ($subadmin as $groupid) {
  310. $group = $this->groupManager->get($groupid);
  311. // Check if group exists
  312. if ($group === null) {
  313. throw new OCSException('Subadmin group does not exist', 102);
  314. }
  315. // Check if trying to make subadmin of admin group
  316. if ($group->getGID() === 'admin') {
  317. throw new OCSException('Cannot create subadmins for admin group', 103);
  318. }
  319. // Check if has permission to promote subadmins
  320. if (!$subAdminManager->isSubAdminOfGroup($user, $group) && !$isAdmin) {
  321. throw new OCSForbiddenException('No permissions to promote subadmins');
  322. }
  323. $subadminGroups[] = $group;
  324. }
  325. }
  326. $generatePasswordResetToken = false;
  327. if ($password === '') {
  328. if ($email === '') {
  329. throw new OCSException('To send a password link to the user an email address is required.', 108);
  330. }
  331. $passwordEvent = new GenerateSecurePasswordEvent();
  332. $this->eventDispatcher->dispatchTyped($passwordEvent);
  333. $password = $passwordEvent->getPassword();
  334. if ($password === null) {
  335. // Fallback: ensure to pass password_policy in any case
  336. $password = $this->secureRandom->generate(10)
  337. . $this->secureRandom->generate(1, ISecureRandom::CHAR_UPPER)
  338. . $this->secureRandom->generate(1, ISecureRandom::CHAR_LOWER)
  339. . $this->secureRandom->generate(1, ISecureRandom::CHAR_DIGITS)
  340. . $this->secureRandom->generate(1, ISecureRandom::CHAR_SYMBOLS);
  341. }
  342. $generatePasswordResetToken = true;
  343. }
  344. if ($email === '' && $this->config->getAppValue('core', 'newUser.requireEmail', 'no') === 'yes') {
  345. throw new OCSException('Required email address was not provided', 110);
  346. }
  347. try {
  348. $newUser = $this->userManager->createUser($userid, $password);
  349. $this->logger->info('Successful addUser call with userid: ' . $userid, ['app' => 'ocs_api']);
  350. foreach ($groups as $group) {
  351. $this->groupManager->get($group)->addUser($newUser);
  352. $this->logger->info('Added userid ' . $userid . ' to group ' . $group, ['app' => 'ocs_api']);
  353. }
  354. foreach ($subadminGroups as $group) {
  355. $subAdminManager->createSubAdmin($newUser, $group);
  356. }
  357. if ($displayName !== '') {
  358. $this->editUser($userid, 'display', $displayName);
  359. }
  360. if ($quota !== '') {
  361. $this->editUser($userid, 'quota', $quota);
  362. }
  363. if ($language !== '') {
  364. $this->editUser($userid, 'language', $language);
  365. }
  366. // Send new user mail only if a mail is set
  367. if ($email !== '') {
  368. $newUser->setEMailAddress($email);
  369. if ($this->config->getAppValue('core', 'newUser.sendEmail', 'yes') === 'yes') {
  370. try {
  371. $emailTemplate = $this->newUserMailHelper->generateTemplate($newUser, $generatePasswordResetToken);
  372. $this->newUserMailHelper->sendMail($newUser, $emailTemplate);
  373. } catch (\Exception $e) {
  374. // Mail could be failing hard or just be plain not configured
  375. // Logging error as it is the hardest of the two
  376. $this->logger->error("Unable to send the invitation mail to $email",
  377. [
  378. 'app' => 'ocs_api',
  379. 'exception' => $e,
  380. ]
  381. );
  382. }
  383. }
  384. }
  385. return new DataResponse(['id' => $userid]);
  386. } catch (HintException $e) {
  387. $this->logger->warning('Failed addUser attempt with hint exception.',
  388. [
  389. 'app' => 'ocs_api',
  390. 'exception' => $e,
  391. ]
  392. );
  393. throw new OCSException($e->getHint(), 107);
  394. } catch (OCSException $e) {
  395. $this->logger->warning('Failed addUser attempt with ocs exeption.',
  396. [
  397. 'app' => 'ocs_api',
  398. 'exception' => $e,
  399. ]
  400. );
  401. throw $e;
  402. } catch (\Exception $e) {
  403. $this->logger->error('Failed addUser attempt with exception.',
  404. [
  405. 'app' => 'ocs_api',
  406. 'exception' => $e
  407. ]
  408. );
  409. throw new OCSException('Bad request', 101);
  410. }
  411. }
  412. /**
  413. * @NoAdminRequired
  414. * @NoSubAdminRequired
  415. *
  416. * gets user info
  417. *
  418. * @param string $userId
  419. * @return DataResponse
  420. * @throws OCSException
  421. */
  422. public function getUser(string $userId): DataResponse {
  423. $data = $this->getUserData($userId);
  424. // getUserData returns empty array if not enough permissions
  425. if (empty($data)) {
  426. throw new OCSException('', OCSController::RESPOND_UNAUTHORISED);
  427. }
  428. return new DataResponse($data);
  429. }
  430. /**
  431. * @NoAdminRequired
  432. * @NoSubAdminRequired
  433. *
  434. * gets user info from the currently logged in user
  435. *
  436. * @return DataResponse
  437. * @throws OCSException
  438. */
  439. public function getCurrentUser(): DataResponse {
  440. $user = $this->userSession->getUser();
  441. if ($user) {
  442. $data = $this->getUserData($user->getUID());
  443. // rename "displayname" to "display-name" only for this call to keep
  444. // the API stable.
  445. $data['display-name'] = $data['displayname'];
  446. unset($data['displayname']);
  447. return new DataResponse($data);
  448. }
  449. throw new OCSException('', OCSController::RESPOND_UNAUTHORISED);
  450. }
  451. /**
  452. * @NoAdminRequired
  453. * @NoSubAdminRequired
  454. */
  455. public function getEditableFields(): DataResponse {
  456. $permittedFields = [];
  457. // Editing self (display, email)
  458. if ($this->config->getSystemValue('allow_user_to_change_display_name', true) !== false) {
  459. $permittedFields[] = IAccountManager::PROPERTY_DISPLAYNAME;
  460. $permittedFields[] = IAccountManager::PROPERTY_EMAIL;
  461. }
  462. if ($this->appManager->isEnabledForUser('federatedfilesharing')) {
  463. $shareProvider = $this->federatedShareProviderFactory->get();
  464. if ($shareProvider->isLookupServerUploadEnabled()) {
  465. $permittedFields[] = IAccountManager::PROPERTY_PHONE;
  466. $permittedFields[] = IAccountManager::PROPERTY_ADDRESS;
  467. $permittedFields[] = IAccountManager::PROPERTY_WEBSITE;
  468. $permittedFields[] = IAccountManager::PROPERTY_TWITTER;
  469. }
  470. }
  471. return new DataResponse($permittedFields);
  472. }
  473. /**
  474. * @NoAdminRequired
  475. * @NoSubAdminRequired
  476. * @PasswordConfirmationRequired
  477. *
  478. * edit users
  479. *
  480. * @param string $userId
  481. * @param string $key
  482. * @param string $value
  483. * @return DataResponse
  484. * @throws OCSException
  485. */
  486. public function editUser(string $userId, string $key, string $value): DataResponse {
  487. $currentLoggedInUser = $this->userSession->getUser();
  488. $targetUser = $this->userManager->get($userId);
  489. if ($targetUser === null) {
  490. throw new OCSException('', OCSController::RESPOND_UNAUTHORISED);
  491. }
  492. $permittedFields = [];
  493. if ($targetUser->getUID() === $currentLoggedInUser->getUID()) {
  494. // Editing self (display, email)
  495. if ($this->config->getSystemValue('allow_user_to_change_display_name', true) !== false) {
  496. $permittedFields[] = 'display';
  497. $permittedFields[] = IAccountManager::PROPERTY_DISPLAYNAME;
  498. $permittedFields[] = IAccountManager::PROPERTY_EMAIL;
  499. }
  500. $permittedFields[] = 'password';
  501. if ($this->config->getSystemValue('force_language', false) === false ||
  502. $this->groupManager->isAdmin($currentLoggedInUser->getUID())) {
  503. $permittedFields[] = 'language';
  504. }
  505. if ($this->config->getSystemValue('force_locale', false) === false ||
  506. $this->groupManager->isAdmin($currentLoggedInUser->getUID())) {
  507. $permittedFields[] = 'locale';
  508. }
  509. if ($this->appManager->isEnabledForUser('federatedfilesharing')) {
  510. $shareProvider = $this->federatedShareProviderFactory->get();
  511. if ($shareProvider->isLookupServerUploadEnabled()) {
  512. $permittedFields[] = IAccountManager::PROPERTY_PHONE;
  513. $permittedFields[] = IAccountManager::PROPERTY_ADDRESS;
  514. $permittedFields[] = IAccountManager::PROPERTY_WEBSITE;
  515. $permittedFields[] = IAccountManager::PROPERTY_TWITTER;
  516. }
  517. }
  518. // If admin they can edit their own quota
  519. if ($this->groupManager->isAdmin($currentLoggedInUser->getUID())) {
  520. $permittedFields[] = 'quota';
  521. }
  522. } else {
  523. // Check if admin / subadmin
  524. $subAdminManager = $this->groupManager->getSubAdmin();
  525. if ($this->groupManager->isAdmin($currentLoggedInUser->getUID())
  526. || $subAdminManager->isUserAccessible($currentLoggedInUser, $targetUser)) {
  527. // They have permissions over the user
  528. $permittedFields[] = 'display';
  529. $permittedFields[] = IAccountManager::PROPERTY_DISPLAYNAME;
  530. $permittedFields[] = IAccountManager::PROPERTY_EMAIL;
  531. $permittedFields[] = 'password';
  532. $permittedFields[] = 'language';
  533. $permittedFields[] = 'locale';
  534. $permittedFields[] = IAccountManager::PROPERTY_PHONE;
  535. $permittedFields[] = IAccountManager::PROPERTY_ADDRESS;
  536. $permittedFields[] = IAccountManager::PROPERTY_WEBSITE;
  537. $permittedFields[] = IAccountManager::PROPERTY_TWITTER;
  538. $permittedFields[] = 'quota';
  539. } else {
  540. // No rights
  541. throw new OCSException('', OCSController::RESPOND_UNAUTHORISED);
  542. }
  543. }
  544. // Check if permitted to edit this field
  545. if (!in_array($key, $permittedFields)) {
  546. throw new OCSException('', OCSController::RESPOND_UNAUTHORISED);
  547. }
  548. // Process the edit
  549. switch ($key) {
  550. case 'display':
  551. case IAccountManager::PROPERTY_DISPLAYNAME:
  552. $targetUser->setDisplayName($value);
  553. break;
  554. case 'quota':
  555. $quota = $value;
  556. if ($quota !== 'none' && $quota !== 'default') {
  557. if (is_numeric($quota)) {
  558. $quota = (float) $quota;
  559. } else {
  560. $quota = \OCP\Util::computerFileSize($quota);
  561. }
  562. if ($quota === false) {
  563. throw new OCSException('Invalid quota value '.$value, 103);
  564. }
  565. if ($quota === -1) {
  566. $quota = 'none';
  567. } else {
  568. $quota = \OCP\Util::humanFileSize($quota);
  569. }
  570. }
  571. $targetUser->setQuota($quota);
  572. break;
  573. case 'password':
  574. try {
  575. if (!$targetUser->canChangePassword()) {
  576. throw new OCSException('Setting the password is not supported by the users backend', 103);
  577. }
  578. $targetUser->setPassword($value);
  579. } catch (HintException $e) { // password policy error
  580. throw new OCSException($e->getMessage(), 103);
  581. }
  582. break;
  583. case 'language':
  584. $languagesCodes = $this->l10nFactory->findAvailableLanguages();
  585. if (!in_array($value, $languagesCodes, true) && $value !== 'en') {
  586. throw new OCSException('Invalid language', 102);
  587. }
  588. $this->config->setUserValue($targetUser->getUID(), 'core', 'lang', $value);
  589. break;
  590. case 'locale':
  591. if (!$this->l10nFactory->localeExists($value)) {
  592. throw new OCSException('Invalid locale', 102);
  593. }
  594. $this->config->setUserValue($targetUser->getUID(), 'core', 'locale', $value);
  595. break;
  596. case IAccountManager::PROPERTY_EMAIL:
  597. if (filter_var($value, FILTER_VALIDATE_EMAIL) || $value === '') {
  598. $targetUser->setEMailAddress($value);
  599. } else {
  600. throw new OCSException('', 102);
  601. }
  602. break;
  603. case IAccountManager::PROPERTY_PHONE:
  604. case IAccountManager::PROPERTY_ADDRESS:
  605. case IAccountManager::PROPERTY_WEBSITE:
  606. case IAccountManager::PROPERTY_TWITTER:
  607. $userAccount = $this->accountManager->getUser($targetUser);
  608. if ($userAccount[$key]['value'] !== $value) {
  609. $userAccount[$key]['value'] = $value;
  610. try {
  611. $this->accountManager->updateUser($targetUser, $userAccount, true);
  612. } catch (\InvalidArgumentException $e) {
  613. throw new OCSException('Invalid ' . $e->getMessage(), 102);
  614. }
  615. }
  616. break;
  617. default:
  618. throw new OCSException('', 103);
  619. }
  620. return new DataResponse();
  621. }
  622. /**
  623. * @PasswordConfirmationRequired
  624. * @NoAdminRequired
  625. *
  626. * @param string $userId
  627. *
  628. * @return DataResponse
  629. *
  630. * @throws OCSException
  631. */
  632. public function wipeUserDevices(string $userId): DataResponse {
  633. /** @var IUser $currentLoggedInUser */
  634. $currentLoggedInUser = $this->userSession->getUser();
  635. $targetUser = $this->userManager->get($userId);
  636. if ($targetUser === null || $targetUser->getUID() === $currentLoggedInUser->getUID()) {
  637. throw new OCSException('', 101);
  638. }
  639. // If not permitted
  640. $subAdminManager = $this->groupManager->getSubAdmin();
  641. if (!$this->groupManager->isAdmin($currentLoggedInUser->getUID()) && !$subAdminManager->isUserAccessible($currentLoggedInUser, $targetUser)) {
  642. throw new OCSException('', OCSController::RESPOND_UNAUTHORISED);
  643. }
  644. $this->remoteWipe->markAllTokensForWipe($targetUser);
  645. return new DataResponse();
  646. }
  647. /**
  648. * @PasswordConfirmationRequired
  649. * @NoAdminRequired
  650. *
  651. * @param string $userId
  652. * @return DataResponse
  653. * @throws OCSException
  654. */
  655. public function deleteUser(string $userId): DataResponse {
  656. $currentLoggedInUser = $this->userSession->getUser();
  657. $targetUser = $this->userManager->get($userId);
  658. if ($targetUser === null || $targetUser->getUID() === $currentLoggedInUser->getUID()) {
  659. throw new OCSException('', 101);
  660. }
  661. // If not permitted
  662. $subAdminManager = $this->groupManager->getSubAdmin();
  663. if (!$this->groupManager->isAdmin($currentLoggedInUser->getUID()) && !$subAdminManager->isUserAccessible($currentLoggedInUser, $targetUser)) {
  664. throw new OCSException('', OCSController::RESPOND_UNAUTHORISED);
  665. }
  666. // Go ahead with the delete
  667. if ($targetUser->delete()) {
  668. return new DataResponse();
  669. } else {
  670. throw new OCSException('', 101);
  671. }
  672. }
  673. /**
  674. * @PasswordConfirmationRequired
  675. * @NoAdminRequired
  676. *
  677. * @param string $userId
  678. * @return DataResponse
  679. * @throws OCSException
  680. * @throws OCSForbiddenException
  681. */
  682. public function disableUser(string $userId): DataResponse {
  683. return $this->setEnabled($userId, false);
  684. }
  685. /**
  686. * @PasswordConfirmationRequired
  687. * @NoAdminRequired
  688. *
  689. * @param string $userId
  690. * @return DataResponse
  691. * @throws OCSException
  692. * @throws OCSForbiddenException
  693. */
  694. public function enableUser(string $userId): DataResponse {
  695. return $this->setEnabled($userId, true);
  696. }
  697. /**
  698. * @param string $userId
  699. * @param bool $value
  700. * @return DataResponse
  701. * @throws OCSException
  702. */
  703. private function setEnabled(string $userId, bool $value): DataResponse {
  704. $currentLoggedInUser = $this->userSession->getUser();
  705. $targetUser = $this->userManager->get($userId);
  706. if ($targetUser === null || $targetUser->getUID() === $currentLoggedInUser->getUID()) {
  707. throw new OCSException('', 101);
  708. }
  709. // If not permitted
  710. $subAdminManager = $this->groupManager->getSubAdmin();
  711. if (!$this->groupManager->isAdmin($currentLoggedInUser->getUID()) && !$subAdminManager->isUserAccessible($currentLoggedInUser, $targetUser)) {
  712. throw new OCSException('', OCSController::RESPOND_UNAUTHORISED);
  713. }
  714. // enable/disable the user now
  715. $targetUser->setEnabled($value);
  716. return new DataResponse();
  717. }
  718. /**
  719. * @NoAdminRequired
  720. * @NoSubAdminRequired
  721. *
  722. * @param string $userId
  723. * @return DataResponse
  724. * @throws OCSException
  725. */
  726. public function getUsersGroups(string $userId): DataResponse {
  727. $loggedInUser = $this->userSession->getUser();
  728. $targetUser = $this->userManager->get($userId);
  729. if ($targetUser === null) {
  730. throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
  731. }
  732. if ($targetUser->getUID() === $loggedInUser->getUID() || $this->groupManager->isAdmin($loggedInUser->getUID())) {
  733. // Self lookup or admin lookup
  734. return new DataResponse([
  735. 'groups' => $this->groupManager->getUserGroupIds($targetUser)
  736. ]);
  737. } else {
  738. $subAdminManager = $this->groupManager->getSubAdmin();
  739. // Looking up someone else
  740. if ($subAdminManager->isUserAccessible($loggedInUser, $targetUser)) {
  741. // Return the group that the method caller is subadmin of for the user in question
  742. /** @var IGroup[] $getSubAdminsGroups */
  743. $getSubAdminsGroups = $subAdminManager->getSubAdminsGroups($loggedInUser);
  744. foreach ($getSubAdminsGroups as $key => $group) {
  745. $getSubAdminsGroups[$key] = $group->getGID();
  746. }
  747. $groups = array_intersect(
  748. $getSubAdminsGroups,
  749. $this->groupManager->getUserGroupIds($targetUser)
  750. );
  751. return new DataResponse(['groups' => $groups]);
  752. } else {
  753. // Not permitted
  754. throw new OCSException('', OCSController::RESPOND_UNAUTHORISED);
  755. }
  756. }
  757. }
  758. /**
  759. * @PasswordConfirmationRequired
  760. * @NoAdminRequired
  761. *
  762. * @param string $userId
  763. * @param string $groupid
  764. * @return DataResponse
  765. * @throws OCSException
  766. */
  767. public function addToGroup(string $userId, string $groupid = ''): DataResponse {
  768. if ($groupid === '') {
  769. throw new OCSException('', 101);
  770. }
  771. $group = $this->groupManager->get($groupid);
  772. $targetUser = $this->userManager->get($userId);
  773. if ($group === null) {
  774. throw new OCSException('', 102);
  775. }
  776. if ($targetUser === null) {
  777. throw new OCSException('', 103);
  778. }
  779. // If they're not an admin, check they are a subadmin of the group in question
  780. $loggedInUser = $this->userSession->getUser();
  781. $subAdminManager = $this->groupManager->getSubAdmin();
  782. if (!$this->groupManager->isAdmin($loggedInUser->getUID()) && !$subAdminManager->isSubAdminOfGroup($loggedInUser, $group)) {
  783. throw new OCSException('', 104);
  784. }
  785. // Add user to group
  786. $group->addUser($targetUser);
  787. return new DataResponse();
  788. }
  789. /**
  790. * @PasswordConfirmationRequired
  791. * @NoAdminRequired
  792. *
  793. * @param string $userId
  794. * @param string $groupid
  795. * @return DataResponse
  796. * @throws OCSException
  797. */
  798. public function removeFromGroup(string $userId, string $groupid): DataResponse {
  799. $loggedInUser = $this->userSession->getUser();
  800. if ($groupid === null || trim($groupid) === '') {
  801. throw new OCSException('', 101);
  802. }
  803. $group = $this->groupManager->get($groupid);
  804. if ($group === null) {
  805. throw new OCSException('', 102);
  806. }
  807. $targetUser = $this->userManager->get($userId);
  808. if ($targetUser === null) {
  809. throw new OCSException('', 103);
  810. }
  811. // If they're not an admin, check they are a subadmin of the group in question
  812. $subAdminManager = $this->groupManager->getSubAdmin();
  813. if (!$this->groupManager->isAdmin($loggedInUser->getUID()) && !$subAdminManager->isSubAdminOfGroup($loggedInUser, $group)) {
  814. throw new OCSException('', 104);
  815. }
  816. // Check they aren't removing themselves from 'admin' or their 'subadmin; group
  817. if ($targetUser->getUID() === $loggedInUser->getUID()) {
  818. if ($this->groupManager->isAdmin($loggedInUser->getUID())) {
  819. if ($group->getGID() === 'admin') {
  820. throw new OCSException('Cannot remove yourself from the admin group', 105);
  821. }
  822. } else {
  823. // Not an admin, so the user must be a subadmin of this group, but that is not allowed.
  824. throw new OCSException('Cannot remove yourself from this group as you are a SubAdmin', 105);
  825. }
  826. } elseif (!$this->groupManager->isAdmin($loggedInUser->getUID())) {
  827. /** @var IGroup[] $subAdminGroups */
  828. $subAdminGroups = $subAdminManager->getSubAdminsGroups($loggedInUser);
  829. $subAdminGroups = array_map(function (IGroup $subAdminGroup) {
  830. return $subAdminGroup->getGID();
  831. }, $subAdminGroups);
  832. $userGroups = $this->groupManager->getUserGroupIds($targetUser);
  833. $userSubAdminGroups = array_intersect($subAdminGroups, $userGroups);
  834. if (count($userSubAdminGroups) <= 1) {
  835. // Subadmin must not be able to remove a user from all their subadmin groups.
  836. throw new OCSException('Not viable to remove user from the last group you are SubAdmin of', 105);
  837. }
  838. }
  839. // Remove user from group
  840. $group->removeUser($targetUser);
  841. return new DataResponse();
  842. }
  843. /**
  844. * Creates a subadmin
  845. *
  846. * @PasswordConfirmationRequired
  847. *
  848. * @param string $userId
  849. * @param string $groupid
  850. * @return DataResponse
  851. * @throws OCSException
  852. */
  853. public function addSubAdmin(string $userId, string $groupid): DataResponse {
  854. $group = $this->groupManager->get($groupid);
  855. $user = $this->userManager->get($userId);
  856. // Check if the user exists
  857. if ($user === null) {
  858. throw new OCSException('User does not exist', 101);
  859. }
  860. // Check if group exists
  861. if ($group === null) {
  862. throw new OCSException('Group does not exist', 102);
  863. }
  864. // Check if trying to make subadmin of admin group
  865. if ($group->getGID() === 'admin') {
  866. throw new OCSException('Cannot create subadmins for admin group', 103);
  867. }
  868. $subAdminManager = $this->groupManager->getSubAdmin();
  869. // We cannot be subadmin twice
  870. if ($subAdminManager->isSubAdminOfGroup($user, $group)) {
  871. return new DataResponse();
  872. }
  873. // Go
  874. $subAdminManager->createSubAdmin($user, $group);
  875. return new DataResponse();
  876. }
  877. /**
  878. * Removes a subadmin from a group
  879. *
  880. * @PasswordConfirmationRequired
  881. *
  882. * @param string $userId
  883. * @param string $groupid
  884. * @return DataResponse
  885. * @throws OCSException
  886. */
  887. public function removeSubAdmin(string $userId, string $groupid): DataResponse {
  888. $group = $this->groupManager->get($groupid);
  889. $user = $this->userManager->get($userId);
  890. $subAdminManager = $this->groupManager->getSubAdmin();
  891. // Check if the user exists
  892. if ($user === null) {
  893. throw new OCSException('User does not exist', 101);
  894. }
  895. // Check if the group exists
  896. if ($group === null) {
  897. throw new OCSException('Group does not exist', 101);
  898. }
  899. // Check if they are a subadmin of this said group
  900. if (!$subAdminManager->isSubAdminOfGroup($user, $group)) {
  901. throw new OCSException('User is not a subadmin of this group', 102);
  902. }
  903. // Go
  904. $subAdminManager->deleteSubAdmin($user, $group);
  905. return new DataResponse();
  906. }
  907. /**
  908. * Get the groups a user is a subadmin of
  909. *
  910. * @param string $userId
  911. * @return DataResponse
  912. * @throws OCSException
  913. */
  914. public function getUserSubAdminGroups(string $userId): DataResponse {
  915. $groups = $this->getUserSubAdminGroupsData($userId);
  916. return new DataResponse($groups);
  917. }
  918. /**
  919. * @NoAdminRequired
  920. * @PasswordConfirmationRequired
  921. *
  922. * resend welcome message
  923. *
  924. * @param string $userId
  925. * @return DataResponse
  926. * @throws OCSException
  927. */
  928. public function resendWelcomeMessage(string $userId): DataResponse {
  929. $currentLoggedInUser = $this->userSession->getUser();
  930. $targetUser = $this->userManager->get($userId);
  931. if ($targetUser === null) {
  932. throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
  933. }
  934. // Check if admin / subadmin
  935. $subAdminManager = $this->groupManager->getSubAdmin();
  936. if (!$subAdminManager->isUserAccessible($currentLoggedInUser, $targetUser)
  937. && !$this->groupManager->isAdmin($currentLoggedInUser->getUID())) {
  938. // No rights
  939. throw new OCSException('', OCSController::RESPOND_UNAUTHORISED);
  940. }
  941. $email = $targetUser->getEMailAddress();
  942. if ($email === '' || $email === null) {
  943. throw new OCSException('Email address not available', 101);
  944. }
  945. try {
  946. $emailTemplate = $this->newUserMailHelper->generateTemplate($targetUser, false);
  947. $this->newUserMailHelper->sendMail($targetUser, $emailTemplate);
  948. } catch (\Exception $e) {
  949. $this->logger->error("Can't send new user mail to $email",
  950. [
  951. 'app' => 'settings',
  952. 'exception' => $e,
  953. ]
  954. );
  955. throw new OCSException('Sending email failed', 102);
  956. }
  957. return new DataResponse();
  958. }
  959. }