User.php 17 KB

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