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.

647 lines
18 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 Bjoern Schiessle <bjoern@schiessle.org>
  7. * @author Christoph Wurst <christoph@winzerhof-wurst.at>
  8. * @author Georg Ehrke <oc.list@georgehrke.com>
  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 Lukas Reschke <lukas@statuscode.ch>
  13. * @author Morris Jobke <hey@morrisjobke.de>
  14. * @author Robin Appelman <robin@icewind.nl>
  15. * @author Roeland Jago Douma <roeland@famdouma.nl>
  16. * @author Thomas Müller <thomas.mueller@tmit.eu>
  17. * @author Vincent Chan <plus.vincchan@gmail.com>
  18. *
  19. * @license AGPL-3.0
  20. *
  21. * This code is free software: you can redistribute it and/or modify
  22. * it under the terms of the GNU Affero General Public License, version 3,
  23. * as published by the Free Software Foundation.
  24. *
  25. * This program is distributed in the hope that it will be useful,
  26. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  27. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  28. * GNU Affero General Public License for more details.
  29. *
  30. * You should have received a copy of the GNU Affero General Public License, version 3,
  31. * along with this program. If not, see <http://www.gnu.org/licenses/>
  32. *
  33. */
  34. namespace OC\User;
  35. use OC\Hooks\PublicEmitter;
  36. use OCP\DB\QueryBuilder\IQueryBuilder;
  37. use OCP\EventDispatcher\IEventDispatcher;
  38. use OCP\IConfig;
  39. use OCP\IGroup;
  40. use OCP\IUser;
  41. use OCP\IUserBackend;
  42. use OCP\IUserManager;
  43. use OCP\User\Backend\IGetRealUIDBackend;
  44. use OCP\User\Events\CreateUserEvent;
  45. use OCP\User\Events\UserCreatedEvent;
  46. use OCP\UserInterface;
  47. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  48. /**
  49. * Class Manager
  50. *
  51. * Hooks available in scope \OC\User:
  52. * - preSetPassword(\OC\User\User $user, string $password, string $recoverPassword)
  53. * - postSetPassword(\OC\User\User $user, string $password, string $recoverPassword)
  54. * - preDelete(\OC\User\User $user)
  55. * - postDelete(\OC\User\User $user)
  56. * - preCreateUser(string $uid, string $password)
  57. * - postCreateUser(\OC\User\User $user, string $password)
  58. * - change(\OC\User\User $user)
  59. * - assignedUserId(string $uid)
  60. * - preUnassignedUserId(string $uid)
  61. * - postUnassignedUserId(string $uid)
  62. *
  63. * @package OC\User
  64. */
  65. class Manager extends PublicEmitter implements IUserManager {
  66. /**
  67. * @var \OCP\UserInterface[] $backends
  68. */
  69. private $backends = [];
  70. /**
  71. * @var \OC\User\User[] $cachedUsers
  72. */
  73. private $cachedUsers = [];
  74. /** @var IConfig */
  75. private $config;
  76. /** @var EventDispatcherInterface */
  77. private $dispatcher;
  78. /** @var IEventDispatcher */
  79. private $eventDispatcher;
  80. public function __construct(IConfig $config,
  81. EventDispatcherInterface $oldDispatcher,
  82. IEventDispatcher $eventDispatcher) {
  83. $this->config = $config;
  84. $this->dispatcher = $oldDispatcher;
  85. $cachedUsers = &$this->cachedUsers;
  86. $this->listen('\OC\User', 'postDelete', function ($user) use (&$cachedUsers) {
  87. /** @var \OC\User\User $user */
  88. unset($cachedUsers[$user->getUID()]);
  89. });
  90. $this->eventDispatcher = $eventDispatcher;
  91. }
  92. /**
  93. * Get the active backends
  94. * @return \OCP\UserInterface[]
  95. */
  96. public function getBackends() {
  97. return $this->backends;
  98. }
  99. /**
  100. * register a user backend
  101. *
  102. * @param \OCP\UserInterface $backend
  103. */
  104. public function registerBackend($backend) {
  105. $this->backends[] = $backend;
  106. }
  107. /**
  108. * remove a user backend
  109. *
  110. * @param \OCP\UserInterface $backend
  111. */
  112. public function removeBackend($backend) {
  113. $this->cachedUsers = [];
  114. if (($i = array_search($backend, $this->backends)) !== false) {
  115. unset($this->backends[$i]);
  116. }
  117. }
  118. /**
  119. * remove all user backends
  120. */
  121. public function clearBackends() {
  122. $this->cachedUsers = [];
  123. $this->backends = [];
  124. }
  125. /**
  126. * get a user by user id
  127. *
  128. * @param string $uid
  129. * @return \OC\User\User|null Either the user or null if the specified user does not exist
  130. */
  131. public function get($uid) {
  132. if (is_null($uid) || $uid === '' || $uid === false) {
  133. return null;
  134. }
  135. if (isset($this->cachedUsers[$uid])) { //check the cache first to prevent having to loop over the backends
  136. return $this->cachedUsers[$uid];
  137. }
  138. foreach ($this->backends as $backend) {
  139. if ($backend->userExists($uid)) {
  140. return $this->getUserObject($uid, $backend);
  141. }
  142. }
  143. return null;
  144. }
  145. /**
  146. * get or construct the user object
  147. *
  148. * @param string $uid
  149. * @param \OCP\UserInterface $backend
  150. * @param bool $cacheUser If false the newly created user object will not be cached
  151. * @return \OC\User\User
  152. */
  153. protected function getUserObject($uid, $backend, $cacheUser = true) {
  154. if ($backend instanceof IGetRealUIDBackend) {
  155. $uid = $backend->getRealUID($uid);
  156. }
  157. if (isset($this->cachedUsers[$uid])) {
  158. return $this->cachedUsers[$uid];
  159. }
  160. $user = new User($uid, $backend, $this->dispatcher, $this, $this->config);
  161. if ($cacheUser) {
  162. $this->cachedUsers[$uid] = $user;
  163. }
  164. return $user;
  165. }
  166. /**
  167. * check if a user exists
  168. *
  169. * @param string $uid
  170. * @return bool
  171. */
  172. public function userExists($uid) {
  173. $user = $this->get($uid);
  174. return ($user !== null);
  175. }
  176. /**
  177. * Check if the password is valid for the user
  178. *
  179. * @param string $loginName
  180. * @param string $password
  181. * @return mixed the User object on success, false otherwise
  182. */
  183. public function checkPassword($loginName, $password) {
  184. $result = $this->checkPasswordNoLogging($loginName, $password);
  185. if ($result === false) {
  186. \OC::$server->getLogger()->warning('Login failed: \''. $loginName .'\' (Remote IP: \''. \OC::$server->getRequest()->getRemoteAddress(). '\')', ['app' => 'core']);
  187. }
  188. return $result;
  189. }
  190. /**
  191. * Check if the password is valid for the user
  192. *
  193. * @internal
  194. * @param string $loginName
  195. * @param string $password
  196. * @return IUser|false the User object on success, false otherwise
  197. */
  198. public function checkPasswordNoLogging($loginName, $password) {
  199. $loginName = str_replace("\0", '', $loginName);
  200. $password = str_replace("\0", '', $password);
  201. foreach ($this->backends as $backend) {
  202. if ($backend->implementsActions(Backend::CHECK_PASSWORD)) {
  203. $uid = $backend->checkPassword($loginName, $password);
  204. if ($uid !== false) {
  205. return $this->getUserObject($uid, $backend);
  206. }
  207. }
  208. }
  209. return false;
  210. }
  211. /**
  212. * search by user id
  213. *
  214. * @param string $pattern
  215. * @param int $limit
  216. * @param int $offset
  217. * @return \OC\User\User[]
  218. */
  219. public function search($pattern, $limit = null, $offset = null) {
  220. $users = [];
  221. foreach ($this->backends as $backend) {
  222. $backendUsers = $backend->getUsers($pattern, $limit, $offset);
  223. if (is_array($backendUsers)) {
  224. foreach ($backendUsers as $uid) {
  225. $users[$uid] = $this->getUserObject($uid, $backend);
  226. }
  227. }
  228. }
  229. uasort($users, function ($a, $b) {
  230. /**
  231. * @var \OC\User\User $a
  232. * @var \OC\User\User $b
  233. */
  234. return strcasecmp($a->getUID(), $b->getUID());
  235. });
  236. return $users;
  237. }
  238. /**
  239. * search by displayName
  240. *
  241. * @param string $pattern
  242. * @param int $limit
  243. * @param int $offset
  244. * @return \OC\User\User[]
  245. */
  246. public function searchDisplayName($pattern, $limit = null, $offset = null) {
  247. $users = [];
  248. foreach ($this->backends as $backend) {
  249. $backendUsers = $backend->getDisplayNames($pattern, $limit, $offset);
  250. if (is_array($backendUsers)) {
  251. foreach ($backendUsers as $uid => $displayName) {
  252. $users[] = $this->getUserObject($uid, $backend);
  253. }
  254. }
  255. }
  256. usort($users, function ($a, $b) {
  257. /**
  258. * @var \OC\User\User $a
  259. * @var \OC\User\User $b
  260. */
  261. return strcasecmp($a->getDisplayName(), $b->getDisplayName());
  262. });
  263. return $users;
  264. }
  265. /**
  266. * @param string $uid
  267. * @param string $password
  268. * @throws \InvalidArgumentException
  269. * @return bool|IUser the created user or false
  270. */
  271. public function createUser($uid, $password) {
  272. $localBackends = [];
  273. foreach ($this->backends as $backend) {
  274. if ($backend instanceof Database) {
  275. // First check if there is another user backend
  276. $localBackends[] = $backend;
  277. continue;
  278. }
  279. if ($backend->implementsActions(Backend::CREATE_USER)) {
  280. return $this->createUserFromBackend($uid, $password, $backend);
  281. }
  282. }
  283. foreach ($localBackends as $backend) {
  284. if ($backend->implementsActions(Backend::CREATE_USER)) {
  285. return $this->createUserFromBackend($uid, $password, $backend);
  286. }
  287. }
  288. return false;
  289. }
  290. /**
  291. * @param string $uid
  292. * @param string $password
  293. * @param UserInterface $backend
  294. * @return IUser|null
  295. * @throws \InvalidArgumentException
  296. */
  297. public function createUserFromBackend($uid, $password, UserInterface $backend) {
  298. $l = \OC::$server->getL10N('lib');
  299. // Check the name for bad characters
  300. // Allowed are: "a-z", "A-Z", "0-9" and "_.@-'"
  301. if (preg_match('/[^a-zA-Z0-9 _.@\-\']/', $uid)) {
  302. throw new \InvalidArgumentException($l->t('Only the following characters are allowed in a username:'
  303. . ' "a-z", "A-Z", "0-9", and "_.@-\'"'));
  304. }
  305. // No empty username
  306. if (trim($uid) === '') {
  307. throw new \InvalidArgumentException($l->t('A valid username must be provided'));
  308. }
  309. // No whitespace at the beginning or at the end
  310. if (trim($uid) !== $uid) {
  311. throw new \InvalidArgumentException($l->t('Username contains whitespace at the beginning or at the end'));
  312. }
  313. // Username only consists of 1 or 2 dots (directory traversal)
  314. if ($uid === '.' || $uid === '..') {
  315. throw new \InvalidArgumentException($l->t('Username must not consist of dots only'));
  316. }
  317. if (!$this->verifyUid($uid)) {
  318. throw new \InvalidArgumentException($l->t('Username is invalid because files already exist for this user'));
  319. }
  320. // No empty password
  321. if (trim($password) === '') {
  322. throw new \InvalidArgumentException($l->t('A valid password must be provided'));
  323. }
  324. // Check if user already exists
  325. if ($this->userExists($uid)) {
  326. throw new \InvalidArgumentException($l->t('The username is already being used'));
  327. }
  328. $this->emit('\OC\User', 'preCreateUser', [$uid, $password]);
  329. $this->eventDispatcher->dispatchTyped(new CreateUserEvent($uid, $password));
  330. $state = $backend->createUser($uid, $password);
  331. if ($state === false) {
  332. throw new \InvalidArgumentException($l->t('Could not create user'));
  333. }
  334. $user = $this->getUserObject($uid, $backend);
  335. if ($user instanceof IUser) {
  336. $this->emit('\OC\User', 'postCreateUser', [$user, $password]);
  337. $this->eventDispatcher->dispatchTyped(new UserCreatedEvent($user, $password));
  338. }
  339. return $user;
  340. }
  341. /**
  342. * returns how many users per backend exist (if supported by backend)
  343. *
  344. * @param boolean $hasLoggedIn when true only users that have a lastLogin
  345. * entry in the preferences table will be affected
  346. * @return array|int an array of backend class as key and count number as value
  347. * if $hasLoggedIn is true only an int is returned
  348. */
  349. public function countUsers($hasLoggedIn = false) {
  350. if ($hasLoggedIn) {
  351. return $this->countSeenUsers();
  352. }
  353. $userCountStatistics = [];
  354. foreach ($this->backends as $backend) {
  355. if ($backend->implementsActions(Backend::COUNT_USERS)) {
  356. $backendUsers = $backend->countUsers();
  357. if ($backendUsers !== false) {
  358. if ($backend instanceof IUserBackend) {
  359. $name = $backend->getBackendName();
  360. } else {
  361. $name = get_class($backend);
  362. }
  363. if (isset($userCountStatistics[$name])) {
  364. $userCountStatistics[$name] += $backendUsers;
  365. } else {
  366. $userCountStatistics[$name] = $backendUsers;
  367. }
  368. }
  369. }
  370. }
  371. return $userCountStatistics;
  372. }
  373. /**
  374. * returns how many users per backend exist in the requested groups (if supported by backend)
  375. *
  376. * @param IGroup[] $groups an array of gid to search in
  377. * @return array|int an array of backend class as key and count number as value
  378. * if $hasLoggedIn is true only an int is returned
  379. */
  380. public function countUsersOfGroups(array $groups) {
  381. $users = [];
  382. foreach ($groups as $group) {
  383. $usersIds = array_map(function ($user) {
  384. return $user->getUID();
  385. }, $group->getUsers());
  386. $users = array_merge($users, $usersIds);
  387. }
  388. return count(array_unique($users));
  389. }
  390. /**
  391. * The callback is executed for each user on each backend.
  392. * If the callback returns false no further users will be retrieved.
  393. *
  394. * @param \Closure $callback
  395. * @param string $search
  396. * @param boolean $onlySeen when true only users that have a lastLogin entry
  397. * in the preferences table will be affected
  398. * @since 9.0.0
  399. */
  400. public function callForAllUsers(\Closure $callback, $search = '', $onlySeen = false) {
  401. if ($onlySeen) {
  402. $this->callForSeenUsers($callback);
  403. } else {
  404. foreach ($this->getBackends() as $backend) {
  405. $limit = 500;
  406. $offset = 0;
  407. do {
  408. $users = $backend->getUsers($search, $limit, $offset);
  409. foreach ($users as $uid) {
  410. if (!$backend->userExists($uid)) {
  411. continue;
  412. }
  413. $user = $this->getUserObject($uid, $backend, false);
  414. $return = $callback($user);
  415. if ($return === false) {
  416. break;
  417. }
  418. }
  419. $offset += $limit;
  420. } while (count($users) >= $limit);
  421. }
  422. }
  423. }
  424. /**
  425. * returns how many users are disabled
  426. *
  427. * @return int
  428. * @since 12.0.0
  429. */
  430. public function countDisabledUsers(): int {
  431. $queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
  432. $queryBuilder->select($queryBuilder->func()->count('*'))
  433. ->from('preferences')
  434. ->where($queryBuilder->expr()->eq('appid', $queryBuilder->createNamedParameter('core')))
  435. ->andWhere($queryBuilder->expr()->eq('configkey', $queryBuilder->createNamedParameter('enabled')))
  436. ->andWhere($queryBuilder->expr()->eq('configvalue', $queryBuilder->createNamedParameter('false'), IQueryBuilder::PARAM_STR));
  437. $result = $queryBuilder->execute();
  438. $count = $result->fetchColumn();
  439. $result->closeCursor();
  440. if ($count !== false) {
  441. $count = (int)$count;
  442. } else {
  443. $count = 0;
  444. }
  445. return $count;
  446. }
  447. /**
  448. * returns how many users are disabled in the requested groups
  449. *
  450. * @param array $groups groupids to search
  451. * @return int
  452. * @since 14.0.0
  453. */
  454. public function countDisabledUsersOfGroups(array $groups): int {
  455. $queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
  456. $queryBuilder->select($queryBuilder->createFunction('COUNT(DISTINCT ' . $queryBuilder->getColumnName('uid') . ')'))
  457. ->from('preferences', 'p')
  458. ->innerJoin('p', 'group_user', 'g', $queryBuilder->expr()->eq('p.userid', 'g.uid'))
  459. ->where($queryBuilder->expr()->eq('appid', $queryBuilder->createNamedParameter('core')))
  460. ->andWhere($queryBuilder->expr()->eq('configkey', $queryBuilder->createNamedParameter('enabled')))
  461. ->andWhere($queryBuilder->expr()->eq('configvalue', $queryBuilder->createNamedParameter('false'), IQueryBuilder::PARAM_STR))
  462. ->andWhere($queryBuilder->expr()->in('gid', $queryBuilder->createNamedParameter($groups, IQueryBuilder::PARAM_STR_ARRAY)));
  463. $result = $queryBuilder->execute();
  464. $count = $result->fetchColumn();
  465. $result->closeCursor();
  466. if ($count !== false) {
  467. $count = (int)$count;
  468. } else {
  469. $count = 0;
  470. }
  471. return $count;
  472. }
  473. /**
  474. * returns how many users have logged in once
  475. *
  476. * @return int
  477. * @since 11.0.0
  478. */
  479. public function countSeenUsers() {
  480. $queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
  481. $queryBuilder->select($queryBuilder->func()->count('*'))
  482. ->from('preferences')
  483. ->where($queryBuilder->expr()->eq('appid', $queryBuilder->createNamedParameter('login')))
  484. ->andWhere($queryBuilder->expr()->eq('configkey', $queryBuilder->createNamedParameter('lastLogin')))
  485. ->andWhere($queryBuilder->expr()->isNotNull('configvalue'));
  486. $query = $queryBuilder->execute();
  487. $result = (int)$query->fetchColumn();
  488. $query->closeCursor();
  489. return $result;
  490. }
  491. /**
  492. * @param \Closure $callback
  493. * @since 11.0.0
  494. */
  495. public function callForSeenUsers(\Closure $callback) {
  496. $limit = 1000;
  497. $offset = 0;
  498. do {
  499. $userIds = $this->getSeenUserIds($limit, $offset);
  500. $offset += $limit;
  501. foreach ($userIds as $userId) {
  502. foreach ($this->backends as $backend) {
  503. if ($backend->userExists($userId)) {
  504. $user = $this->getUserObject($userId, $backend, false);
  505. $return = $callback($user);
  506. if ($return === false) {
  507. return;
  508. }
  509. break;
  510. }
  511. }
  512. }
  513. } while (count($userIds) >= $limit);
  514. }
  515. /**
  516. * Getting all userIds that have a listLogin value requires checking the
  517. * value in php because on oracle you cannot use a clob in a where clause,
  518. * preventing us from doing a not null or length(value) > 0 check.
  519. *
  520. * @param int $limit
  521. * @param int $offset
  522. * @return string[] with user ids
  523. */
  524. private function getSeenUserIds($limit = null, $offset = null) {
  525. $queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
  526. $queryBuilder->select(['userid'])
  527. ->from('preferences')
  528. ->where($queryBuilder->expr()->eq(
  529. 'appid', $queryBuilder->createNamedParameter('login'))
  530. )
  531. ->andWhere($queryBuilder->expr()->eq(
  532. 'configkey', $queryBuilder->createNamedParameter('lastLogin'))
  533. )
  534. ->andWhere($queryBuilder->expr()->isNotNull('configvalue')
  535. );
  536. if ($limit !== null) {
  537. $queryBuilder->setMaxResults($limit);
  538. }
  539. if ($offset !== null) {
  540. $queryBuilder->setFirstResult($offset);
  541. }
  542. $query = $queryBuilder->execute();
  543. $result = [];
  544. while ($row = $query->fetch()) {
  545. $result[] = $row['userid'];
  546. }
  547. $query->closeCursor();
  548. return $result;
  549. }
  550. /**
  551. * @param string $email
  552. * @return IUser[]
  553. * @since 9.1.0
  554. */
  555. public function getByEmail($email) {
  556. $userIds = $this->config->getUsersForUserValueCaseInsensitive('settings', 'email', $email);
  557. $users = array_map(function ($uid) {
  558. return $this->get($uid);
  559. }, $userIds);
  560. return array_values(array_filter($users, function ($u) {
  561. return ($u instanceof IUser);
  562. }));
  563. }
  564. private function verifyUid(string $uid): bool {
  565. $appdata = 'appdata_' . $this->config->getSystemValueString('instanceid');
  566. if (\in_array($uid, [
  567. '.htaccess',
  568. 'files_external',
  569. '.ocdata',
  570. 'owncloud.log',
  571. 'nextcloud.log',
  572. $appdata], true)) {
  573. return false;
  574. }
  575. $dataDirectory = $this->config->getSystemValueString('datadirectory', \OC::$SERVERROOT . '/data');
  576. return !file_exists(rtrim($dataDirectory, '/') . '/' . $uid);
  577. }
  578. }