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 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472
  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 Robin McCorkell <robin@mccorkell.me.uk>
  13. * @author Roeland Jago Douma <roeland@famdouma.nl>
  14. * @author Thomas Müller <thomas.mueller@tmit.eu>
  15. * @author Vincent Chan <plus.vincchan@gmail.com>
  16. * @author Volkan Gezer <volkangezer@gmail.com>
  17. *
  18. * @license AGPL-3.0
  19. *
  20. * This code is free software: you can redistribute it and/or modify
  21. * it under the terms of the GNU Affero General Public License, version 3,
  22. * as published by the Free Software Foundation.
  23. *
  24. * This program is distributed in the hope that it will be useful,
  25. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  26. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  27. * GNU Affero General Public License for more details.
  28. *
  29. * You should have received a copy of the GNU Affero General Public License, version 3,
  30. * along with this program. If not, see <http://www.gnu.org/licenses/>
  31. *
  32. */
  33. namespace OC\User;
  34. use OC\Hooks\PublicEmitter;
  35. use OCP\IUser;
  36. use OCP\IUserBackend;
  37. use OCP\IUserManager;
  38. use OCP\IConfig;
  39. /**
  40. * Class Manager
  41. *
  42. * Hooks available in scope \OC\User:
  43. * - preSetPassword(\OC\User\User $user, string $password, string $recoverPassword)
  44. * - postSetPassword(\OC\User\User $user, string $password, string $recoverPassword)
  45. * - preDelete(\OC\User\User $user)
  46. * - postDelete(\OC\User\User $user)
  47. * - preCreateUser(string $uid, string $password)
  48. * - postCreateUser(\OC\User\User $user, string $password)
  49. * - change(\OC\User\User $user)
  50. *
  51. * @package OC\User
  52. */
  53. class Manager extends PublicEmitter implements IUserManager {
  54. /**
  55. * @var \OCP\UserInterface[] $backends
  56. */
  57. private $backends = array();
  58. /**
  59. * @var \OC\User\User[] $cachedUsers
  60. */
  61. private $cachedUsers = array();
  62. /**
  63. * @var \OCP\IConfig $config
  64. */
  65. private $config;
  66. /**
  67. * @param \OCP\IConfig $config
  68. */
  69. public function __construct(IConfig $config) {
  70. $this->config = $config;
  71. $cachedUsers = &$this->cachedUsers;
  72. $this->listen('\OC\User', 'postDelete', function ($user) use (&$cachedUsers) {
  73. /** @var \OC\User\User $user */
  74. unset($cachedUsers[$user->getUID()]);
  75. });
  76. }
  77. /**
  78. * Get the active backends
  79. * @return \OCP\UserInterface[]
  80. */
  81. public function getBackends() {
  82. return $this->backends;
  83. }
  84. /**
  85. * register a user backend
  86. *
  87. * @param \OCP\UserInterface $backend
  88. */
  89. public function registerBackend($backend) {
  90. $this->backends[] = $backend;
  91. }
  92. /**
  93. * remove a user backend
  94. *
  95. * @param \OCP\UserInterface $backend
  96. */
  97. public function removeBackend($backend) {
  98. $this->cachedUsers = array();
  99. if (($i = array_search($backend, $this->backends)) !== false) {
  100. unset($this->backends[$i]);
  101. }
  102. }
  103. /**
  104. * remove all user backends
  105. */
  106. public function clearBackends() {
  107. $this->cachedUsers = array();
  108. $this->backends = array();
  109. }
  110. /**
  111. * get a user by user id
  112. *
  113. * @param string $uid
  114. * @return \OC\User\User|null Either the user or null if the specified user does not exist
  115. */
  116. public function get($uid) {
  117. if (isset($this->cachedUsers[$uid])) { //check the cache first to prevent having to loop over the backends
  118. return $this->cachedUsers[$uid];
  119. }
  120. foreach ($this->backends as $backend) {
  121. if ($backend->userExists($uid)) {
  122. return $this->getUserObject($uid, $backend);
  123. }
  124. }
  125. return null;
  126. }
  127. /**
  128. * get or construct the user object
  129. *
  130. * @param string $uid
  131. * @param \OCP\UserInterface $backend
  132. * @param bool $cacheUser If false the newly created user object will not be cached
  133. * @return \OC\User\User
  134. */
  135. protected function getUserObject($uid, $backend, $cacheUser = true) {
  136. if (isset($this->cachedUsers[$uid])) {
  137. return $this->cachedUsers[$uid];
  138. }
  139. if (method_exists($backend, 'loginName2UserName')) {
  140. $loginName = $backend->loginName2UserName($uid);
  141. if ($loginName !== false) {
  142. $uid = $loginName;
  143. }
  144. if (isset($this->cachedUsers[$uid])) {
  145. return $this->cachedUsers[$uid];
  146. }
  147. }
  148. $user = new User($uid, $backend, $this, $this->config);
  149. if ($cacheUser) {
  150. $this->cachedUsers[$uid] = $user;
  151. }
  152. return $user;
  153. }
  154. /**
  155. * check if a user exists
  156. *
  157. * @param string $uid
  158. * @return bool
  159. */
  160. public function userExists($uid) {
  161. $user = $this->get($uid);
  162. return ($user !== null);
  163. }
  164. /**
  165. * Check if the password is valid for the user
  166. *
  167. * @param string $loginName
  168. * @param string $password
  169. * @return mixed the User object on success, false otherwise
  170. */
  171. public function checkPassword($loginName, $password) {
  172. $loginName = str_replace("\0", '', $loginName);
  173. $password = str_replace("\0", '', $password);
  174. foreach ($this->backends as $backend) {
  175. if ($backend->implementsActions(Backend::CHECK_PASSWORD)) {
  176. $uid = $backend->checkPassword($loginName, $password);
  177. if ($uid !== false) {
  178. return $this->getUserObject($uid, $backend);
  179. }
  180. }
  181. }
  182. \OC::$server->getLogger()->warning('Login failed: \''. $loginName .'\' (Remote IP: \''. \OC::$server->getRequest()->getRemoteAddress(). '\')', ['app' => 'core']);
  183. return false;
  184. }
  185. /**
  186. * search by user id
  187. *
  188. * @param string $pattern
  189. * @param int $limit
  190. * @param int $offset
  191. * @return \OC\User\User[]
  192. */
  193. public function search($pattern, $limit = null, $offset = null) {
  194. $users = array();
  195. foreach ($this->backends as $backend) {
  196. $backendUsers = $backend->getUsers($pattern, $limit, $offset);
  197. if (is_array($backendUsers)) {
  198. foreach ($backendUsers as $uid) {
  199. $users[$uid] = $this->getUserObject($uid, $backend);
  200. }
  201. }
  202. }
  203. uasort($users, function ($a, $b) {
  204. /**
  205. * @var \OC\User\User $a
  206. * @var \OC\User\User $b
  207. */
  208. return strcmp($a->getUID(), $b->getUID());
  209. });
  210. return $users;
  211. }
  212. /**
  213. * search by displayName
  214. *
  215. * @param string $pattern
  216. * @param int $limit
  217. * @param int $offset
  218. * @return \OC\User\User[]
  219. */
  220. public function searchDisplayName($pattern, $limit = null, $offset = null) {
  221. $users = array();
  222. foreach ($this->backends as $backend) {
  223. $backendUsers = $backend->getDisplayNames($pattern, $limit, $offset);
  224. if (is_array($backendUsers)) {
  225. foreach ($backendUsers as $uid => $displayName) {
  226. $users[] = $this->getUserObject($uid, $backend);
  227. }
  228. }
  229. }
  230. usort($users, function ($a, $b) {
  231. /**
  232. * @var \OC\User\User $a
  233. * @var \OC\User\User $b
  234. */
  235. return strcmp($a->getDisplayName(), $b->getDisplayName());
  236. });
  237. return $users;
  238. }
  239. /**
  240. * @param string $uid
  241. * @param string $password
  242. * @throws \Exception
  243. * @return bool|\OC\User\User the created user or false
  244. */
  245. public function createUser($uid, $password) {
  246. $l = \OC::$server->getL10N('lib');
  247. // Check the name for bad characters
  248. // Allowed are: "a-z", "A-Z", "0-9" and "_.@-'"
  249. if (preg_match('/[^a-zA-Z0-9 _\.@\-\']/', $uid)) {
  250. throw new \Exception($l->t('Only the following characters are allowed in a username:'
  251. . ' "a-z", "A-Z", "0-9", and "_.@-\'"'));
  252. }
  253. // No empty username
  254. if (trim($uid) == '') {
  255. throw new \Exception($l->t('A valid username must be provided'));
  256. }
  257. // No whitespace at the beginning or at the end
  258. if (strlen(trim($uid, "\t\n\r\0\x0B\xe2\x80\x8b")) !== strlen(trim($uid))) {
  259. throw new \Exception($l->t('Username contains whitespace at the beginning or at the end'));
  260. }
  261. // No empty password
  262. if (trim($password) == '') {
  263. throw new \Exception($l->t('A valid password must be provided'));
  264. }
  265. // Check if user already exists
  266. if ($this->userExists($uid)) {
  267. throw new \Exception($l->t('The username is already being used'));
  268. }
  269. $this->emit('\OC\User', 'preCreateUser', array($uid, $password));
  270. foreach ($this->backends as $backend) {
  271. if ($backend->implementsActions(Backend::CREATE_USER)) {
  272. $backend->createUser($uid, $password);
  273. $user = $this->getUserObject($uid, $backend);
  274. $this->emit('\OC\User', 'postCreateUser', array($user, $password));
  275. return $user;
  276. }
  277. }
  278. return false;
  279. }
  280. /**
  281. * returns how many users per backend exist (if supported by backend)
  282. *
  283. * @param boolean $hasLoggedIn when true only users that have a lastLogin
  284. * entry in the preferences table will be affected
  285. * @return array|int an array of backend class as key and count number as value
  286. * if $hasLoggedIn is true only an int is returned
  287. */
  288. public function countUsers($hasLoggedIn = false) {
  289. if ($hasLoggedIn) {
  290. return $this->countSeenUsers();
  291. }
  292. $userCountStatistics = [];
  293. foreach ($this->backends as $backend) {
  294. if ($backend->implementsActions(Backend::COUNT_USERS)) {
  295. $backendUsers = $backend->countUsers();
  296. if($backendUsers !== false) {
  297. if($backend instanceof IUserBackend) {
  298. $name = $backend->getBackendName();
  299. } else {
  300. $name = get_class($backend);
  301. }
  302. if(isset($userCountStatistics[$name])) {
  303. $userCountStatistics[$name] += $backendUsers;
  304. } else {
  305. $userCountStatistics[$name] = $backendUsers;
  306. }
  307. }
  308. }
  309. }
  310. return $userCountStatistics;
  311. }
  312. /**
  313. * The callback is executed for each user on each backend.
  314. * If the callback returns false no further users will be retrieved.
  315. *
  316. * @param \Closure $callback
  317. * @param string $search
  318. * @param boolean $onlySeen when true only users that have a lastLogin entry
  319. * in the preferences table will be affected
  320. * @since 9.0.0
  321. */
  322. public function callForAllUsers(\Closure $callback, $search = '', $onlySeen = false) {
  323. if ($onlySeen) {
  324. $this->callForSeenUsers($callback);
  325. } else {
  326. foreach ($this->getBackends() as $backend) {
  327. $limit = 500;
  328. $offset = 0;
  329. do {
  330. $users = $backend->getUsers($search, $limit, $offset);
  331. foreach ($users as $uid) {
  332. if (!$backend->userExists($uid)) {
  333. continue;
  334. }
  335. $user = $this->getUserObject($uid, $backend, false);
  336. $return = $callback($user);
  337. if ($return === false) {
  338. break;
  339. }
  340. }
  341. $offset += $limit;
  342. } while (count($users) >= $limit);
  343. }
  344. }
  345. }
  346. /**
  347. * returns how many users have logged in once
  348. *
  349. * @return int
  350. * @since 11.0.0
  351. */
  352. public function countSeenUsers() {
  353. $queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
  354. $queryBuilder->select($queryBuilder->createFunction('COUNT(*)'))
  355. ->from('preferences')
  356. ->where($queryBuilder->expr()->eq('appid', $queryBuilder->createNamedParameter('login')))
  357. ->andWhere($queryBuilder->expr()->eq('configkey', $queryBuilder->createNamedParameter('lastLogin')))
  358. ->andWhere($queryBuilder->expr()->isNotNull('configvalue'));
  359. $query = $queryBuilder->execute();
  360. $result = (int)$query->fetchColumn();
  361. $query->closeCursor();
  362. return $result;
  363. }
  364. /**
  365. * @param \Closure $callback
  366. * @since 11.0.0
  367. */
  368. public function callForSeenUsers(\Closure $callback) {
  369. $limit = 1000;
  370. $offset = 0;
  371. do {
  372. $userIds = $this->getSeenUserIds($limit, $offset);
  373. $offset += $limit;
  374. foreach ($userIds as $userId) {
  375. foreach ($this->backends as $backend) {
  376. if ($backend->userExists($userId)) {
  377. $user = $this->getUserObject($userId, $backend, false);
  378. $return = $callback($user);
  379. if ($return === false) {
  380. return;
  381. }
  382. }
  383. }
  384. }
  385. } while (count($userIds) >= $limit);
  386. }
  387. /**
  388. * Getting all userIds that have a listLogin value requires checking the
  389. * value in php because on oracle you cannot use a clob in a where clause,
  390. * preventing us from doing a not null or length(value) > 0 check.
  391. *
  392. * @param int $limit
  393. * @param int $offset
  394. * @return string[] with user ids
  395. */
  396. private function getSeenUserIds($limit = null, $offset = null) {
  397. $queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
  398. $queryBuilder->select(['userid'])
  399. ->from('preferences')
  400. ->where($queryBuilder->expr()->eq(
  401. 'appid', $queryBuilder->createNamedParameter('login'))
  402. )
  403. ->andWhere($queryBuilder->expr()->eq(
  404. 'configkey', $queryBuilder->createNamedParameter('lastLogin'))
  405. )
  406. ->andWhere($queryBuilder->expr()->isNotNull('configvalue')
  407. );
  408. if ($limit !== null) {
  409. $queryBuilder->setMaxResults($limit);
  410. }
  411. if ($offset !== null) {
  412. $queryBuilder->setFirstResult($offset);
  413. }
  414. $query = $queryBuilder->execute();
  415. $result = [];
  416. while ($row = $query->fetch()) {
  417. $result[] = $row['userid'];
  418. }
  419. $query->closeCursor();
  420. return $result;
  421. }
  422. /**
  423. * @param string $email
  424. * @return IUser[]
  425. * @since 9.1.0
  426. */
  427. public function getByEmail($email) {
  428. $userIds = $this->config->getUsersForUserValue('settings', 'email', $email);
  429. return array_map(function($uid) {
  430. return $this->get($uid);
  431. }, $userIds);
  432. }
  433. }