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.

Database.php 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489
  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 fabian <fabian@web2.0-apps.de>
  13. * @author Georg Ehrke <oc.list@georgehrke.com>
  14. * @author Jakob Sack <mail@jakobsack.de>
  15. * @author Joas Schilling <coding@schilljs.com>
  16. * @author Jörn Friedrich Dreyer <jfd@butonic.de>
  17. * @author Loki3000 <github@labcms.ru>
  18. * @author Lukas Reschke <lukas@statuscode.ch>
  19. * @author Michael Gapczynski <GapczynskiM@gmail.com>
  20. * @author michag86 <micha_g@arcor.de>
  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\IDBConnection;
  59. use OCP\User\Backend\ABackend;
  60. use OCP\User\Backend\ICheckPasswordBackend;
  61. use OCP\User\Backend\ICountUsersBackend;
  62. use OCP\User\Backend\ICreateUserBackend;
  63. use OCP\User\Backend\IGetDisplayNameBackend;
  64. use OCP\User\Backend\IGetHomeBackend;
  65. use OCP\User\Backend\IGetRealUIDBackend;
  66. use OCP\User\Backend\ISetDisplayNameBackend;
  67. use OCP\User\Backend\ISetPasswordBackend;
  68. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  69. use Symfony\Component\EventDispatcher\GenericEvent;
  70. /**
  71. * Class for user management in a SQL Database (e.g. MySQL, SQLite)
  72. */
  73. class Database extends ABackend
  74. implements ICreateUserBackend,
  75. ISetPasswordBackend,
  76. ISetDisplayNameBackend,
  77. IGetDisplayNameBackend,
  78. ICheckPasswordBackend,
  79. IGetHomeBackend,
  80. ICountUsersBackend,
  81. IGetRealUIDBackend {
  82. /** @var CappedMemoryCache */
  83. private $cache;
  84. /** @var EventDispatcherInterface */
  85. private $eventDispatcher;
  86. /** @var IDBConnection */
  87. private $dbConn;
  88. /** @var string */
  89. private $table;
  90. /**
  91. * \OC\User\Database constructor.
  92. *
  93. * @param EventDispatcherInterface $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->getEventDispatcher();
  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. $event = new GenericEvent($password);
  123. $this->eventDispatcher->dispatch('OCP\PasswordPolicy::validate', $event);
  124. $qb = $this->dbConn->getQueryBuilder();
  125. $qb->insert($this->table)
  126. ->values([
  127. 'uid' => $qb->createNamedParameter($uid),
  128. 'password' => $qb->createNamedParameter(\OC::$server->getHasher()->hash($password)),
  129. 'uid_lower' => $qb->createNamedParameter(mb_strtolower($uid)),
  130. ]);
  131. $result = $qb->execute();
  132. // Clear cache
  133. unset($this->cache[$uid]);
  134. return $result ? true : false;
  135. }
  136. return false;
  137. }
  138. /**
  139. * delete a user
  140. *
  141. * @param string $uid The username of the user to delete
  142. * @return bool
  143. *
  144. * Deletes a user
  145. */
  146. public function deleteUser($uid) {
  147. $this->fixDI();
  148. // Delete user-group-relation
  149. $query = $this->dbConn->getQueryBuilder();
  150. $query->delete($this->table)
  151. ->where($query->expr()->eq('uid_lower', $query->createNamedParameter(mb_strtolower($uid))));
  152. $result = $query->execute();
  153. if (isset($this->cache[$uid])) {
  154. unset($this->cache[$uid]);
  155. }
  156. return $result ? true : false;
  157. }
  158. private function updatePassword(string $uid, string $passwordHash): bool {
  159. $query = $this->dbConn->getQueryBuilder();
  160. $query->update($this->table)
  161. ->set('password', $query->createNamedParameter($passwordHash))
  162. ->where($query->expr()->eq('uid_lower', $query->createNamedParameter(mb_strtolower($uid))));
  163. $result = $query->execute();
  164. return $result ? true : false;
  165. }
  166. /**
  167. * Set password
  168. *
  169. * @param string $uid The username
  170. * @param string $password The new password
  171. * @return bool
  172. *
  173. * Change the password of a user
  174. */
  175. public function setPassword(string $uid, string $password): bool {
  176. $this->fixDI();
  177. if ($this->userExists($uid)) {
  178. $event = new GenericEvent($password);
  179. $this->eventDispatcher->dispatch('OCP\PasswordPolicy::validate', $event);
  180. $hasher = \OC::$server->getHasher();
  181. $hashedPassword = $hasher->hash($password);
  182. return $this->updatePassword($uid, $hashedPassword);
  183. }
  184. return false;
  185. }
  186. /**
  187. * Set display name
  188. *
  189. * @param string $uid The username
  190. * @param string $displayName The new display name
  191. * @return bool
  192. *
  193. * Change the display name of a user
  194. */
  195. public function setDisplayName(string $uid, string $displayName): bool {
  196. $this->fixDI();
  197. if ($this->userExists($uid)) {
  198. $query = $this->dbConn->getQueryBuilder();
  199. $query->update($this->table)
  200. ->set('displayname', $query->createNamedParameter($displayName))
  201. ->where($query->expr()->eq('uid_lower', $query->createNamedParameter(mb_strtolower($uid))));
  202. $query->execute();
  203. $this->cache[$uid]['displayname'] = $displayName;
  204. return true;
  205. }
  206. return false;
  207. }
  208. /**
  209. * get display name of the user
  210. *
  211. * @param string $uid user ID of the user
  212. * @return string display name
  213. */
  214. public function getDisplayName($uid): string {
  215. $uid = (string)$uid;
  216. $this->loadUser($uid);
  217. return empty($this->cache[$uid]['displayname']) ? $uid : $this->cache[$uid]['displayname'];
  218. }
  219. /**
  220. * Get a list of all display names and user ids.
  221. *
  222. * @param string $search
  223. * @param string|null $limit
  224. * @param string|null $offset
  225. * @return array an array of all displayNames (value) and the corresponding uids (key)
  226. */
  227. public function getDisplayNames($search = '', $limit = null, $offset = null) {
  228. $this->fixDI();
  229. $query = $this->dbConn->getQueryBuilder();
  230. $query->select('uid', 'displayname')
  231. ->from($this->table, 'u')
  232. ->leftJoin('u', 'preferences', 'p', $query->expr()->andX(
  233. $query->expr()->eq('userid', 'uid'),
  234. $query->expr()->eq('appid', $query->expr()->literal('settings')),
  235. $query->expr()->eq('configkey', $query->expr()->literal('email')))
  236. )
  237. // sqlite doesn't like re-using a single named parameter here
  238. ->where($query->expr()->iLike('uid', $query->createPositionalParameter('%' . $this->dbConn->escapeLikeParameter($search) . '%')))
  239. ->orWhere($query->expr()->iLike('displayname', $query->createPositionalParameter('%' . $this->dbConn->escapeLikeParameter($search) . '%')))
  240. ->orWhere($query->expr()->iLike('configvalue', $query->createPositionalParameter('%' . $this->dbConn->escapeLikeParameter($search) . '%')))
  241. ->orderBy($query->func()->lower('displayname'), 'ASC')
  242. ->orderBy('uid_lower', 'ASC')
  243. ->setMaxResults($limit)
  244. ->setFirstResult($offset);
  245. $result = $query->execute();
  246. $displayNames = [];
  247. while ($row = $result->fetch()) {
  248. $displayNames[(string)$row['uid']] = (string)$row['displayname'];
  249. }
  250. return $displayNames;
  251. }
  252. /**
  253. * Check if the password is correct
  254. *
  255. * @param string $uid The username
  256. * @param string $password The password
  257. * @return string
  258. *
  259. * Check if the password is correct without logging in the user
  260. * returns the user id or false
  261. */
  262. public function checkPassword(string $uid, string $password) {
  263. $this->fixDI();
  264. $qb = $this->dbConn->getQueryBuilder();
  265. $qb->select('uid', 'password')
  266. ->from($this->table)
  267. ->where(
  268. $qb->expr()->eq(
  269. 'uid_lower', $qb->createNamedParameter(mb_strtolower($uid))
  270. )
  271. );
  272. $result = $qb->execute();
  273. $row = $result->fetch();
  274. $result->closeCursor();
  275. if ($row) {
  276. $storedHash = $row['password'];
  277. $newHash = '';
  278. if (\OC::$server->getHasher()->verify($password, $storedHash, $newHash)) {
  279. if (!empty($newHash)) {
  280. $this->updatePassword($uid, $newHash);
  281. }
  282. return (string)$row['uid'];
  283. }
  284. }
  285. return false;
  286. }
  287. /**
  288. * Load an user in the cache
  289. *
  290. * @param string $uid the username
  291. * @return boolean true if user was found, false otherwise
  292. */
  293. private function loadUser($uid) {
  294. $this->fixDI();
  295. $uid = (string)$uid;
  296. if (!isset($this->cache[$uid])) {
  297. //guests $uid could be NULL or ''
  298. if ($uid === '') {
  299. $this->cache[$uid] = false;
  300. return true;
  301. }
  302. $qb = $this->dbConn->getQueryBuilder();
  303. $qb->select('uid', 'displayname')
  304. ->from($this->table)
  305. ->where(
  306. $qb->expr()->eq(
  307. 'uid_lower', $qb->createNamedParameter(mb_strtolower($uid))
  308. )
  309. );
  310. $result = $qb->execute();
  311. $row = $result->fetch();
  312. $result->closeCursor();
  313. $this->cache[$uid] = false;
  314. // "uid" is primary key, so there can only be a single result
  315. if ($row !== false) {
  316. $this->cache[$uid]['uid'] = (string)$row['uid'];
  317. $this->cache[$uid]['displayname'] = (string)$row['displayname'];
  318. } else {
  319. return false;
  320. }
  321. }
  322. return true;
  323. }
  324. /**
  325. * Get a list of all users
  326. *
  327. * @param string $search
  328. * @param null|int $limit
  329. * @param null|int $offset
  330. * @return string[] an array of all uids
  331. */
  332. public function getUsers($search = '', $limit = null, $offset = null) {
  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. }