User.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596
  1. <?php
  2. /**
  3. * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
  4. * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
  5. * SPDX-License-Identifier: AGPL-3.0-only
  6. */
  7. namespace OC\User;
  8. use InvalidArgumentException;
  9. use OC\Accounts\AccountManager;
  10. use OC\Avatar\AvatarManager;
  11. use OC\Hooks\Emitter;
  12. use OC_Helper;
  13. use OCP\Accounts\IAccountManager;
  14. use OCP\Comments\ICommentsManager;
  15. use OCP\EventDispatcher\IEventDispatcher;
  16. use OCP\Group\Events\BeforeUserRemovedEvent;
  17. use OCP\Group\Events\UserRemovedEvent;
  18. use OCP\IAvatarManager;
  19. use OCP\IConfig;
  20. use OCP\IImage;
  21. use OCP\IURLGenerator;
  22. use OCP\IUser;
  23. use OCP\IUserBackend;
  24. use OCP\Notification\IManager as INotificationManager;
  25. use OCP\User\Backend\IGetHomeBackend;
  26. use OCP\User\Backend\IProvideAvatarBackend;
  27. use OCP\User\Backend\IProvideEnabledStateBackend;
  28. use OCP\User\Backend\ISetDisplayNameBackend;
  29. use OCP\User\Backend\ISetPasswordBackend;
  30. use OCP\User\Events\BeforePasswordUpdatedEvent;
  31. use OCP\User\Events\BeforeUserDeletedEvent;
  32. use OCP\User\Events\PasswordUpdatedEvent;
  33. use OCP\User\Events\UserChangedEvent;
  34. use OCP\User\Events\UserDeletedEvent;
  35. use OCP\User\GetQuotaEvent;
  36. use OCP\UserInterface;
  37. use function json_decode;
  38. use function json_encode;
  39. class User implements IUser {
  40. private const CONFIG_KEY_MANAGERS = 'manager';
  41. /** @var IAccountManager */
  42. protected $accountManager;
  43. /** @var string */
  44. private $uid;
  45. /** @var string|null */
  46. private $displayName;
  47. /** @var UserInterface|null */
  48. private $backend;
  49. /** @var IEventDispatcher */
  50. private $dispatcher;
  51. /** @var bool|null */
  52. private $enabled;
  53. /** @var Emitter|Manager */
  54. private $emitter;
  55. /** @var string */
  56. private $home;
  57. /** @var int|null */
  58. private $lastLogin;
  59. /** @var \OCP\IConfig */
  60. private $config;
  61. /** @var IAvatarManager */
  62. private $avatarManager;
  63. /** @var IURLGenerator */
  64. private $urlGenerator;
  65. public function __construct(string $uid, ?UserInterface $backend, IEventDispatcher $dispatcher, $emitter = null, ?IConfig $config = null, $urlGenerator = null) {
  66. $this->uid = $uid;
  67. $this->backend = $backend;
  68. $this->emitter = $emitter;
  69. if (is_null($config)) {
  70. $config = \OC::$server->getConfig();
  71. }
  72. $this->config = $config;
  73. $this->urlGenerator = $urlGenerator;
  74. if (is_null($this->urlGenerator)) {
  75. $this->urlGenerator = \OC::$server->getURLGenerator();
  76. }
  77. $this->dispatcher = $dispatcher;
  78. }
  79. /**
  80. * get the user id
  81. *
  82. * @return string
  83. */
  84. public function getUID() {
  85. return $this->uid;
  86. }
  87. /**
  88. * get the display name for the user, if no specific display name is set it will fallback to the user id
  89. *
  90. * @return string
  91. */
  92. public function getDisplayName() {
  93. if ($this->displayName === null) {
  94. $displayName = '';
  95. if ($this->backend && $this->backend->implementsActions(Backend::GET_DISPLAYNAME)) {
  96. // get display name and strip whitespace from the beginning and end of it
  97. $backendDisplayName = $this->backend->getDisplayName($this->uid);
  98. if (is_string($backendDisplayName)) {
  99. $displayName = trim($backendDisplayName);
  100. }
  101. }
  102. if (!empty($displayName)) {
  103. $this->displayName = $displayName;
  104. } else {
  105. $this->displayName = $this->uid;
  106. }
  107. }
  108. return $this->displayName;
  109. }
  110. /**
  111. * set the displayname for the user
  112. *
  113. * @param string $displayName
  114. * @return bool
  115. *
  116. * @since 25.0.0 Throw InvalidArgumentException
  117. * @throws \InvalidArgumentException
  118. */
  119. public function setDisplayName($displayName) {
  120. $displayName = trim($displayName);
  121. $oldDisplayName = $this->getDisplayName();
  122. if ($this->backend->implementsActions(Backend::SET_DISPLAYNAME) && !empty($displayName) && $displayName !== $oldDisplayName) {
  123. /** @var ISetDisplayNameBackend $backend */
  124. $backend = $this->backend;
  125. $result = $backend->setDisplayName($this->uid, $displayName);
  126. if ($result) {
  127. $this->displayName = $displayName;
  128. $this->triggerChange('displayName', $displayName, $oldDisplayName);
  129. }
  130. return $result !== false;
  131. }
  132. return false;
  133. }
  134. /**
  135. * @inheritDoc
  136. */
  137. public function setEMailAddress($mailAddress) {
  138. $this->setSystemEMailAddress($mailAddress);
  139. }
  140. /**
  141. * @inheritDoc
  142. */
  143. public function setSystemEMailAddress(string $mailAddress): void {
  144. $oldMailAddress = $this->getSystemEMailAddress();
  145. if ($mailAddress === '') {
  146. $this->config->deleteUserValue($this->uid, 'settings', 'email');
  147. } else {
  148. $this->config->setUserValue($this->uid, 'settings', 'email', $mailAddress);
  149. }
  150. $primaryAddress = $this->getPrimaryEMailAddress();
  151. if ($primaryAddress === $mailAddress) {
  152. // on match no dedicated primary settings is necessary
  153. $this->setPrimaryEMailAddress('');
  154. }
  155. if ($oldMailAddress !== strtolower($mailAddress)) {
  156. $this->triggerChange('eMailAddress', $mailAddress, $oldMailAddress);
  157. }
  158. }
  159. /**
  160. * @inheritDoc
  161. */
  162. public function setPrimaryEMailAddress(string $mailAddress): void {
  163. if ($mailAddress === '') {
  164. $this->config->deleteUserValue($this->uid, 'settings', 'primary_email');
  165. return;
  166. }
  167. $this->ensureAccountManager();
  168. $account = $this->accountManager->getAccount($this);
  169. $property = $account->getPropertyCollection(IAccountManager::COLLECTION_EMAIL)
  170. ->getPropertyByValue($mailAddress);
  171. if ($property === null || $property->getLocallyVerified() !== IAccountManager::VERIFIED) {
  172. throw new InvalidArgumentException('Only verified emails can be set as primary');
  173. }
  174. $this->config->setUserValue($this->uid, 'settings', 'primary_email', $mailAddress);
  175. }
  176. private function ensureAccountManager() {
  177. if (!$this->accountManager instanceof IAccountManager) {
  178. $this->accountManager = \OC::$server->get(IAccountManager::class);
  179. }
  180. }
  181. /**
  182. * returns the timestamp of the user's last login or 0 if the user did never
  183. * login
  184. *
  185. * @return int
  186. */
  187. public function getLastLogin() {
  188. if ($this->lastLogin === null) {
  189. $this->lastLogin = (int) $this->config->getUserValue($this->uid, 'login', 'lastLogin', 0);
  190. }
  191. return (int) $this->lastLogin;
  192. }
  193. /**
  194. * updates the timestamp of the most recent login of this user
  195. */
  196. public function updateLastLoginTimestamp() {
  197. $previousLogin = $this->getLastLogin();
  198. $now = time();
  199. $firstTimeLogin = $previousLogin === 0;
  200. if ($now - $previousLogin > 60) {
  201. $this->lastLogin = time();
  202. $this->config->setUserValue(
  203. $this->uid, 'login', 'lastLogin', (string)$this->lastLogin);
  204. }
  205. return $firstTimeLogin;
  206. }
  207. /**
  208. * Delete the user
  209. *
  210. * @return bool
  211. */
  212. public function delete() {
  213. if ($this->emitter) {
  214. /** @deprecated 21.0.0 use BeforeUserDeletedEvent event with the IEventDispatcher instead */
  215. $this->emitter->emit('\OC\User', 'preDelete', [$this]);
  216. }
  217. $this->dispatcher->dispatchTyped(new BeforeUserDeletedEvent($this));
  218. $result = $this->backend->deleteUser($this->uid);
  219. if ($result) {
  220. // FIXME: Feels like an hack - suggestions?
  221. $groupManager = \OC::$server->getGroupManager();
  222. // We have to delete the user from all groups
  223. foreach ($groupManager->getUserGroupIds($this) as $groupId) {
  224. $group = $groupManager->get($groupId);
  225. if ($group) {
  226. $this->dispatcher->dispatchTyped(new BeforeUserRemovedEvent($group, $this));
  227. $group->removeUser($this);
  228. $this->dispatcher->dispatchTyped(new UserRemovedEvent($group, $this));
  229. }
  230. }
  231. // Delete the user's keys in preferences
  232. \OC::$server->getConfig()->deleteAllUserValues($this->uid);
  233. \OC::$server->get(ICommentsManager::class)->deleteReferencesOfActor('users', $this->uid);
  234. \OC::$server->get(ICommentsManager::class)->deleteReadMarksFromUser($this);
  235. /** @var AvatarManager $avatarManager */
  236. $avatarManager = \OCP\Server::get(AvatarManager::class);
  237. $avatarManager->deleteUserAvatar($this->uid);
  238. $notification = \OC::$server->get(INotificationManager::class)->createNotification();
  239. $notification->setUser($this->uid);
  240. \OC::$server->get(INotificationManager::class)->markProcessed($notification);
  241. /** @var AccountManager $accountManager */
  242. $accountManager = \OCP\Server::get(AccountManager::class);
  243. $accountManager->deleteUser($this);
  244. if ($this->emitter) {
  245. /** @deprecated 21.0.0 use UserDeletedEvent event with the IEventDispatcher instead */
  246. $this->emitter->emit('\OC\User', 'postDelete', [$this]);
  247. }
  248. $this->dispatcher->dispatchTyped(new UserDeletedEvent($this));
  249. }
  250. return !($result === false);
  251. }
  252. /**
  253. * Set the password of the user
  254. *
  255. * @param string $password
  256. * @param string $recoveryPassword for the encryption app to reset encryption keys
  257. * @return bool
  258. */
  259. public function setPassword($password, $recoveryPassword = null) {
  260. $this->dispatcher->dispatchTyped(new BeforePasswordUpdatedEvent($this, $password, $recoveryPassword));
  261. if ($this->emitter) {
  262. $this->emitter->emit('\OC\User', 'preSetPassword', [$this, $password, $recoveryPassword]);
  263. }
  264. if ($this->backend->implementsActions(Backend::SET_PASSWORD)) {
  265. /** @var ISetPasswordBackend $backend */
  266. $backend = $this->backend;
  267. $result = $backend->setPassword($this->uid, $password);
  268. if ($result !== false) {
  269. $this->dispatcher->dispatchTyped(new PasswordUpdatedEvent($this, $password, $recoveryPassword));
  270. if ($this->emitter) {
  271. $this->emitter->emit('\OC\User', 'postSetPassword', [$this, $password, $recoveryPassword]);
  272. }
  273. }
  274. return !($result === false);
  275. } else {
  276. return false;
  277. }
  278. }
  279. /**
  280. * get the users home folder to mount
  281. *
  282. * @return string
  283. */
  284. public function getHome() {
  285. if (!$this->home) {
  286. /** @psalm-suppress UndefinedInterfaceMethod Once we get rid of the legacy implementsActions, psalm won't complain anymore */
  287. if (($this->backend instanceof IGetHomeBackend || $this->backend->implementsActions(Backend::GET_HOME)) && $home = $this->backend->getHome($this->uid)) {
  288. $this->home = $home;
  289. } elseif ($this->config) {
  290. $this->home = $this->config->getSystemValueString('datadirectory', \OC::$SERVERROOT . '/data') . '/' . $this->uid;
  291. } else {
  292. $this->home = \OC::$SERVERROOT . '/data/' . $this->uid;
  293. }
  294. }
  295. return $this->home;
  296. }
  297. /**
  298. * Get the name of the backend class the user is connected with
  299. *
  300. * @return string
  301. */
  302. public function getBackendClassName() {
  303. if ($this->backend instanceof IUserBackend) {
  304. return $this->backend->getBackendName();
  305. }
  306. return get_class($this->backend);
  307. }
  308. public function getBackend(): ?UserInterface {
  309. return $this->backend;
  310. }
  311. /**
  312. * Check if the backend allows the user to change his avatar on Personal page
  313. *
  314. * @return bool
  315. */
  316. public function canChangeAvatar() {
  317. if ($this->backend instanceof IProvideAvatarBackend || $this->backend->implementsActions(Backend::PROVIDE_AVATAR)) {
  318. /** @var IProvideAvatarBackend $backend */
  319. $backend = $this->backend;
  320. return $backend->canChangeAvatar($this->uid);
  321. }
  322. return true;
  323. }
  324. /**
  325. * check if the backend supports changing passwords
  326. *
  327. * @return bool
  328. */
  329. public function canChangePassword() {
  330. return $this->backend->implementsActions(Backend::SET_PASSWORD);
  331. }
  332. /**
  333. * check if the backend supports changing display names
  334. *
  335. * @return bool
  336. */
  337. public function canChangeDisplayName() {
  338. if (!$this->config->getSystemValueBool('allow_user_to_change_display_name', true)) {
  339. return false;
  340. }
  341. return $this->backend->implementsActions(Backend::SET_DISPLAYNAME);
  342. }
  343. /**
  344. * check if the user is enabled
  345. *
  346. * @return bool
  347. */
  348. public function isEnabled() {
  349. $queryDatabaseValue = function (): bool {
  350. if ($this->enabled === null) {
  351. $enabled = $this->config->getUserValue($this->uid, 'core', 'enabled', 'true');
  352. $this->enabled = $enabled === 'true';
  353. }
  354. return $this->enabled;
  355. };
  356. if ($this->backend instanceof IProvideEnabledStateBackend) {
  357. return $this->backend->isUserEnabled($this->uid, $queryDatabaseValue);
  358. } else {
  359. return $queryDatabaseValue();
  360. }
  361. }
  362. /**
  363. * set the enabled status for the user
  364. *
  365. * @return void
  366. */
  367. public function setEnabled(bool $enabled = true) {
  368. $oldStatus = $this->isEnabled();
  369. $setDatabaseValue = function (bool $enabled): void {
  370. $this->config->setUserValue($this->uid, 'core', 'enabled', $enabled ? 'true' : 'false');
  371. $this->enabled = $enabled;
  372. };
  373. if ($this->backend instanceof IProvideEnabledStateBackend) {
  374. $queryDatabaseValue = function (): bool {
  375. if ($this->enabled === null) {
  376. $enabled = $this->config->getUserValue($this->uid, 'core', 'enabled', 'true');
  377. $this->enabled = $enabled === 'true';
  378. }
  379. return $this->enabled;
  380. };
  381. $enabled = $this->backend->setUserEnabled($this->uid, $enabled, $queryDatabaseValue, $setDatabaseValue);
  382. if ($oldStatus !== $enabled) {
  383. $this->triggerChange('enabled', $enabled, $oldStatus);
  384. }
  385. } elseif ($oldStatus !== $enabled) {
  386. $setDatabaseValue($enabled);
  387. $this->triggerChange('enabled', $enabled, $oldStatus);
  388. }
  389. }
  390. /**
  391. * get the users email address
  392. *
  393. * @return string|null
  394. * @since 9.0.0
  395. */
  396. public function getEMailAddress() {
  397. return $this->getPrimaryEMailAddress() ?? $this->getSystemEMailAddress();
  398. }
  399. /**
  400. * @inheritDoc
  401. */
  402. public function getSystemEMailAddress(): ?string {
  403. return $this->config->getUserValue($this->uid, 'settings', 'email', null);
  404. }
  405. /**
  406. * @inheritDoc
  407. */
  408. public function getPrimaryEMailAddress(): ?string {
  409. return $this->config->getUserValue($this->uid, 'settings', 'primary_email', null);
  410. }
  411. /**
  412. * get the users' quota
  413. *
  414. * @return string
  415. * @since 9.0.0
  416. */
  417. public function getQuota() {
  418. // allow apps to modify the user quota by hooking into the event
  419. $event = new GetQuotaEvent($this);
  420. $this->dispatcher->dispatchTyped($event);
  421. $overwriteQuota = $event->getQuota();
  422. if ($overwriteQuota) {
  423. $quota = $overwriteQuota;
  424. } else {
  425. $quota = $this->config->getUserValue($this->uid, 'files', 'quota', 'default');
  426. }
  427. if ($quota === 'default') {
  428. $quota = $this->config->getAppValue('files', 'default_quota', 'none');
  429. // if unlimited quota is not allowed => avoid getting 'unlimited' as default_quota fallback value
  430. // use the first preset instead
  431. $allowUnlimitedQuota = $this->config->getAppValue('files', 'allow_unlimited_quota', '1') === '1';
  432. if (!$allowUnlimitedQuota) {
  433. $presets = $this->config->getAppValue('files', 'quota_preset', '1 GB, 5 GB, 10 GB');
  434. $presets = array_filter(array_map('trim', explode(',', $presets)));
  435. $quotaPreset = array_values(array_diff($presets, ['default', 'none']));
  436. if (count($quotaPreset) > 0) {
  437. $quota = $this->config->getAppValue('files', 'default_quota', $quotaPreset[0]);
  438. }
  439. }
  440. }
  441. return $quota;
  442. }
  443. /**
  444. * set the users' quota
  445. *
  446. * @param string $quota
  447. * @return void
  448. * @throws InvalidArgumentException
  449. * @since 9.0.0
  450. */
  451. public function setQuota($quota) {
  452. $oldQuota = $this->config->getUserValue($this->uid, 'files', 'quota', '');
  453. if ($quota !== 'none' and $quota !== 'default') {
  454. $bytesQuota = OC_Helper::computerFileSize($quota);
  455. if ($bytesQuota === false) {
  456. throw new InvalidArgumentException('Failed to set quota to invalid value '.$quota);
  457. }
  458. $quota = OC_Helper::humanFileSize($bytesQuota);
  459. }
  460. if ($quota !== $oldQuota) {
  461. $this->config->setUserValue($this->uid, 'files', 'quota', $quota);
  462. $this->triggerChange('quota', $quota, $oldQuota);
  463. }
  464. \OC_Helper::clearStorageInfo('/' . $this->uid . '/files');
  465. }
  466. public function getManagerUids(): array {
  467. $encodedUids = $this->config->getUserValue(
  468. $this->uid,
  469. 'settings',
  470. self::CONFIG_KEY_MANAGERS,
  471. '[]'
  472. );
  473. return json_decode($encodedUids, false, 512, JSON_THROW_ON_ERROR);
  474. }
  475. public function setManagerUids(array $uids): void {
  476. $oldUids = $this->getManagerUids();
  477. $this->config->setUserValue(
  478. $this->uid,
  479. 'settings',
  480. self::CONFIG_KEY_MANAGERS,
  481. json_encode($uids, JSON_THROW_ON_ERROR)
  482. );
  483. $this->triggerChange('managers', $uids, $oldUids);
  484. }
  485. /**
  486. * get the avatar image if it exists
  487. *
  488. * @param int $size
  489. * @return IImage|null
  490. * @since 9.0.0
  491. */
  492. public function getAvatarImage($size) {
  493. // delay the initialization
  494. if (is_null($this->avatarManager)) {
  495. $this->avatarManager = \OC::$server->get(IAvatarManager::class);
  496. }
  497. $avatar = $this->avatarManager->getAvatar($this->uid);
  498. $image = $avatar->get($size);
  499. if ($image) {
  500. return $image;
  501. }
  502. return null;
  503. }
  504. /**
  505. * get the federation cloud id
  506. *
  507. * @return string
  508. * @since 9.0.0
  509. */
  510. public function getCloudId() {
  511. $uid = $this->getUID();
  512. $server = rtrim($this->urlGenerator->getAbsoluteURL('/'), '/');
  513. if (str_ends_with($server, '/index.php')) {
  514. $server = substr($server, 0, -10);
  515. }
  516. $server = $this->removeProtocolFromUrl($server);
  517. return $uid . '@' . $server;
  518. }
  519. private function removeProtocolFromUrl(string $url): string {
  520. if (str_starts_with($url, 'https://')) {
  521. return substr($url, strlen('https://'));
  522. }
  523. return $url;
  524. }
  525. public function triggerChange($feature, $value = null, $oldValue = null) {
  526. $this->dispatcher->dispatchTyped(new UserChangedEvent($this, $feature, $value, $oldValue));
  527. if ($this->emitter) {
  528. $this->emitter->emit('\OC\User', 'changeUser', [$this, $feature, $value, $oldValue]);
  529. }
  530. }
  531. }