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.

498 lines
13 KiB

3 years ago
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * @copyright Copyright (c) 2016, ownCloud, Inc.
  5. *
  6. * @author adrien <adrien.waksberg@believedigital.com>
  7. * @author Aldo "xoen" Giambelluca <xoen@xoen.org>
  8. * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
  9. * @author Bart Visscher <bartv@thisnet.nl>
  10. * @author Bjoern Schiessle <bjoern@schiessle.org>
  11. * @author Björn Schießle <bjoern@schiessle.org>
  12. * @author Christoph Wurst <christoph@winzerhof-wurst.at>
  13. * @author Daniel Calviño Sánchez <danxuliu@gmail.com>
  14. * @author fabian <fabian@web2.0-apps.de>
  15. * @author Georg Ehrke <oc.list@georgehrke.com>
  16. * @author Jakob Sack <mail@jakobsack.de>
  17. * @author Joas Schilling <coding@schilljs.com>
  18. * @author Jörn Friedrich Dreyer <jfd@butonic.de>
  19. * @author Loki3000 <github@labcms.ru>
  20. * @author Lukas Reschke <lukas@statuscode.ch>
  21. * @author Morris Jobke <hey@morrisjobke.de>
  22. * @author nishiki <nishiki@yaegashi.fr>
  23. * @author Robin Appelman <robin@icewind.nl>
  24. * @author Robin McCorkell <robin@mccorkell.me.uk>
  25. * @author Roeland Jago Douma <roeland@famdouma.nl>
  26. * @author Thomas Müller <thomas.mueller@tmit.eu>
  27. * @author Vincent Petry <pvince81@owncloud.com>
  28. *
  29. * @license AGPL-3.0
  30. *
  31. * This code is free software: you can redistribute it and/or modify
  32. * it under the terms of the GNU Affero General Public License, version 3,
  33. * as published by the Free Software Foundation.
  34. *
  35. * This program is distributed in the hope that it will be useful,
  36. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  37. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  38. * GNU Affero General Public License for more details.
  39. *
  40. * You should have received a copy of the GNU Affero General Public License, version 3,
  41. * along with this program. If not, see <http://www.gnu.org/licenses/>
  42. *
  43. */
  44. /*
  45. *
  46. * The following SQL statement is just a help for developers and will not be
  47. * executed!
  48. *
  49. * CREATE TABLE `users` (
  50. * `uid` varchar(64) COLLATE utf8_unicode_ci NOT NULL,
  51. * `password` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  52. * PRIMARY KEY (`uid`)
  53. * ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
  54. *
  55. */
  56. namespace OC\User;
  57. use OC\Cache\CappedMemoryCache;
  58. use OCP\EventDispatcher\IEventDispatcher;
  59. use OCP\IDBConnection;
  60. use OCP\Security\Events\ValidatePasswordPolicyEvent;
  61. use OCP\User\Backend\ABackend;
  62. use OCP\User\Backend\ICheckPasswordBackend;
  63. use OCP\User\Backend\ICountUsersBackend;
  64. use OCP\User\Backend\ICreateUserBackend;
  65. use OCP\User\Backend\IGetDisplayNameBackend;
  66. use OCP\User\Backend\IGetHomeBackend;
  67. use OCP\User\Backend\IGetRealUIDBackend;
  68. use OCP\User\Backend\ISetDisplayNameBackend;
  69. use OCP\User\Backend\ISetPasswordBackend;
  70. /**
  71. * Class for user management in a SQL Database (e.g. MySQL, SQLite)
  72. */
  73. class Database extends ABackend implements
  74. ICreateUserBackend,
  75. ISetPasswordBackend,
  76. ISetDisplayNameBackend,
  77. IGetDisplayNameBackend,
  78. ICheckPasswordBackend,
  79. IGetHomeBackend,
  80. ICountUsersBackend,
  81. IGetRealUIDBackend {
  82. /** @var CappedMemoryCache */
  83. private $cache;
  84. /** @var IEventDispatcher */
  85. private $eventDispatcher;
  86. /** @var IDBConnection */
  87. private $dbConn;
  88. /** @var string */
  89. private $table;
  90. /**
  91. * \OC\User\Database constructor.
  92. *
  93. * @param IEventDispatcher $eventDispatcher
  94. * @param string $table
  95. */
  96. public function __construct($eventDispatcher = null, $table = 'users') {
  97. $this->cache = new CappedMemoryCache();
  98. $this->table = $table;
  99. $this->eventDispatcher = $eventDispatcher ? $eventDispatcher : \OC::$server->query(IEventDispatcher::class);
  100. }
  101. /**
  102. * FIXME: This function should not be required!
  103. */
  104. private function fixDI() {
  105. if ($this->dbConn === null) {
  106. $this->dbConn = \OC::$server->getDatabaseConnection();
  107. }
  108. }
  109. /**
  110. * Create a new user
  111. *
  112. * @param string $uid The username of the user to create
  113. * @param string $password The password of the new user
  114. * @return bool
  115. *
  116. * Creates a new user. Basic checking of username is done in OC_User
  117. * itself, not in its subclasses.
  118. */
  119. public function createUser(string $uid, string $password): bool {
  120. $this->fixDI();
  121. if (!$this->userExists($uid)) {
  122. $this->eventDispatcher->dispatchTyped(new ValidatePasswordPolicyEvent($password));
  123. $qb = $this->dbConn->getQueryBuilder();
  124. $qb->insert($this->table)
  125. ->values([
  126. 'uid' => $qb->createNamedParameter($uid),
  127. 'password' => $qb->createNamedParameter(\OC::$server->getHasher()->hash($password)),
  128. 'uid_lower' => $qb->createNamedParameter(mb_strtolower($uid)),
  129. ]);
  130. $result = $qb->execute();
  131. // Clear cache
  132. unset($this->cache[$uid]);
  133. return $result ? true : false;
  134. }
  135. return false;
  136. }
  137. /**
  138. * delete a user
  139. *
  140. * @param string $uid The username of the user to delete
  141. * @return bool
  142. *
  143. * Deletes a user
  144. */
  145. public function deleteUser($uid) {
  146. $this->fixDI();
  147. // Delete user-group-relation
  148. $query = $this->dbConn->getQueryBuilder();
  149. $query->delete($this->table)
  150. ->where($query->expr()->eq('uid_lower', $query->createNamedParameter(mb_strtolower($uid))));
  151. $result = $query->execute();
  152. if (isset($this->cache[$uid])) {
  153. unset($this->cache[$uid]);
  154. }
  155. return $result ? true : false;
  156. }
  157. private function updatePassword(string $uid, string $passwordHash): bool {
  158. $query = $this->dbConn->getQueryBuilder();
  159. $query->update($this->table)
  160. ->set('password', $query->createNamedParameter($passwordHash))
  161. ->where($query->expr()->eq('uid_lower', $query->createNamedParameter(mb_strtolower($uid))));
  162. $result = $query->execute();
  163. return $result ? true : false;
  164. }
  165. /**
  166. * Set password
  167. *
  168. * @param string $uid The username
  169. * @param string $password The new password
  170. * @return bool
  171. *
  172. * Change the password of a user
  173. */
  174. public function setPassword(string $uid, string $password): bool {
  175. $this->fixDI();
  176. if ($this->userExists($uid)) {
  177. $this->eventDispatcher->dispatchTyped(new ValidatePasswordPolicyEvent($password));
  178. $hasher = \OC::$server->getHasher();
  179. $hashedPassword = $hasher->hash($password);
  180. return $this->updatePassword($uid, $hashedPassword);
  181. }
  182. return false;
  183. }
  184. /**
  185. * Set display name
  186. *
  187. * @param string $uid The username
  188. * @param string $displayName The new display name
  189. * @return bool
  190. *
  191. * Change the display name of a user
  192. */
  193. public function setDisplayName(string $uid, string $displayName): bool {
  194. $this->fixDI();
  195. if ($this->userExists($uid)) {
  196. $query = $this->dbConn->getQueryBuilder();
  197. $query->update($this->table)
  198. ->set('displayname', $query->createNamedParameter($displayName))
  199. ->where($query->expr()->eq('uid_lower', $query->createNamedParameter(mb_strtolower($uid))));
  200. $query->execute();
  201. $this->cache[$uid]['displayname'] = $displayName;
  202. return true;
  203. }
  204. return false;
  205. }
  206. /**
  207. * get display name of the user
  208. *
  209. * @param string $uid user ID of the user
  210. * @return string display name
  211. */
  212. public function getDisplayName($uid): string {
  213. $uid = (string)$uid;
  214. $this->loadUser($uid);
  215. return empty($this->cache[$uid]['displayname']) ? $uid : $this->cache[$uid]['displayname'];
  216. }
  217. /**
  218. * Get a list of all display names and user ids.
  219. *
  220. * @param string $search
  221. * @param string|null $limit
  222. * @param string|null $offset
  223. * @return array an array of all displayNames (value) and the corresponding uids (key)
  224. */
  225. public function getDisplayNames($search = '', $limit = null, $offset = null) {
  226. $limit = $this->fixLimit($limit);
  227. $this->fixDI();
  228. $query = $this->dbConn->getQueryBuilder();
  229. $query->select('uid', 'displayname')
  230. ->from($this->table, 'u')
  231. ->leftJoin('u', 'preferences', 'p', $query->expr()->andX(
  232. $query->expr()->eq('userid', 'uid'),
  233. $query->expr()->eq('appid', $query->expr()->literal('settings')),
  234. $query->expr()->eq('configkey', $query->expr()->literal('email')))
  235. )
  236. // sqlite doesn't like re-using a single named parameter here
  237. ->where($query->expr()->iLike('uid', $query->createPositionalParameter('%' . $this->dbConn->escapeLikeParameter($search) . '%')))
  238. ->orWhere($query->expr()->iLike('displayname', $query->createPositionalParameter('%' . $this->dbConn->escapeLikeParameter($search) . '%')))
  239. ->orWhere($query->expr()->iLike('configvalue', $query->createPositionalParameter('%' . $this->dbConn->escapeLikeParameter($search) . '%')))
  240. ->orderBy($query->func()->lower('displayname'), 'ASC')
  241. ->orderBy('uid_lower', 'ASC')
  242. ->setMaxResults($limit)
  243. ->setFirstResult($offset);
  244. $result = $query->execute();
  245. $displayNames = [];
  246. while ($row = $result->fetch()) {
  247. $displayNames[(string)$row['uid']] = (string)$row['displayname'];
  248. }
  249. return $displayNames;
  250. }
  251. /**
  252. * Check if the password is correct
  253. *
  254. * @param string $loginName The loginname
  255. * @param string $password The password
  256. * @return string
  257. *
  258. * Check if the password is correct without logging in the user
  259. * returns the user id or false
  260. */
  261. public function checkPassword(string $loginName, string $password) {
  262. $this->fixDI();
  263. $qb = $this->dbConn->getQueryBuilder();
  264. $qb->select('uid', 'password')
  265. ->from($this->table)
  266. ->where(
  267. $qb->expr()->eq(
  268. 'uid_lower', $qb->createNamedParameter(mb_strtolower($loginName))
  269. )
  270. );
  271. $result = $qb->execute();
  272. $row = $result->fetch();
  273. $result->closeCursor();
  274. if ($row) {
  275. $storedHash = $row['password'];
  276. $newHash = '';
  277. if (\OC::$server->getHasher()->verify($password, $storedHash, $newHash)) {
  278. if (!empty($newHash)) {
  279. $this->updatePassword($loginName, $newHash);
  280. }
  281. return (string)$row['uid'];
  282. }
  283. }
  284. return false;
  285. }
  286. /**
  287. * Load an user in the cache
  288. *
  289. * @param string $uid the username
  290. * @return boolean true if user was found, false otherwise
  291. */
  292. private function loadUser($uid) {
  293. $this->fixDI();
  294. $uid = (string)$uid;
  295. if (!isset($this->cache[$uid])) {
  296. //guests $uid could be NULL or ''
  297. if ($uid === '') {
  298. $this->cache[$uid] = false;
  299. return true;
  300. }
  301. $qb = $this->dbConn->getQueryBuilder();
  302. $qb->select('uid', 'displayname')
  303. ->from($this->table)
  304. ->where(
  305. $qb->expr()->eq(
  306. 'uid_lower', $qb->createNamedParameter(mb_strtolower($uid))
  307. )
  308. );
  309. $result = $qb->execute();
  310. $row = $result->fetch();
  311. $result->closeCursor();
  312. $this->cache[$uid] = false;
  313. // "uid" is primary key, so there can only be a single result
  314. if ($row !== false) {
  315. $this->cache[$uid]['uid'] = (string)$row['uid'];
  316. $this->cache[$uid]['displayname'] = (string)$row['displayname'];
  317. } else {
  318. return false;
  319. }
  320. }
  321. return true;
  322. }
  323. /**
  324. * Get a list of all users
  325. *
  326. * @param string $search
  327. * @param null|int $limit
  328. * @param null|int $offset
  329. * @return string[] an array of all uids
  330. */
  331. public function getUsers($search = '', $limit = null, $offset = null) {
  332. $limit = $this->fixLimit($limit);
  333. $users = $this->getDisplayNames($search, $limit, $offset);
  334. $userIds = array_map(function ($uid) {
  335. return (string)$uid;
  336. }, array_keys($users));
  337. sort($userIds, SORT_STRING | SORT_FLAG_CASE);
  338. return $userIds;
  339. }
  340. /**
  341. * check if a user exists
  342. *
  343. * @param string $uid the username
  344. * @return boolean
  345. */
  346. public function userExists($uid) {
  347. $this->loadUser($uid);
  348. return $this->cache[$uid] !== false;
  349. }
  350. /**
  351. * get the user's home directory
  352. *
  353. * @param string $uid the username
  354. * @return string|false
  355. */
  356. public function getHome(string $uid) {
  357. if ($this->userExists($uid)) {
  358. return \OC::$server->getConfig()->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data') . '/' . $uid;
  359. }
  360. return false;
  361. }
  362. /**
  363. * @return bool
  364. */
  365. public function hasUserListings() {
  366. return true;
  367. }
  368. /**
  369. * counts the users in the database
  370. *
  371. * @return int|bool
  372. */
  373. public function countUsers() {
  374. $this->fixDI();
  375. $query = $this->dbConn->getQueryBuilder();
  376. $query->select($query->func()->count('uid'))
  377. ->from($this->table);
  378. $result = $query->execute();
  379. return $result->fetchColumn();
  380. }
  381. /**
  382. * returns the username for the given login name in the correct casing
  383. *
  384. * @param string $loginName
  385. * @return string|false
  386. */
  387. public function loginName2UserName($loginName) {
  388. if ($this->userExists($loginName)) {
  389. return $this->cache[$loginName]['uid'];
  390. }
  391. return false;
  392. }
  393. /**
  394. * Backend name to be shown in user management
  395. *
  396. * @return string the name of the backend to be shown
  397. */
  398. public function getBackendName() {
  399. return 'Database';
  400. }
  401. public static function preLoginNameUsedAsUserName($param) {
  402. if (!isset($param['uid'])) {
  403. throw new \Exception('key uid is expected to be set in $param');
  404. }
  405. $backends = \OC::$server->getUserManager()->getBackends();
  406. foreach ($backends as $backend) {
  407. if ($backend instanceof Database) {
  408. /** @var \OC\User\Database $backend */
  409. $uid = $backend->loginName2UserName($param['uid']);
  410. if ($uid !== false) {
  411. $param['uid'] = $uid;
  412. return;
  413. }
  414. }
  415. }
  416. }
  417. public function getRealUID(string $uid): string {
  418. if (!$this->userExists($uid)) {
  419. throw new \RuntimeException($uid . ' does not exist');
  420. }
  421. return $this->cache[$uid]['uid'];
  422. }
  423. private function fixLimit($limit) {
  424. if (is_int($limit) && $limit >= 0) {
  425. return $limit;
  426. }
  427. return null;
  428. }
  429. }