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

503 lines
14 KiB

3 years ago
  1. <?php
  2. /**
  3. * @copyright Copyright (c) 2016, ownCloud, Inc.
  4. *
  5. * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
  6. * @author Bart Visscher <bartv@thisnet.nl>
  7. * @author Björn Schießle <bjoern@schiessle.org>
  8. * @author Christoph Wurst <christoph@winzerhof-wurst.at>
  9. * @author Joas Schilling <coding@schilljs.com>
  10. * @author John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
  11. * @author Jörn Friedrich Dreyer <jfd@butonic.de>
  12. * @author Julius Härtl <jus@bitgrid.net>
  13. * @author Leon Klingele <leon@struktur.de>
  14. * @author Lukas Reschke <lukas@statuscode.ch>
  15. * @author Morris Jobke <hey@morrisjobke.de>
  16. * @author Robin Appelman <robin@icewind.nl>
  17. * @author Roeland Jago Douma <roeland@famdouma.nl>
  18. * @author Thomas Müller <thomas.mueller@tmit.eu>
  19. * @author Vincent Petry <pvince81@owncloud.com>
  20. *
  21. * @license AGPL-3.0
  22. *
  23. * This code is free software: you can redistribute it and/or modify
  24. * it under the terms of the GNU Affero General Public License, version 3,
  25. * as published by the Free Software Foundation.
  26. *
  27. * This program is distributed in the hope that it will be useful,
  28. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  29. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  30. * GNU Affero General Public License for more details.
  31. *
  32. * You should have received a copy of the GNU Affero General Public License, version 3,
  33. * along with this program. If not, see <http://www.gnu.org/licenses/>
  34. *
  35. */
  36. namespace OC\User;
  37. use OC\Accounts\AccountManager;
  38. use OC\Avatar\AvatarManager;
  39. use OC\Files\Cache\Storage;
  40. use OC\Hooks\Emitter;
  41. use OC_Helper;
  42. use OCP\EventDispatcher\IEventDispatcher;
  43. use OCP\Group\Events\BeforeUserRemovedEvent;
  44. use OCP\Group\Events\UserRemovedEvent;
  45. use OCP\IAvatarManager;
  46. use OCP\IConfig;
  47. use OCP\IImage;
  48. use OCP\IURLGenerator;
  49. use OCP\IUser;
  50. use OCP\IUserBackend;
  51. use OCP\User\GetQuotaEvent;
  52. use OCP\UserInterface;
  53. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  54. use Symfony\Component\EventDispatcher\GenericEvent;
  55. class User implements IUser {
  56. /** @var string */
  57. private $uid;
  58. /** @var string */
  59. private $displayName;
  60. /** @var UserInterface|null */
  61. private $backend;
  62. /** @var EventDispatcherInterface */
  63. private $legacyDispatcher;
  64. /** @var IEventDispatcher */
  65. private $dispatcher;
  66. /** @var bool */
  67. private $enabled;
  68. /** @var Emitter|Manager */
  69. private $emitter;
  70. /** @var string */
  71. private $home;
  72. /** @var int */
  73. private $lastLogin;
  74. /** @var \OCP\IConfig */
  75. private $config;
  76. /** @var IAvatarManager */
  77. private $avatarManager;
  78. /** @var IURLGenerator */
  79. private $urlGenerator;
  80. public function __construct(string $uid, ?UserInterface $backend, EventDispatcherInterface $dispatcher, $emitter = null, IConfig $config = null, $urlGenerator = null) {
  81. $this->uid = $uid;
  82. $this->backend = $backend;
  83. $this->legacyDispatcher = $dispatcher;
  84. $this->emitter = $emitter;
  85. if (is_null($config)) {
  86. $config = \OC::$server->getConfig();
  87. }
  88. $this->config = $config;
  89. $this->urlGenerator = $urlGenerator;
  90. $enabled = $this->config->getUserValue($uid, 'core', 'enabled', 'true');
  91. $this->enabled = ($enabled === 'true');
  92. $this->lastLogin = $this->config->getUserValue($uid, 'login', 'lastLogin', 0);
  93. if (is_null($this->urlGenerator)) {
  94. $this->urlGenerator = \OC::$server->getURLGenerator();
  95. }
  96. // TODO: inject
  97. $this->dispatcher = \OC::$server->query(IEventDispatcher::class);
  98. }
  99. /**
  100. * get the user id
  101. *
  102. * @return string
  103. */
  104. public function getUID() {
  105. return $this->uid;
  106. }
  107. /**
  108. * get the display name for the user, if no specific display name is set it will fallback to the user id
  109. *
  110. * @return string
  111. */
  112. public function getDisplayName() {
  113. if (!isset($this->displayName)) {
  114. $displayName = '';
  115. if ($this->backend && $this->backend->implementsActions(Backend::GET_DISPLAYNAME)) {
  116. // get display name and strip whitespace from the beginning and end of it
  117. $backendDisplayName = $this->backend->getDisplayName($this->uid);
  118. if (is_string($backendDisplayName)) {
  119. $displayName = trim($backendDisplayName);
  120. }
  121. }
  122. if (!empty($displayName)) {
  123. $this->displayName = $displayName;
  124. } else {
  125. $this->displayName = $this->uid;
  126. }
  127. }
  128. return $this->displayName;
  129. }
  130. /**
  131. * set the displayname for the user
  132. *
  133. * @param string $displayName
  134. * @return bool
  135. */
  136. public function setDisplayName($displayName) {
  137. $displayName = trim($displayName);
  138. $oldDisplayName = $this->getDisplayName();
  139. if ($this->backend->implementsActions(Backend::SET_DISPLAYNAME) && !empty($displayName) && $displayName !== $oldDisplayName) {
  140. $result = $this->backend->setDisplayName($this->uid, $displayName);
  141. if ($result) {
  142. $this->displayName = $displayName;
  143. $this->triggerChange('displayName', $displayName, $oldDisplayName);
  144. }
  145. return $result !== false;
  146. }
  147. return false;
  148. }
  149. /**
  150. * set the email address of the user
  151. *
  152. * @param string|null $mailAddress
  153. * @return void
  154. * @since 9.0.0
  155. */
  156. public function setEMailAddress($mailAddress) {
  157. $oldMailAddress = $this->getEMailAddress();
  158. if ($oldMailAddress !== $mailAddress) {
  159. if ($mailAddress === '') {
  160. $this->config->deleteUserValue($this->uid, 'settings', 'email');
  161. } else {
  162. $this->config->setUserValue($this->uid, 'settings', 'email', $mailAddress);
  163. }
  164. $this->triggerChange('eMailAddress', $mailAddress, $oldMailAddress);
  165. }
  166. }
  167. /**
  168. * returns the timestamp of the user's last login or 0 if the user did never
  169. * login
  170. *
  171. * @return int
  172. */
  173. public function getLastLogin() {
  174. return $this->lastLogin;
  175. }
  176. /**
  177. * updates the timestamp of the most recent login of this user
  178. */
  179. public function updateLastLoginTimestamp() {
  180. $firstTimeLogin = ($this->lastLogin === 0);
  181. $this->lastLogin = time();
  182. $this->config->setUserValue(
  183. $this->uid, 'login', 'lastLogin', $this->lastLogin);
  184. return $firstTimeLogin;
  185. }
  186. /**
  187. * Delete the user
  188. *
  189. * @return bool
  190. */
  191. public function delete() {
  192. $this->legacyDispatcher->dispatch(IUser::class . '::preDelete', new GenericEvent($this));
  193. if ($this->emitter) {
  194. $this->emitter->emit('\OC\User', 'preDelete', [$this]);
  195. }
  196. // get the home now because it won't return it after user deletion
  197. $homePath = $this->getHome();
  198. $result = $this->backend->deleteUser($this->uid);
  199. if ($result) {
  200. // FIXME: Feels like an hack - suggestions?
  201. $groupManager = \OC::$server->getGroupManager();
  202. // We have to delete the user from all groups
  203. foreach ($groupManager->getUserGroupIds($this) as $groupId) {
  204. $group = $groupManager->get($groupId);
  205. if ($group) {
  206. $this->dispatcher->dispatchTyped(new BeforeUserRemovedEvent($group, $this));
  207. $group->removeUser($this);
  208. $this->dispatcher->dispatchTyped(new UserRemovedEvent($group, $this));
  209. }
  210. }
  211. // Delete the user's keys in preferences
  212. \OC::$server->getConfig()->deleteAllUserValues($this->uid);
  213. // Delete user files in /data/
  214. if ($homePath !== false) {
  215. // FIXME: this operates directly on FS, should use View instead...
  216. // also this is not testable/mockable...
  217. \OC_Helper::rmdirr($homePath);
  218. }
  219. // Delete the users entry in the storage table
  220. Storage::remove('home::' . $this->uid);
  221. \OC::$server->getCommentsManager()->deleteReferencesOfActor('users', $this->uid);
  222. \OC::$server->getCommentsManager()->deleteReadMarksFromUser($this);
  223. /** @var IAvatarManager $avatarManager */
  224. $avatarManager = \OC::$server->query(AvatarManager::class);
  225. $avatarManager->deleteUserAvatar($this->uid);
  226. $notification = \OC::$server->getNotificationManager()->createNotification();
  227. $notification->setUser($this->uid);
  228. \OC::$server->getNotificationManager()->markProcessed($notification);
  229. /** @var AccountManager $accountManager */
  230. $accountManager = \OC::$server->query(AccountManager::class);
  231. $accountManager->deleteUser($this);
  232. $this->legacyDispatcher->dispatch(IUser::class . '::postDelete', new GenericEvent($this));
  233. if ($this->emitter) {
  234. $this->emitter->emit('\OC\User', 'postDelete', [$this]);
  235. }
  236. }
  237. return !($result === false);
  238. }
  239. /**
  240. * Set the password of the user
  241. *
  242. * @param string $password
  243. * @param string $recoveryPassword for the encryption app to reset encryption keys
  244. * @return bool
  245. */
  246. public function setPassword($password, $recoveryPassword = null) {
  247. $this->legacyDispatcher->dispatch(IUser::class . '::preSetPassword', new GenericEvent($this, [
  248. 'password' => $password,
  249. 'recoveryPassword' => $recoveryPassword,
  250. ]));
  251. if ($this->emitter) {
  252. $this->emitter->emit('\OC\User', 'preSetPassword', [$this, $password, $recoveryPassword]);
  253. }
  254. if ($this->backend->implementsActions(Backend::SET_PASSWORD)) {
  255. $result = $this->backend->setPassword($this->uid, $password);
  256. $this->legacyDispatcher->dispatch(IUser::class . '::postSetPassword', new GenericEvent($this, [
  257. 'password' => $password,
  258. 'recoveryPassword' => $recoveryPassword,
  259. ]));
  260. if ($this->emitter) {
  261. $this->emitter->emit('\OC\User', 'postSetPassword', [$this, $password, $recoveryPassword]);
  262. }
  263. return !($result === false);
  264. } else {
  265. return false;
  266. }
  267. }
  268. /**
  269. * get the users home folder to mount
  270. *
  271. * @return string
  272. */
  273. public function getHome() {
  274. if (!$this->home) {
  275. if ($this->backend->implementsActions(Backend::GET_HOME) and $home = $this->backend->getHome($this->uid)) {
  276. $this->home = $home;
  277. } elseif ($this->config) {
  278. $this->home = $this->config->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data') . '/' . $this->uid;
  279. } else {
  280. $this->home = \OC::$SERVERROOT . '/data/' . $this->uid;
  281. }
  282. }
  283. return $this->home;
  284. }
  285. /**
  286. * Get the name of the backend class the user is connected with
  287. *
  288. * @return string
  289. */
  290. public function getBackendClassName() {
  291. if ($this->backend instanceof IUserBackend) {
  292. return $this->backend->getBackendName();
  293. }
  294. return get_class($this->backend);
  295. }
  296. public function getBackend() {
  297. return $this->backend;
  298. }
  299. /**
  300. * check if the backend allows the user to change his avatar on Personal page
  301. *
  302. * @return bool
  303. */
  304. public function canChangeAvatar() {
  305. if ($this->backend->implementsActions(Backend::PROVIDE_AVATAR)) {
  306. return $this->backend->canChangeAvatar($this->uid);
  307. }
  308. return true;
  309. }
  310. /**
  311. * check if the backend supports changing passwords
  312. *
  313. * @return bool
  314. */
  315. public function canChangePassword() {
  316. return $this->backend->implementsActions(Backend::SET_PASSWORD);
  317. }
  318. /**
  319. * check if the backend supports changing display names
  320. *
  321. * @return bool
  322. */
  323. public function canChangeDisplayName() {
  324. if ($this->config->getSystemValue('allow_user_to_change_display_name') === false) {
  325. return false;
  326. }
  327. return $this->backend->implementsActions(Backend::SET_DISPLAYNAME);
  328. }
  329. /**
  330. * check if the user is enabled
  331. *
  332. * @return bool
  333. */
  334. public function isEnabled() {
  335. return $this->enabled;
  336. }
  337. /**
  338. * set the enabled status for the user
  339. *
  340. * @param bool $enabled
  341. */
  342. public function setEnabled(bool $enabled = true) {
  343. $oldStatus = $this->isEnabled();
  344. $this->enabled = $enabled;
  345. if ($oldStatus !== $this->enabled) {
  346. // TODO: First change the value, then trigger the event as done for all other properties.
  347. $this->triggerChange('enabled', $enabled, $oldStatus);
  348. $this->config->setUserValue($this->uid, 'core', 'enabled', $enabled ? 'true' : 'false');
  349. }
  350. }
  351. /**
  352. * get the users email address
  353. *
  354. * @return string|null
  355. * @since 9.0.0
  356. */
  357. public function getEMailAddress() {
  358. return $this->config->getUserValue($this->uid, 'settings', 'email', null);
  359. }
  360. /**
  361. * get the users' quota
  362. *
  363. * @return string
  364. * @since 9.0.0
  365. */
  366. public function getQuota() {
  367. // allow apps to modify the user quota by hooking into the event
  368. $event = new GetQuotaEvent($this);
  369. $this->dispatcher->dispatchTyped($event);
  370. $overwriteQuota = $event->getQuota();
  371. if ($overwriteQuota) {
  372. $quota = $overwriteQuota;
  373. } else {
  374. $quota = $this->config->getUserValue($this->uid, 'files', 'quota', 'default');
  375. }
  376. if ($quota === 'default') {
  377. $quota = $this->config->getAppValue('files', 'default_quota', 'none');
  378. }
  379. return $quota;
  380. }
  381. /**
  382. * set the users' quota
  383. *
  384. * @param string $quota
  385. * @return void
  386. * @since 9.0.0
  387. */
  388. public function setQuota($quota) {
  389. $oldQuota = $this->config->getUserValue($this->uid, 'files', 'quota', '');
  390. if ($quota !== 'none' and $quota !== 'default') {
  391. $quota = OC_Helper::computerFileSize($quota);
  392. $quota = OC_Helper::humanFileSize($quota);
  393. }
  394. if ($quota !== $oldQuota) {
  395. $this->config->setUserValue($this->uid, 'files', 'quota', $quota);
  396. $this->triggerChange('quota', $quota, $oldQuota);
  397. }
  398. }
  399. /**
  400. * get the avatar image if it exists
  401. *
  402. * @param int $size
  403. * @return IImage|null
  404. * @since 9.0.0
  405. */
  406. public function getAvatarImage($size) {
  407. // delay the initialization
  408. if (is_null($this->avatarManager)) {
  409. $this->avatarManager = \OC::$server->getAvatarManager();
  410. }
  411. $avatar = $this->avatarManager->getAvatar($this->uid);
  412. $image = $avatar->get(-1);
  413. if ($image) {
  414. return $image;
  415. }
  416. return null;
  417. }
  418. /**
  419. * get the federation cloud id
  420. *
  421. * @return string
  422. * @since 9.0.0
  423. */
  424. public function getCloudId() {
  425. $uid = $this->getUID();
  426. $server = $this->urlGenerator->getAbsoluteURL('/');
  427. $server = rtrim($this->removeProtocolFromUrl($server), '/');
  428. return \OC::$server->getCloudIdManager()->getCloudId($uid, $server)->getId();
  429. }
  430. /**
  431. * @param string $url
  432. * @return string
  433. */
  434. private function removeProtocolFromUrl($url) {
  435. if (strpos($url, 'https://') === 0) {
  436. return substr($url, strlen('https://'));
  437. } elseif (strpos($url, 'http://') === 0) {
  438. return substr($url, strlen('http://'));
  439. }
  440. return $url;
  441. }
  442. public function triggerChange($feature, $value = null, $oldValue = null) {
  443. $this->legacyDispatcher->dispatch(IUser::class . '::changeUser', new GenericEvent($this, [
  444. 'feature' => $feature,
  445. 'value' => $value,
  446. 'oldValue' => $oldValue,
  447. ]));
  448. if ($this->emitter) {
  449. $this->emitter->emit('\OC\User', 'changeUser', [$this, $feature, $value, $oldValue]);
  450. }
  451. }
  452. }