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.

Manager.php 17KB

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