UsersController.php 51 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584
  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@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. * @author Vincent Petry <vincent@nextcloud.com>
  25. * @author Kate Döen <kate.doeen@nextcloud.com>
  26. *
  27. * @license AGPL-3.0
  28. *
  29. * This code is free software: you can redistribute it and/or modify
  30. * it under the terms of the GNU Affero General Public License, version 3,
  31. * as published by the Free Software Foundation.
  32. *
  33. * This program is distributed in the hope that it will be useful,
  34. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  35. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  36. * GNU Affero General Public License for more details.
  37. *
  38. * You should have received a copy of the GNU Affero General Public License, version 3,
  39. * along with this program. If not, see <http://www.gnu.org/licenses/>
  40. *
  41. */
  42. namespace OCA\Provisioning_API\Controller;
  43. use InvalidArgumentException;
  44. use OC\Authentication\Token\RemoteWipe;
  45. use OC\KnownUser\KnownUserService;
  46. use OC\User\Backend;
  47. use OCA\Provisioning_API\ResponseDefinitions;
  48. use OCA\Settings\Mailer\NewUserMailHelper;
  49. use OCP\Accounts\IAccountManager;
  50. use OCP\Accounts\IAccountProperty;
  51. use OCP\Accounts\PropertyDoesNotExistException;
  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\EventDispatcher\IEventDispatcher;
  58. use OCP\HintException;
  59. use OCP\IConfig;
  60. use OCP\IGroup;
  61. use OCP\IGroupManager;
  62. use OCP\IPhoneNumberUtil;
  63. use OCP\IRequest;
  64. use OCP\IURLGenerator;
  65. use OCP\IUser;
  66. use OCP\IUserManager;
  67. use OCP\IUserSession;
  68. use OCP\L10N\IFactory;
  69. use OCP\Security\Events\GenerateSecurePasswordEvent;
  70. use OCP\Security\ISecureRandom;
  71. use OCP\User\Backend\ISetDisplayNameBackend;
  72. use Psr\Log\LoggerInterface;
  73. /**
  74. * @psalm-import-type ProvisioningApiUserDetails from ResponseDefinitions
  75. */
  76. class UsersController extends AUserData {
  77. /** @var IURLGenerator */
  78. protected $urlGenerator;
  79. /** @var LoggerInterface */
  80. private $logger;
  81. /** @var IFactory */
  82. protected $l10nFactory;
  83. /** @var NewUserMailHelper */
  84. private $newUserMailHelper;
  85. /** @var ISecureRandom */
  86. private $secureRandom;
  87. /** @var RemoteWipe */
  88. private $remoteWipe;
  89. /** @var KnownUserService */
  90. private $knownUserService;
  91. /** @var IEventDispatcher */
  92. private $eventDispatcher;
  93. public function __construct(
  94. string $appName,
  95. IRequest $request,
  96. IUserManager $userManager,
  97. IConfig $config,
  98. IGroupManager $groupManager,
  99. IUserSession $userSession,
  100. IAccountManager $accountManager,
  101. IURLGenerator $urlGenerator,
  102. LoggerInterface $logger,
  103. IFactory $l10nFactory,
  104. NewUserMailHelper $newUserMailHelper,
  105. ISecureRandom $secureRandom,
  106. RemoteWipe $remoteWipe,
  107. KnownUserService $knownUserService,
  108. IEventDispatcher $eventDispatcher,
  109. private IPhoneNumberUtil $phoneNumberUtil,
  110. ) {
  111. parent::__construct(
  112. $appName,
  113. $request,
  114. $userManager,
  115. $config,
  116. $groupManager,
  117. $userSession,
  118. $accountManager,
  119. $l10nFactory
  120. );
  121. $this->urlGenerator = $urlGenerator;
  122. $this->logger = $logger;
  123. $this->l10nFactory = $l10nFactory;
  124. $this->newUserMailHelper = $newUserMailHelper;
  125. $this->secureRandom = $secureRandom;
  126. $this->remoteWipe = $remoteWipe;
  127. $this->knownUserService = $knownUserService;
  128. $this->eventDispatcher = $eventDispatcher;
  129. }
  130. /**
  131. * @NoAdminRequired
  132. *
  133. * Get a list of users
  134. *
  135. * @param string $search Text to search for
  136. * @param int|null $limit Limit the amount of groups returned
  137. * @param int $offset Offset for searching for groups
  138. * @return DataResponse<Http::STATUS_OK, array{users: string[]}, array{}>
  139. *
  140. * 200: Users returned
  141. */
  142. public function getUsers(string $search = '', int $limit = null, int $offset = 0): DataResponse {
  143. $user = $this->userSession->getUser();
  144. $users = [];
  145. // Admin? Or SubAdmin?
  146. $uid = $user->getUID();
  147. $subAdminManager = $this->groupManager->getSubAdmin();
  148. if ($this->groupManager->isAdmin($uid)) {
  149. $users = $this->userManager->search($search, $limit, $offset);
  150. } elseif ($subAdminManager->isSubAdmin($user)) {
  151. $subAdminOfGroups = $subAdminManager->getSubAdminsGroups($user);
  152. foreach ($subAdminOfGroups as $key => $group) {
  153. $subAdminOfGroups[$key] = $group->getGID();
  154. }
  155. $users = [];
  156. foreach ($subAdminOfGroups as $group) {
  157. $users = array_merge($users, $this->groupManager->displayNamesInGroup($group, $search, $limit, $offset));
  158. }
  159. }
  160. /** @var string[] $users */
  161. $users = array_keys($users);
  162. return new DataResponse([
  163. 'users' => $users
  164. ]);
  165. }
  166. /**
  167. * @NoAdminRequired
  168. *
  169. * Get a list of users and their details
  170. *
  171. * @param string $search Text to search for
  172. * @param int|null $limit Limit the amount of groups returned
  173. * @param int $offset Offset for searching for groups
  174. * @return DataResponse<Http::STATUS_OK, array{users: array<string, ProvisioningApiUserDetails|array{id: string}>}, array{}>
  175. *
  176. * 200: Users details returned
  177. */
  178. public function getUsersDetails(string $search = '', int $limit = null, int $offset = 0): DataResponse {
  179. $currentUser = $this->userSession->getUser();
  180. $users = [];
  181. // Admin? Or SubAdmin?
  182. $uid = $currentUser->getUID();
  183. $subAdminManager = $this->groupManager->getSubAdmin();
  184. if ($this->groupManager->isAdmin($uid)) {
  185. $users = $this->userManager->search($search, $limit, $offset);
  186. $users = array_keys($users);
  187. } elseif ($subAdminManager->isSubAdmin($currentUser)) {
  188. $subAdminOfGroups = $subAdminManager->getSubAdminsGroups($currentUser);
  189. foreach ($subAdminOfGroups as $key => $group) {
  190. $subAdminOfGroups[$key] = $group->getGID();
  191. }
  192. $users = [];
  193. foreach ($subAdminOfGroups as $group) {
  194. $users[] = array_keys($this->groupManager->displayNamesInGroup($group, $search, $limit, $offset));
  195. }
  196. $users = array_merge(...$users);
  197. }
  198. $usersDetails = [];
  199. foreach ($users as $userId) {
  200. $userId = (string) $userId;
  201. $userData = $this->getUserData($userId);
  202. // Do not insert empty entry
  203. if ($userData !== null) {
  204. $usersDetails[$userId] = $userData;
  205. } else {
  206. // Logged user does not have permissions to see this user
  207. // only showing its id
  208. $usersDetails[$userId] = ['id' => $userId];
  209. }
  210. }
  211. return new DataResponse([
  212. 'users' => $usersDetails
  213. ]);
  214. }
  215. /**
  216. * @NoAdminRequired
  217. *
  218. * Get the list of disabled users and their details
  219. *
  220. * @param ?int $limit Limit the amount of users returned
  221. * @param int $offset Offset
  222. * @return DataResponse<Http::STATUS_OK, array{users: array<string, ProvisioningApiUserDetails|array{id: string}>}, array{}>
  223. *
  224. * 200: Disabled users details returned
  225. */
  226. public function getDisabledUsersDetails(?int $limit = null, int $offset = 0): DataResponse {
  227. $currentUser = $this->userSession->getUser();
  228. if ($currentUser === null) {
  229. return new DataResponse(['users' => []]);
  230. }
  231. if ($limit !== null && $limit < 0) {
  232. throw new InvalidArgumentException("Invalid limit value: $limit");
  233. }
  234. if ($offset < 0) {
  235. throw new InvalidArgumentException("Invalid offset value: $offset");
  236. }
  237. $users = [];
  238. // Admin? Or SubAdmin?
  239. $uid = $currentUser->getUID();
  240. $subAdminManager = $this->groupManager->getSubAdmin();
  241. if ($this->groupManager->isAdmin($uid)) {
  242. $users = $this->userManager->getDisabledUsers($limit, $offset);
  243. $users = array_map(fn (IUser $user): string => $user->getUID(), $users);
  244. } elseif ($subAdminManager->isSubAdmin($currentUser)) {
  245. $subAdminOfGroups = $subAdminManager->getSubAdminsGroups($currentUser);
  246. $users = [];
  247. /* We have to handle offset ourselve for correctness */
  248. $tempLimit = ($limit === null ? null : $limit + $offset);
  249. foreach ($subAdminOfGroups as $group) {
  250. $users = array_merge(
  251. $users,
  252. array_map(
  253. fn (IUser $user): string => $user->getUID(),
  254. array_filter(
  255. $group->searchUsers('', ($tempLimit === null ? null : $tempLimit - count($users))),
  256. fn (IUser $user): bool => $user->isEnabled()
  257. )
  258. )
  259. );
  260. if (($tempLimit !== null) && (count($users) >= $tempLimit)) {
  261. break;
  262. }
  263. }
  264. $users = array_slice($users, $offset);
  265. }
  266. $usersDetails = [];
  267. foreach ($users as $userId) {
  268. $userData = $this->getUserData($userId);
  269. // Do not insert empty entry
  270. if ($userData !== null) {
  271. $usersDetails[$userId] = $userData;
  272. } else {
  273. // Logged user does not have permissions to see this user
  274. // only showing its id
  275. $usersDetails[$userId] = ['id' => $userId];
  276. }
  277. }
  278. return new DataResponse([
  279. 'users' => $usersDetails
  280. ]);
  281. }
  282. /**
  283. * @NoAdminRequired
  284. * @NoSubAdminRequired
  285. *
  286. * Search users by their phone numbers
  287. *
  288. * @param string $location Location of the phone number (for country code)
  289. * @param array<string, string[]> $search Phone numbers to search for
  290. * @return DataResponse<Http::STATUS_OK, array<string, string>, array{}>|DataResponse<Http::STATUS_BAD_REQUEST, array<empty>, array{}>
  291. *
  292. * 200: Users returned
  293. * 400: Invalid location
  294. */
  295. public function searchByPhoneNumbers(string $location, array $search): DataResponse {
  296. if ($this->phoneNumberUtil->getCountryCodeForRegion($location) === null) {
  297. // Not a valid region code
  298. return new DataResponse([], Http::STATUS_BAD_REQUEST);
  299. }
  300. /** @var IUser $user */
  301. $user = $this->userSession->getUser();
  302. $knownTo = $user->getUID();
  303. $defaultPhoneRegion = $this->config->getSystemValueString('default_phone_region');
  304. $normalizedNumberToKey = [];
  305. foreach ($search as $key => $phoneNumbers) {
  306. foreach ($phoneNumbers as $phone) {
  307. $normalizedNumber = $this->phoneNumberUtil->convertToStandardFormat($phone, $location);
  308. if ($normalizedNumber !== null) {
  309. $normalizedNumberToKey[$normalizedNumber] = (string) $key;
  310. }
  311. if ($defaultPhoneRegion !== '' && $defaultPhoneRegion !== $location && str_starts_with($phone, '0')) {
  312. // If the number has a leading zero (no country code),
  313. // we also check the default phone region of the instance,
  314. // when it's different to the user's given region.
  315. $normalizedNumber = $this->phoneNumberUtil->convertToStandardFormat($phone, $defaultPhoneRegion);
  316. if ($normalizedNumber !== null) {
  317. $normalizedNumberToKey[$normalizedNumber] = (string) $key;
  318. }
  319. }
  320. }
  321. }
  322. $phoneNumbers = array_keys($normalizedNumberToKey);
  323. if (empty($phoneNumbers)) {
  324. return new DataResponse();
  325. }
  326. // Cleanup all previous entries and only allow new matches
  327. $this->knownUserService->deleteKnownTo($knownTo);
  328. $userMatches = $this->accountManager->searchUsers(IAccountManager::PROPERTY_PHONE, $phoneNumbers);
  329. if (empty($userMatches)) {
  330. return new DataResponse();
  331. }
  332. $cloudUrl = rtrim($this->urlGenerator->getAbsoluteURL('/'), '/');
  333. if (strpos($cloudUrl, 'http://') === 0) {
  334. $cloudUrl = substr($cloudUrl, strlen('http://'));
  335. } elseif (strpos($cloudUrl, 'https://') === 0) {
  336. $cloudUrl = substr($cloudUrl, strlen('https://'));
  337. }
  338. $matches = [];
  339. foreach ($userMatches as $phone => $userId) {
  340. // Not using the ICloudIdManager as that would run a search for each contact to find the display name in the address book
  341. $matches[$normalizedNumberToKey[$phone]] = $userId . '@' . $cloudUrl;
  342. $this->knownUserService->storeIsKnownToUser($knownTo, $userId);
  343. }
  344. return new DataResponse($matches);
  345. }
  346. /**
  347. * @throws OCSException
  348. */
  349. private function createNewUserId(): string {
  350. $attempts = 0;
  351. do {
  352. $uidCandidate = $this->secureRandom->generate(10, ISecureRandom::CHAR_HUMAN_READABLE);
  353. if (!$this->userManager->userExists($uidCandidate)) {
  354. return $uidCandidate;
  355. }
  356. $attempts++;
  357. } while ($attempts < 10);
  358. throw new OCSException('Could not create non-existing user id', 111);
  359. }
  360. /**
  361. * @PasswordConfirmationRequired
  362. * @NoAdminRequired
  363. *
  364. * Create a new user
  365. *
  366. * @param string $userid ID of the user
  367. * @param string $password Password of the user
  368. * @param string $displayName Display name of the user
  369. * @param string $email Email of the user
  370. * @param string[] $groups Groups of the user
  371. * @param string[] $subadmin Groups where the user is subadmin
  372. * @param string $quota Quota of the user
  373. * @param string $language Language of the user
  374. * @param ?string $manager Manager of the user
  375. * @return DataResponse<Http::STATUS_OK, array{id: string}, array{}>
  376. * @throws OCSException
  377. * @throws OCSForbiddenException Missing permissions to make user subadmin
  378. *
  379. * 200: User added successfully
  380. */
  381. public function addUser(
  382. string $userid,
  383. string $password = '',
  384. string $displayName = '',
  385. string $email = '',
  386. array $groups = [],
  387. array $subadmin = [],
  388. string $quota = '',
  389. string $language = '',
  390. ?string $manager = null,
  391. ): DataResponse {
  392. $user = $this->userSession->getUser();
  393. $isAdmin = $this->groupManager->isAdmin($user->getUID());
  394. $subAdminManager = $this->groupManager->getSubAdmin();
  395. if (empty($userid) && $this->config->getAppValue('core', 'newUser.generateUserID', 'no') === 'yes') {
  396. $userid = $this->createNewUserId();
  397. }
  398. if ($this->userManager->userExists($userid)) {
  399. $this->logger->error('Failed addUser attempt: User already exists.', ['app' => 'ocs_api']);
  400. throw new OCSException($this->l10nFactory->get('provisioning_api')->t('User already exists'), 102);
  401. }
  402. if ($groups !== []) {
  403. foreach ($groups as $group) {
  404. if (!$this->groupManager->groupExists($group)) {
  405. throw new OCSException('group ' . $group . ' does not exist', 104);
  406. }
  407. if (!$isAdmin && !$subAdminManager->isSubAdminOfGroup($user, $this->groupManager->get($group))) {
  408. throw new OCSException('insufficient privileges for group ' . $group, 105);
  409. }
  410. }
  411. } else {
  412. if (!$isAdmin) {
  413. throw new OCSException('no group specified (required for subadmins)', 106);
  414. }
  415. }
  416. $subadminGroups = [];
  417. if ($subadmin !== []) {
  418. foreach ($subadmin as $groupid) {
  419. $group = $this->groupManager->get($groupid);
  420. // Check if group exists
  421. if ($group === null) {
  422. throw new OCSException('Subadmin group does not exist', 102);
  423. }
  424. // Check if trying to make subadmin of admin group
  425. if ($group->getGID() === 'admin') {
  426. throw new OCSException('Cannot create subadmins for admin group', 103);
  427. }
  428. // Check if has permission to promote subadmins
  429. if (!$subAdminManager->isSubAdminOfGroup($user, $group) && !$isAdmin) {
  430. throw new OCSForbiddenException('No permissions to promote subadmins');
  431. }
  432. $subadminGroups[] = $group;
  433. }
  434. }
  435. $generatePasswordResetToken = false;
  436. if (strlen($password) > IUserManager::MAX_PASSWORD_LENGTH) {
  437. throw new OCSException('Invalid password value', 101);
  438. }
  439. if ($password === '') {
  440. if ($email === '') {
  441. throw new OCSException('To send a password link to the user an email address is required.', 108);
  442. }
  443. $passwordEvent = new GenerateSecurePasswordEvent();
  444. $this->eventDispatcher->dispatchTyped($passwordEvent);
  445. $password = $passwordEvent->getPassword();
  446. if ($password === null) {
  447. // Fallback: ensure to pass password_policy in any case
  448. $password = $this->secureRandom->generate(10)
  449. . $this->secureRandom->generate(1, ISecureRandom::CHAR_UPPER)
  450. . $this->secureRandom->generate(1, ISecureRandom::CHAR_LOWER)
  451. . $this->secureRandom->generate(1, ISecureRandom::CHAR_DIGITS)
  452. . $this->secureRandom->generate(1, ISecureRandom::CHAR_SYMBOLS);
  453. }
  454. $generatePasswordResetToken = true;
  455. }
  456. if ($email === '' && $this->config->getAppValue('core', 'newUser.requireEmail', 'no') === 'yes') {
  457. throw new OCSException('Required email address was not provided', 110);
  458. }
  459. try {
  460. $newUser = $this->userManager->createUser($userid, $password);
  461. $this->logger->info('Successful addUser call with userid: ' . $userid, ['app' => 'ocs_api']);
  462. foreach ($groups as $group) {
  463. $this->groupManager->get($group)->addUser($newUser);
  464. $this->logger->info('Added userid ' . $userid . ' to group ' . $group, ['app' => 'ocs_api']);
  465. }
  466. foreach ($subadminGroups as $group) {
  467. $subAdminManager->createSubAdmin($newUser, $group);
  468. }
  469. if ($displayName !== '') {
  470. try {
  471. $this->editUser($userid, self::USER_FIELD_DISPLAYNAME, $displayName);
  472. } catch (OCSException $e) {
  473. if ($newUser instanceof IUser) {
  474. $newUser->delete();
  475. }
  476. throw $e;
  477. }
  478. }
  479. if ($quota !== '') {
  480. $this->editUser($userid, self::USER_FIELD_QUOTA, $quota);
  481. }
  482. if ($language !== '') {
  483. $this->editUser($userid, self::USER_FIELD_LANGUAGE, $language);
  484. }
  485. /**
  486. * null -> nothing sent
  487. * '' -> unset manager
  488. * else -> set manager
  489. */
  490. if ($manager !== null) {
  491. $this->editUser($userid, self::USER_FIELD_MANAGER, $manager);
  492. }
  493. // Send new user mail only if a mail is set
  494. if ($email !== '') {
  495. $newUser->setEMailAddress($email);
  496. if ($this->config->getAppValue('core', 'newUser.sendEmail', 'yes') === 'yes') {
  497. try {
  498. $emailTemplate = $this->newUserMailHelper->generateTemplate($newUser, $generatePasswordResetToken);
  499. $this->newUserMailHelper->sendMail($newUser, $emailTemplate);
  500. } catch (\Exception $e) {
  501. // Mail could be failing hard or just be plain not configured
  502. // Logging error as it is the hardest of the two
  503. $this->logger->error(
  504. "Unable to send the invitation mail to $email",
  505. [
  506. 'app' => 'ocs_api',
  507. 'exception' => $e,
  508. ]
  509. );
  510. }
  511. }
  512. }
  513. return new DataResponse(['id' => $userid]);
  514. } catch (HintException $e) {
  515. $this->logger->warning(
  516. 'Failed addUser attempt with hint exception.',
  517. [
  518. 'app' => 'ocs_api',
  519. 'exception' => $e,
  520. ]
  521. );
  522. throw new OCSException($e->getHint(), 107);
  523. } catch (OCSException $e) {
  524. $this->logger->warning(
  525. 'Failed addUser attempt with ocs exception.',
  526. [
  527. 'app' => 'ocs_api',
  528. 'exception' => $e,
  529. ]
  530. );
  531. throw $e;
  532. } catch (InvalidArgumentException $e) {
  533. $this->logger->error(
  534. 'Failed addUser attempt with invalid argument exception.',
  535. [
  536. 'app' => 'ocs_api',
  537. 'exception' => $e,
  538. ]
  539. );
  540. throw new OCSException($e->getMessage(), 101);
  541. } catch (\Exception $e) {
  542. $this->logger->error(
  543. 'Failed addUser attempt with exception.',
  544. [
  545. 'app' => 'ocs_api',
  546. 'exception' => $e
  547. ]
  548. );
  549. throw new OCSException('Bad request', 101);
  550. }
  551. }
  552. /**
  553. * @NoAdminRequired
  554. * @NoSubAdminRequired
  555. *
  556. * Get the details of a user
  557. *
  558. * @param string $userId ID of the user
  559. * @return DataResponse<Http::STATUS_OK, ProvisioningApiUserDetails, array{}>
  560. * @throws OCSException
  561. *
  562. * 200: User returned
  563. */
  564. public function getUser(string $userId): DataResponse {
  565. $includeScopes = false;
  566. $currentUser = $this->userSession->getUser();
  567. if ($currentUser && $currentUser->getUID() === $userId) {
  568. $includeScopes = true;
  569. }
  570. $data = $this->getUserData($userId, $includeScopes);
  571. // getUserData returns null if not enough permissions
  572. if ($data === null) {
  573. throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
  574. }
  575. return new DataResponse($data);
  576. }
  577. /**
  578. * @NoAdminRequired
  579. * @NoSubAdminRequired
  580. *
  581. * Get the details of the current user
  582. *
  583. * @return DataResponse<Http::STATUS_OK, ProvisioningApiUserDetails, array{}>
  584. * @throws OCSException
  585. *
  586. * 200: Current user returned
  587. */
  588. public function getCurrentUser(): DataResponse {
  589. $user = $this->userSession->getUser();
  590. if ($user) {
  591. /** @var ProvisioningApiUserDetails $data */
  592. $data = $this->getUserData($user->getUID(), true);
  593. return new DataResponse($data);
  594. }
  595. throw new OCSException('', OCSController::RESPOND_UNAUTHORISED);
  596. }
  597. /**
  598. * @NoAdminRequired
  599. * @NoSubAdminRequired
  600. *
  601. * Get a list of fields that are editable for the current user
  602. *
  603. * @return DataResponse<Http::STATUS_OK, string[], array{}>
  604. * @throws OCSException
  605. *
  606. * 200: Editable fields returned
  607. */
  608. public function getEditableFields(): DataResponse {
  609. $currentLoggedInUser = $this->userSession->getUser();
  610. if (!$currentLoggedInUser instanceof IUser) {
  611. throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
  612. }
  613. return $this->getEditableFieldsForUser($currentLoggedInUser->getUID());
  614. }
  615. /**
  616. * @NoAdminRequired
  617. * @NoSubAdminRequired
  618. *
  619. * Get a list of fields that are editable for a user
  620. *
  621. * @param string $userId ID of the user
  622. * @return DataResponse<Http::STATUS_OK, string[], array{}>
  623. * @throws OCSException
  624. *
  625. * 200: Editable fields for user returned
  626. */
  627. public function getEditableFieldsForUser(string $userId): DataResponse {
  628. $currentLoggedInUser = $this->userSession->getUser();
  629. if (!$currentLoggedInUser instanceof IUser) {
  630. throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
  631. }
  632. $permittedFields = [];
  633. if ($userId !== $currentLoggedInUser->getUID()) {
  634. $targetUser = $this->userManager->get($userId);
  635. if (!$targetUser instanceof IUser) {
  636. throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
  637. }
  638. $subAdminManager = $this->groupManager->getSubAdmin();
  639. if (
  640. !$this->groupManager->isAdmin($currentLoggedInUser->getUID())
  641. && !$subAdminManager->isUserAccessible($currentLoggedInUser, $targetUser)
  642. ) {
  643. throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
  644. }
  645. } else {
  646. $targetUser = $currentLoggedInUser;
  647. }
  648. // Editing self (display, email)
  649. if ($this->config->getSystemValue('allow_user_to_change_display_name', true) !== false) {
  650. if (
  651. $targetUser->getBackend() instanceof ISetDisplayNameBackend
  652. || $targetUser->getBackend()->implementsActions(Backend::SET_DISPLAYNAME)
  653. ) {
  654. $permittedFields[] = IAccountManager::PROPERTY_DISPLAYNAME;
  655. }
  656. $permittedFields[] = IAccountManager::PROPERTY_EMAIL;
  657. }
  658. $permittedFields[] = IAccountManager::COLLECTION_EMAIL;
  659. $permittedFields[] = IAccountManager::PROPERTY_PHONE;
  660. $permittedFields[] = IAccountManager::PROPERTY_ADDRESS;
  661. $permittedFields[] = IAccountManager::PROPERTY_WEBSITE;
  662. $permittedFields[] = IAccountManager::PROPERTY_TWITTER;
  663. $permittedFields[] = IAccountManager::PROPERTY_FEDIVERSE;
  664. $permittedFields[] = IAccountManager::PROPERTY_ORGANISATION;
  665. $permittedFields[] = IAccountManager::PROPERTY_ROLE;
  666. $permittedFields[] = IAccountManager::PROPERTY_HEADLINE;
  667. $permittedFields[] = IAccountManager::PROPERTY_BIOGRAPHY;
  668. $permittedFields[] = IAccountManager::PROPERTY_PROFILE_ENABLED;
  669. return new DataResponse($permittedFields);
  670. }
  671. /**
  672. * @NoAdminRequired
  673. * @NoSubAdminRequired
  674. * @PasswordConfirmationRequired
  675. * @UserRateThrottle(limit=5, period=60)
  676. *
  677. * Update multiple values of the user's details
  678. *
  679. * @param string $userId ID of the user
  680. * @param string $collectionName Collection to update
  681. * @param string $key Key that will be updated
  682. * @param string $value New value for the key
  683. * @return DataResponse<Http::STATUS_OK, array<empty>, array{}>
  684. * @throws OCSException
  685. *
  686. * 200: User values edited successfully
  687. */
  688. public function editUserMultiValue(
  689. string $userId,
  690. string $collectionName,
  691. string $key,
  692. string $value
  693. ): DataResponse {
  694. $currentLoggedInUser = $this->userSession->getUser();
  695. if ($currentLoggedInUser === null) {
  696. throw new OCSException('', OCSController::RESPOND_UNAUTHORISED);
  697. }
  698. $targetUser = $this->userManager->get($userId);
  699. if ($targetUser === null) {
  700. throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
  701. }
  702. $subAdminManager = $this->groupManager->getSubAdmin();
  703. $isAdminOrSubadmin = $this->groupManager->isAdmin($currentLoggedInUser->getUID())
  704. || $subAdminManager->isUserAccessible($currentLoggedInUser, $targetUser);
  705. $permittedFields = [];
  706. if ($targetUser->getUID() === $currentLoggedInUser->getUID()) {
  707. // Editing self (display, email)
  708. $permittedFields[] = IAccountManager::COLLECTION_EMAIL;
  709. $permittedFields[] = IAccountManager::COLLECTION_EMAIL . self::SCOPE_SUFFIX;
  710. } else {
  711. // Check if admin / subadmin
  712. if ($isAdminOrSubadmin) {
  713. // They have permissions over the user
  714. $permittedFields[] = IAccountManager::COLLECTION_EMAIL;
  715. } else {
  716. // No rights
  717. throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
  718. }
  719. }
  720. // Check if permitted to edit this field
  721. if (!in_array($collectionName, $permittedFields)) {
  722. throw new OCSException('', 103);
  723. }
  724. switch ($collectionName) {
  725. case IAccountManager::COLLECTION_EMAIL:
  726. $userAccount = $this->accountManager->getAccount($targetUser);
  727. $mailCollection = $userAccount->getPropertyCollection(IAccountManager::COLLECTION_EMAIL);
  728. $mailCollection->removePropertyByValue($key);
  729. if ($value !== '') {
  730. $mailCollection->addPropertyWithDefaults($value);
  731. $property = $mailCollection->getPropertyByValue($key);
  732. if ($isAdminOrSubadmin && $property) {
  733. // admin set mails are auto-verified
  734. $property->setLocallyVerified(IAccountManager::VERIFIED);
  735. }
  736. }
  737. $this->accountManager->updateAccount($userAccount);
  738. break;
  739. case IAccountManager::COLLECTION_EMAIL . self::SCOPE_SUFFIX:
  740. $userAccount = $this->accountManager->getAccount($targetUser);
  741. $mailCollection = $userAccount->getPropertyCollection(IAccountManager::COLLECTION_EMAIL);
  742. $targetProperty = null;
  743. foreach ($mailCollection->getProperties() as $property) {
  744. if ($property->getValue() === $key) {
  745. $targetProperty = $property;
  746. break;
  747. }
  748. }
  749. if ($targetProperty instanceof IAccountProperty) {
  750. try {
  751. $targetProperty->setScope($value);
  752. $this->accountManager->updateAccount($userAccount);
  753. } catch (InvalidArgumentException $e) {
  754. throw new OCSException('', 102);
  755. }
  756. } else {
  757. throw new OCSException('', 102);
  758. }
  759. break;
  760. default:
  761. throw new OCSException('', 103);
  762. }
  763. return new DataResponse();
  764. }
  765. /**
  766. * @NoAdminRequired
  767. * @NoSubAdminRequired
  768. * @PasswordConfirmationRequired
  769. * @UserRateThrottle(limit=50, period=600)
  770. *
  771. * Update a value of the user's details
  772. *
  773. * @param string $userId ID of the user
  774. * @param string $key Key that will be updated
  775. * @param string $value New value for the key
  776. * @return DataResponse<Http::STATUS_OK, array<empty>, array{}>
  777. * @throws OCSException
  778. *
  779. * 200: User value edited successfully
  780. */
  781. public function editUser(string $userId, string $key, string $value): DataResponse {
  782. $currentLoggedInUser = $this->userSession->getUser();
  783. $targetUser = $this->userManager->get($userId);
  784. if ($targetUser === null) {
  785. throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
  786. }
  787. $permittedFields = [];
  788. if ($targetUser->getUID() === $currentLoggedInUser->getUID()) {
  789. // Editing self (display, email)
  790. if ($this->config->getSystemValue('allow_user_to_change_display_name', true) !== false) {
  791. if (
  792. $targetUser->getBackend() instanceof ISetDisplayNameBackend
  793. || $targetUser->getBackend()->implementsActions(Backend::SET_DISPLAYNAME)
  794. ) {
  795. $permittedFields[] = self::USER_FIELD_DISPLAYNAME;
  796. $permittedFields[] = IAccountManager::PROPERTY_DISPLAYNAME;
  797. }
  798. $permittedFields[] = IAccountManager::PROPERTY_EMAIL;
  799. }
  800. $permittedFields[] = IAccountManager::PROPERTY_DISPLAYNAME . self::SCOPE_SUFFIX;
  801. $permittedFields[] = IAccountManager::PROPERTY_EMAIL . self::SCOPE_SUFFIX;
  802. $permittedFields[] = IAccountManager::COLLECTION_EMAIL;
  803. $permittedFields[] = self::USER_FIELD_PASSWORD;
  804. $permittedFields[] = self::USER_FIELD_NOTIFICATION_EMAIL;
  805. if (
  806. $this->config->getSystemValue('force_language', false) === false ||
  807. $this->groupManager->isAdmin($currentLoggedInUser->getUID())
  808. ) {
  809. $permittedFields[] = self::USER_FIELD_LANGUAGE;
  810. }
  811. if (
  812. $this->config->getSystemValue('force_locale', false) === false ||
  813. $this->groupManager->isAdmin($currentLoggedInUser->getUID())
  814. ) {
  815. $permittedFields[] = self::USER_FIELD_LOCALE;
  816. }
  817. $permittedFields[] = IAccountManager::PROPERTY_PHONE;
  818. $permittedFields[] = IAccountManager::PROPERTY_ADDRESS;
  819. $permittedFields[] = IAccountManager::PROPERTY_WEBSITE;
  820. $permittedFields[] = IAccountManager::PROPERTY_TWITTER;
  821. $permittedFields[] = IAccountManager::PROPERTY_FEDIVERSE;
  822. $permittedFields[] = IAccountManager::PROPERTY_ORGANISATION;
  823. $permittedFields[] = IAccountManager::PROPERTY_ROLE;
  824. $permittedFields[] = IAccountManager::PROPERTY_HEADLINE;
  825. $permittedFields[] = IAccountManager::PROPERTY_BIOGRAPHY;
  826. $permittedFields[] = IAccountManager::PROPERTY_PROFILE_ENABLED;
  827. $permittedFields[] = IAccountManager::PROPERTY_PHONE . self::SCOPE_SUFFIX;
  828. $permittedFields[] = IAccountManager::PROPERTY_ADDRESS . self::SCOPE_SUFFIX;
  829. $permittedFields[] = IAccountManager::PROPERTY_WEBSITE . self::SCOPE_SUFFIX;
  830. $permittedFields[] = IAccountManager::PROPERTY_TWITTER . self::SCOPE_SUFFIX;
  831. $permittedFields[] = IAccountManager::PROPERTY_FEDIVERSE . self::SCOPE_SUFFIX;
  832. $permittedFields[] = IAccountManager::PROPERTY_ORGANISATION . self::SCOPE_SUFFIX;
  833. $permittedFields[] = IAccountManager::PROPERTY_ROLE . self::SCOPE_SUFFIX;
  834. $permittedFields[] = IAccountManager::PROPERTY_HEADLINE . self::SCOPE_SUFFIX;
  835. $permittedFields[] = IAccountManager::PROPERTY_BIOGRAPHY . self::SCOPE_SUFFIX;
  836. $permittedFields[] = IAccountManager::PROPERTY_PROFILE_ENABLED . self::SCOPE_SUFFIX;
  837. $permittedFields[] = IAccountManager::PROPERTY_AVATAR . self::SCOPE_SUFFIX;
  838. // If admin they can edit their own quota and manager
  839. if ($this->groupManager->isAdmin($currentLoggedInUser->getUID())) {
  840. $permittedFields[] = self::USER_FIELD_QUOTA;
  841. $permittedFields[] = self::USER_FIELD_MANAGER;
  842. }
  843. } else {
  844. // Check if admin / subadmin
  845. $subAdminManager = $this->groupManager->getSubAdmin();
  846. if (
  847. $this->groupManager->isAdmin($currentLoggedInUser->getUID())
  848. || $subAdminManager->isUserAccessible($currentLoggedInUser, $targetUser)
  849. ) {
  850. // They have permissions over the user
  851. if (
  852. $targetUser->getBackend() instanceof ISetDisplayNameBackend
  853. || $targetUser->getBackend()->implementsActions(Backend::SET_DISPLAYNAME)
  854. ) {
  855. $permittedFields[] = self::USER_FIELD_DISPLAYNAME;
  856. $permittedFields[] = IAccountManager::PROPERTY_DISPLAYNAME;
  857. }
  858. $permittedFields[] = IAccountManager::PROPERTY_EMAIL;
  859. $permittedFields[] = IAccountManager::COLLECTION_EMAIL;
  860. $permittedFields[] = self::USER_FIELD_PASSWORD;
  861. $permittedFields[] = self::USER_FIELD_LANGUAGE;
  862. $permittedFields[] = self::USER_FIELD_LOCALE;
  863. $permittedFields[] = IAccountManager::PROPERTY_PHONE;
  864. $permittedFields[] = IAccountManager::PROPERTY_ADDRESS;
  865. $permittedFields[] = IAccountManager::PROPERTY_WEBSITE;
  866. $permittedFields[] = IAccountManager::PROPERTY_TWITTER;
  867. $permittedFields[] = IAccountManager::PROPERTY_FEDIVERSE;
  868. $permittedFields[] = IAccountManager::PROPERTY_ORGANISATION;
  869. $permittedFields[] = IAccountManager::PROPERTY_ROLE;
  870. $permittedFields[] = IAccountManager::PROPERTY_HEADLINE;
  871. $permittedFields[] = IAccountManager::PROPERTY_BIOGRAPHY;
  872. $permittedFields[] = IAccountManager::PROPERTY_PROFILE_ENABLED;
  873. $permittedFields[] = self::USER_FIELD_QUOTA;
  874. $permittedFields[] = self::USER_FIELD_NOTIFICATION_EMAIL;
  875. $permittedFields[] = self::USER_FIELD_MANAGER;
  876. } else {
  877. // No rights
  878. throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
  879. }
  880. }
  881. // Check if permitted to edit this field
  882. if (!in_array($key, $permittedFields)) {
  883. throw new OCSException('', 103);
  884. }
  885. // Process the edit
  886. switch ($key) {
  887. case self::USER_FIELD_DISPLAYNAME:
  888. case IAccountManager::PROPERTY_DISPLAYNAME:
  889. try {
  890. $targetUser->setDisplayName($value);
  891. } catch (InvalidArgumentException $e) {
  892. throw new OCSException($e->getMessage(), 101);
  893. }
  894. break;
  895. case self::USER_FIELD_QUOTA:
  896. $quota = $value;
  897. if ($quota !== 'none' && $quota !== 'default') {
  898. if (is_numeric($quota)) {
  899. $quota = (float) $quota;
  900. } else {
  901. $quota = \OCP\Util::computerFileSize($quota);
  902. }
  903. if ($quota === false) {
  904. throw new OCSException('Invalid quota value ' . $value, 102);
  905. }
  906. if ($quota === -1) {
  907. $quota = 'none';
  908. } else {
  909. $maxQuota = (int) $this->config->getAppValue('files', 'max_quota', '-1');
  910. if ($maxQuota !== -1 && $quota > $maxQuota) {
  911. throw new OCSException('Invalid quota value. ' . $value . ' is exceeding the maximum quota', 102);
  912. }
  913. $quota = \OCP\Util::humanFileSize($quota);
  914. }
  915. }
  916. // no else block because quota can be set to 'none' in previous if
  917. if ($quota === 'none') {
  918. $allowUnlimitedQuota = $this->config->getAppValue('files', 'allow_unlimited_quota', '1') === '1';
  919. if (!$allowUnlimitedQuota) {
  920. throw new OCSException('Unlimited quota is forbidden on this instance', 102);
  921. }
  922. }
  923. $targetUser->setQuota($quota);
  924. break;
  925. case self::USER_FIELD_MANAGER:
  926. $targetUser->setManagerUids([$value]);
  927. break;
  928. case self::USER_FIELD_PASSWORD:
  929. try {
  930. if (strlen($value) > IUserManager::MAX_PASSWORD_LENGTH) {
  931. throw new OCSException('Invalid password value', 102);
  932. }
  933. if (!$targetUser->canChangePassword()) {
  934. throw new OCSException('Setting the password is not supported by the users backend', 103);
  935. }
  936. $targetUser->setPassword($value);
  937. } catch (HintException $e) { // password policy error
  938. throw new OCSException($e->getMessage(), 103);
  939. }
  940. break;
  941. case self::USER_FIELD_LANGUAGE:
  942. $languagesCodes = $this->l10nFactory->findAvailableLanguages();
  943. if (!in_array($value, $languagesCodes, true) && $value !== 'en') {
  944. throw new OCSException('Invalid language', 102);
  945. }
  946. $this->config->setUserValue($targetUser->getUID(), 'core', 'lang', $value);
  947. break;
  948. case self::USER_FIELD_LOCALE:
  949. if (!$this->l10nFactory->localeExists($value)) {
  950. throw new OCSException('Invalid locale', 102);
  951. }
  952. $this->config->setUserValue($targetUser->getUID(), 'core', 'locale', $value);
  953. break;
  954. case self::USER_FIELD_NOTIFICATION_EMAIL:
  955. $success = false;
  956. if ($value === '' || filter_var($value, FILTER_VALIDATE_EMAIL)) {
  957. try {
  958. $targetUser->setPrimaryEMailAddress($value);
  959. $success = true;
  960. } catch (InvalidArgumentException $e) {
  961. $this->logger->info(
  962. 'Cannot set primary email, because provided address is not verified',
  963. [
  964. 'app' => 'provisioning_api',
  965. 'exception' => $e,
  966. ]
  967. );
  968. }
  969. }
  970. if (!$success) {
  971. throw new OCSException('', 102);
  972. }
  973. break;
  974. case IAccountManager::PROPERTY_EMAIL:
  975. if (filter_var($value, FILTER_VALIDATE_EMAIL) || $value === '') {
  976. $targetUser->setEMailAddress($value);
  977. } else {
  978. throw new OCSException('', 102);
  979. }
  980. break;
  981. case IAccountManager::COLLECTION_EMAIL:
  982. if (filter_var($value, FILTER_VALIDATE_EMAIL) && $value !== $targetUser->getSystemEMailAddress()) {
  983. $userAccount = $this->accountManager->getAccount($targetUser);
  984. $mailCollection = $userAccount->getPropertyCollection(IAccountManager::COLLECTION_EMAIL);
  985. if ($mailCollection->getPropertyByValue($value)) {
  986. throw new OCSException('', 102);
  987. }
  988. $mailCollection->addPropertyWithDefaults($value);
  989. $this->accountManager->updateAccount($userAccount);
  990. } else {
  991. throw new OCSException('', 102);
  992. }
  993. break;
  994. case IAccountManager::PROPERTY_PHONE:
  995. case IAccountManager::PROPERTY_ADDRESS:
  996. case IAccountManager::PROPERTY_WEBSITE:
  997. case IAccountManager::PROPERTY_TWITTER:
  998. case IAccountManager::PROPERTY_FEDIVERSE:
  999. case IAccountManager::PROPERTY_ORGANISATION:
  1000. case IAccountManager::PROPERTY_ROLE:
  1001. case IAccountManager::PROPERTY_HEADLINE:
  1002. case IAccountManager::PROPERTY_BIOGRAPHY:
  1003. $userAccount = $this->accountManager->getAccount($targetUser);
  1004. try {
  1005. $userProperty = $userAccount->getProperty($key);
  1006. if ($userProperty->getValue() !== $value) {
  1007. try {
  1008. $userProperty->setValue($value);
  1009. if ($userProperty->getName() === IAccountManager::PROPERTY_PHONE) {
  1010. $this->knownUserService->deleteByContactUserId($targetUser->getUID());
  1011. }
  1012. } catch (InvalidArgumentException $e) {
  1013. throw new OCSException('Invalid ' . $e->getMessage(), 102);
  1014. }
  1015. }
  1016. } catch (PropertyDoesNotExistException $e) {
  1017. $userAccount->setProperty($key, $value, IAccountManager::SCOPE_PRIVATE, IAccountManager::NOT_VERIFIED);
  1018. }
  1019. try {
  1020. $this->accountManager->updateAccount($userAccount);
  1021. } catch (InvalidArgumentException $e) {
  1022. throw new OCSException('Invalid ' . $e->getMessage(), 102);
  1023. }
  1024. break;
  1025. case IAccountManager::PROPERTY_PROFILE_ENABLED:
  1026. $userAccount = $this->accountManager->getAccount($targetUser);
  1027. try {
  1028. $userProperty = $userAccount->getProperty($key);
  1029. if ($userProperty->getValue() !== $value) {
  1030. $userProperty->setValue($value);
  1031. }
  1032. } catch (PropertyDoesNotExistException $e) {
  1033. $userAccount->setProperty($key, $value, IAccountManager::SCOPE_LOCAL, IAccountManager::NOT_VERIFIED);
  1034. }
  1035. $this->accountManager->updateAccount($userAccount);
  1036. break;
  1037. case IAccountManager::PROPERTY_DISPLAYNAME . self::SCOPE_SUFFIX:
  1038. case IAccountManager::PROPERTY_EMAIL . self::SCOPE_SUFFIX:
  1039. case IAccountManager::PROPERTY_PHONE . self::SCOPE_SUFFIX:
  1040. case IAccountManager::PROPERTY_ADDRESS . self::SCOPE_SUFFIX:
  1041. case IAccountManager::PROPERTY_WEBSITE . self::SCOPE_SUFFIX:
  1042. case IAccountManager::PROPERTY_TWITTER . self::SCOPE_SUFFIX:
  1043. case IAccountManager::PROPERTY_FEDIVERSE . self::SCOPE_SUFFIX:
  1044. case IAccountManager::PROPERTY_ORGANISATION . self::SCOPE_SUFFIX:
  1045. case IAccountManager::PROPERTY_ROLE . self::SCOPE_SUFFIX:
  1046. case IAccountManager::PROPERTY_HEADLINE . self::SCOPE_SUFFIX:
  1047. case IAccountManager::PROPERTY_BIOGRAPHY . self::SCOPE_SUFFIX:
  1048. case IAccountManager::PROPERTY_PROFILE_ENABLED . self::SCOPE_SUFFIX:
  1049. case IAccountManager::PROPERTY_AVATAR . self::SCOPE_SUFFIX:
  1050. $propertyName = substr($key, 0, strlen($key) - strlen(self::SCOPE_SUFFIX));
  1051. $userAccount = $this->accountManager->getAccount($targetUser);
  1052. $userProperty = $userAccount->getProperty($propertyName);
  1053. if ($userProperty->getScope() !== $value) {
  1054. try {
  1055. $userProperty->setScope($value);
  1056. $this->accountManager->updateAccount($userAccount);
  1057. } catch (InvalidArgumentException $e) {
  1058. throw new OCSException('Invalid ' . $e->getMessage(), 102);
  1059. }
  1060. }
  1061. break;
  1062. default:
  1063. throw new OCSException('', 103);
  1064. }
  1065. return new DataResponse();
  1066. }
  1067. /**
  1068. * @PasswordConfirmationRequired
  1069. * @NoAdminRequired
  1070. *
  1071. * Wipe all devices of a user
  1072. *
  1073. * @param string $userId ID of the user
  1074. *
  1075. * @return DataResponse<Http::STATUS_OK, array<empty>, array{}>
  1076. *
  1077. * @throws OCSException
  1078. *
  1079. * 200: Wiped all user devices successfully
  1080. */
  1081. public function wipeUserDevices(string $userId): DataResponse {
  1082. /** @var IUser $currentLoggedInUser */
  1083. $currentLoggedInUser = $this->userSession->getUser();
  1084. $targetUser = $this->userManager->get($userId);
  1085. if ($targetUser === null) {
  1086. throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
  1087. }
  1088. if ($targetUser->getUID() === $currentLoggedInUser->getUID()) {
  1089. throw new OCSException('', 101);
  1090. }
  1091. // If not permitted
  1092. $subAdminManager = $this->groupManager->getSubAdmin();
  1093. if (!$this->groupManager->isAdmin($currentLoggedInUser->getUID()) && !$subAdminManager->isUserAccessible($currentLoggedInUser, $targetUser)) {
  1094. throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
  1095. }
  1096. $this->remoteWipe->markAllTokensForWipe($targetUser);
  1097. return new DataResponse();
  1098. }
  1099. /**
  1100. * @PasswordConfirmationRequired
  1101. * @NoAdminRequired
  1102. *
  1103. * Delete a user
  1104. *
  1105. * @param string $userId ID of the user
  1106. * @return DataResponse<Http::STATUS_OK, array<empty>, array{}>
  1107. * @throws OCSException
  1108. *
  1109. * 200: User deleted successfully
  1110. */
  1111. public function deleteUser(string $userId): DataResponse {
  1112. $currentLoggedInUser = $this->userSession->getUser();
  1113. $targetUser = $this->userManager->get($userId);
  1114. if ($targetUser === null) {
  1115. throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
  1116. }
  1117. if ($targetUser->getUID() === $currentLoggedInUser->getUID()) {
  1118. throw new OCSException('', 101);
  1119. }
  1120. // If not permitted
  1121. $subAdminManager = $this->groupManager->getSubAdmin();
  1122. if (!$this->groupManager->isAdmin($currentLoggedInUser->getUID()) && !$subAdminManager->isUserAccessible($currentLoggedInUser, $targetUser)) {
  1123. throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
  1124. }
  1125. // Go ahead with the delete
  1126. if ($targetUser->delete()) {
  1127. return new DataResponse();
  1128. } else {
  1129. throw new OCSException('', 101);
  1130. }
  1131. }
  1132. /**
  1133. * @PasswordConfirmationRequired
  1134. * @NoAdminRequired
  1135. *
  1136. * Disable a user
  1137. *
  1138. * @param string $userId ID of the user
  1139. * @return DataResponse<Http::STATUS_OK, array<empty>, array{}>
  1140. * @throws OCSException
  1141. *
  1142. * 200: User disabled successfully
  1143. */
  1144. public function disableUser(string $userId): DataResponse {
  1145. return $this->setEnabled($userId, false);
  1146. }
  1147. /**
  1148. * @PasswordConfirmationRequired
  1149. * @NoAdminRequired
  1150. *
  1151. * Enable a user
  1152. *
  1153. * @param string $userId ID of the user
  1154. * @return DataResponse<Http::STATUS_OK, array<empty>, array{}>
  1155. * @throws OCSException
  1156. *
  1157. * 200: User enabled successfully
  1158. */
  1159. public function enableUser(string $userId): DataResponse {
  1160. return $this->setEnabled($userId, true);
  1161. }
  1162. /**
  1163. * @param string $userId
  1164. * @param bool $value
  1165. * @return DataResponse<Http::STATUS_OK, array<empty>, array{}>
  1166. * @throws OCSException
  1167. */
  1168. private function setEnabled(string $userId, bool $value): DataResponse {
  1169. $currentLoggedInUser = $this->userSession->getUser();
  1170. $targetUser = $this->userManager->get($userId);
  1171. if ($targetUser === null || $targetUser->getUID() === $currentLoggedInUser->getUID()) {
  1172. throw new OCSException('', 101);
  1173. }
  1174. // If not permitted
  1175. $subAdminManager = $this->groupManager->getSubAdmin();
  1176. if (!$this->groupManager->isAdmin($currentLoggedInUser->getUID()) && !$subAdminManager->isUserAccessible($currentLoggedInUser, $targetUser)) {
  1177. throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
  1178. }
  1179. // enable/disable the user now
  1180. $targetUser->setEnabled($value);
  1181. return new DataResponse();
  1182. }
  1183. /**
  1184. * @NoAdminRequired
  1185. * @NoSubAdminRequired
  1186. *
  1187. * Get a list of groups the user belongs to
  1188. *
  1189. * @param string $userId ID of the user
  1190. * @return DataResponse<Http::STATUS_OK, array{groups: string[]}, array{}>
  1191. * @throws OCSException
  1192. *
  1193. * 200: Users groups returned
  1194. */
  1195. public function getUsersGroups(string $userId): DataResponse {
  1196. $loggedInUser = $this->userSession->getUser();
  1197. $targetUser = $this->userManager->get($userId);
  1198. if ($targetUser === null) {
  1199. throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
  1200. }
  1201. if ($targetUser->getUID() === $loggedInUser->getUID() || $this->groupManager->isAdmin($loggedInUser->getUID())) {
  1202. // Self lookup or admin lookup
  1203. return new DataResponse([
  1204. 'groups' => $this->groupManager->getUserGroupIds($targetUser)
  1205. ]);
  1206. } else {
  1207. $subAdminManager = $this->groupManager->getSubAdmin();
  1208. // Looking up someone else
  1209. if ($subAdminManager->isUserAccessible($loggedInUser, $targetUser)) {
  1210. // Return the group that the method caller is subadmin of for the user in question
  1211. /** @var IGroup[] $getSubAdminsGroups */
  1212. $getSubAdminsGroups = $subAdminManager->getSubAdminsGroups($loggedInUser);
  1213. foreach ($getSubAdminsGroups as $key => $group) {
  1214. $getSubAdminsGroups[$key] = $group->getGID();
  1215. }
  1216. /** @var string[] $groups */
  1217. $groups = array_intersect(
  1218. $getSubAdminsGroups,
  1219. $this->groupManager->getUserGroupIds($targetUser)
  1220. );
  1221. return new DataResponse(['groups' => $groups]);
  1222. } else {
  1223. // Not permitted
  1224. throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
  1225. }
  1226. }
  1227. }
  1228. /**
  1229. * @PasswordConfirmationRequired
  1230. * @NoAdminRequired
  1231. *
  1232. * Add a user to a group
  1233. *
  1234. * @param string $userId ID of the user
  1235. * @param string $groupid ID of the group
  1236. * @return DataResponse<Http::STATUS_OK, array<empty>, array{}>
  1237. * @throws OCSException
  1238. *
  1239. * 200: User added to group successfully
  1240. */
  1241. public function addToGroup(string $userId, string $groupid = ''): DataResponse {
  1242. if ($groupid === '') {
  1243. throw new OCSException('', 101);
  1244. }
  1245. $group = $this->groupManager->get($groupid);
  1246. $targetUser = $this->userManager->get($userId);
  1247. if ($group === null) {
  1248. throw new OCSException('', 102);
  1249. }
  1250. if ($targetUser === null) {
  1251. throw new OCSException('', 103);
  1252. }
  1253. // If they're not an admin, check they are a subadmin of the group in question
  1254. $loggedInUser = $this->userSession->getUser();
  1255. $subAdminManager = $this->groupManager->getSubAdmin();
  1256. if (!$this->groupManager->isAdmin($loggedInUser->getUID()) && !$subAdminManager->isSubAdminOfGroup($loggedInUser, $group)) {
  1257. throw new OCSException('', 104);
  1258. }
  1259. // Add user to group
  1260. $group->addUser($targetUser);
  1261. return new DataResponse();
  1262. }
  1263. /**
  1264. * @PasswordConfirmationRequired
  1265. * @NoAdminRequired
  1266. *
  1267. * Remove a user from a group
  1268. *
  1269. * @param string $userId ID of the user
  1270. * @param string $groupid ID of the group
  1271. * @return DataResponse<Http::STATUS_OK, array<empty>, array{}>
  1272. * @throws OCSException
  1273. *
  1274. * 200: User removed from group successfully
  1275. */
  1276. public function removeFromGroup(string $userId, string $groupid): DataResponse {
  1277. $loggedInUser = $this->userSession->getUser();
  1278. if ($groupid === null || trim($groupid) === '') {
  1279. throw new OCSException('', 101);
  1280. }
  1281. $group = $this->groupManager->get($groupid);
  1282. if ($group === null) {
  1283. throw new OCSException('', 102);
  1284. }
  1285. $targetUser = $this->userManager->get($userId);
  1286. if ($targetUser === null) {
  1287. throw new OCSException('', 103);
  1288. }
  1289. // If they're not an admin, check they are a subadmin of the group in question
  1290. $subAdminManager = $this->groupManager->getSubAdmin();
  1291. if (!$this->groupManager->isAdmin($loggedInUser->getUID()) && !$subAdminManager->isSubAdminOfGroup($loggedInUser, $group)) {
  1292. throw new OCSException('', 104);
  1293. }
  1294. // Check they aren't removing themselves from 'admin' or their 'subadmin; group
  1295. if ($targetUser->getUID() === $loggedInUser->getUID()) {
  1296. if ($this->groupManager->isAdmin($loggedInUser->getUID())) {
  1297. if ($group->getGID() === 'admin') {
  1298. throw new OCSException('Cannot remove yourself from the admin group', 105);
  1299. }
  1300. } else {
  1301. // Not an admin, so the user must be a subadmin of this group, but that is not allowed.
  1302. throw new OCSException('Cannot remove yourself from this group as you are a SubAdmin', 105);
  1303. }
  1304. } elseif (!$this->groupManager->isAdmin($loggedInUser->getUID())) {
  1305. /** @var IGroup[] $subAdminGroups */
  1306. $subAdminGroups = $subAdminManager->getSubAdminsGroups($loggedInUser);
  1307. $subAdminGroups = array_map(function (IGroup $subAdminGroup) {
  1308. return $subAdminGroup->getGID();
  1309. }, $subAdminGroups);
  1310. $userGroups = $this->groupManager->getUserGroupIds($targetUser);
  1311. $userSubAdminGroups = array_intersect($subAdminGroups, $userGroups);
  1312. if (count($userSubAdminGroups) <= 1) {
  1313. // Subadmin must not be able to remove a user from all their subadmin groups.
  1314. throw new OCSException('Not viable to remove user from the last group you are SubAdmin of', 105);
  1315. }
  1316. }
  1317. // Remove user from group
  1318. $group->removeUser($targetUser);
  1319. return new DataResponse();
  1320. }
  1321. /**
  1322. * @PasswordConfirmationRequired
  1323. *
  1324. * Make a user a subadmin of a group
  1325. *
  1326. * @param string $userId ID of the user
  1327. * @param string $groupid ID of the group
  1328. * @return DataResponse<Http::STATUS_OK, array<empty>, array{}>
  1329. * @throws OCSException
  1330. *
  1331. * 200: User added as group subadmin successfully
  1332. */
  1333. public function addSubAdmin(string $userId, string $groupid): DataResponse {
  1334. $group = $this->groupManager->get($groupid);
  1335. $user = $this->userManager->get($userId);
  1336. // Check if the user exists
  1337. if ($user === null) {
  1338. throw new OCSException('User does not exist', 101);
  1339. }
  1340. // Check if group exists
  1341. if ($group === null) {
  1342. throw new OCSException('Group does not exist', 102);
  1343. }
  1344. // Check if trying to make subadmin of admin group
  1345. if ($group->getGID() === 'admin') {
  1346. throw new OCSException('Cannot create subadmins for admin group', 103);
  1347. }
  1348. $subAdminManager = $this->groupManager->getSubAdmin();
  1349. // We cannot be subadmin twice
  1350. if ($subAdminManager->isSubAdminOfGroup($user, $group)) {
  1351. return new DataResponse();
  1352. }
  1353. // Go
  1354. $subAdminManager->createSubAdmin($user, $group);
  1355. return new DataResponse();
  1356. }
  1357. /**
  1358. * @PasswordConfirmationRequired
  1359. *
  1360. * Remove a user from the subadmins of a group
  1361. *
  1362. * @param string $userId ID of the user
  1363. * @param string $groupid ID of the group
  1364. * @return DataResponse<Http::STATUS_OK, array<empty>, array{}>
  1365. * @throws OCSException
  1366. *
  1367. * 200: User removed as group subadmin successfully
  1368. */
  1369. public function removeSubAdmin(string $userId, string $groupid): DataResponse {
  1370. $group = $this->groupManager->get($groupid);
  1371. $user = $this->userManager->get($userId);
  1372. $subAdminManager = $this->groupManager->getSubAdmin();
  1373. // Check if the user exists
  1374. if ($user === null) {
  1375. throw new OCSException('User does not exist', 101);
  1376. }
  1377. // Check if the group exists
  1378. if ($group === null) {
  1379. throw new OCSException('Group does not exist', 101);
  1380. }
  1381. // Check if they are a subadmin of this said group
  1382. if (!$subAdminManager->isSubAdminOfGroup($user, $group)) {
  1383. throw new OCSException('User is not a subadmin of this group', 102);
  1384. }
  1385. // Go
  1386. $subAdminManager->deleteSubAdmin($user, $group);
  1387. return new DataResponse();
  1388. }
  1389. /**
  1390. * Get the groups a user is a subadmin of
  1391. *
  1392. * @param string $userId ID if the user
  1393. * @return DataResponse<Http::STATUS_OK, string[], array{}>
  1394. * @throws OCSException
  1395. *
  1396. * 200: User subadmin groups returned
  1397. */
  1398. public function getUserSubAdminGroups(string $userId): DataResponse {
  1399. $groups = $this->getUserSubAdminGroupsData($userId);
  1400. return new DataResponse($groups);
  1401. }
  1402. /**
  1403. * @NoAdminRequired
  1404. * @PasswordConfirmationRequired
  1405. *
  1406. * Resend the welcome message
  1407. *
  1408. * @param string $userId ID if the user
  1409. * @return DataResponse<Http::STATUS_OK, array<empty>, array{}>
  1410. * @throws OCSException
  1411. *
  1412. * 200: Resent welcome message successfully
  1413. */
  1414. public function resendWelcomeMessage(string $userId): DataResponse {
  1415. $currentLoggedInUser = $this->userSession->getUser();
  1416. $targetUser = $this->userManager->get($userId);
  1417. if ($targetUser === null) {
  1418. throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
  1419. }
  1420. // Check if admin / subadmin
  1421. $subAdminManager = $this->groupManager->getSubAdmin();
  1422. if (
  1423. !$subAdminManager->isUserAccessible($currentLoggedInUser, $targetUser)
  1424. && !$this->groupManager->isAdmin($currentLoggedInUser->getUID())
  1425. ) {
  1426. // No rights
  1427. throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
  1428. }
  1429. $email = $targetUser->getEMailAddress();
  1430. if ($email === '' || $email === null) {
  1431. throw new OCSException('Email address not available', 101);
  1432. }
  1433. try {
  1434. $emailTemplate = $this->newUserMailHelper->generateTemplate($targetUser, false);
  1435. $this->newUserMailHelper->sendMail($targetUser, $emailTemplate);
  1436. } catch (\Exception $e) {
  1437. $this->logger->error(
  1438. "Can't send new user mail to $email",
  1439. [
  1440. 'app' => 'settings',
  1441. 'exception' => $e,
  1442. ]
  1443. );
  1444. throw new OCSException('Sending email failed', 102);
  1445. }
  1446. return new DataResponse();
  1447. }
  1448. }