diff options
author | Thomas Müller <DeepDiver1975@users.noreply.github.com> | 2016-05-11 11:16:56 +0200 |
---|---|---|
committer | Thomas Müller <DeepDiver1975@users.noreply.github.com> | 2016-05-11 11:16:56 +0200 |
commit | 9eea0620045979a1ed437749005256bdb223fce4 (patch) | |
tree | 357f8fee1424d56c5a77f2683d66c6e507d928ac /lib/private/User | |
parent | a02c5240073c83c2fe9ce2d064ac9f942d201a26 (diff) | |
parent | e2748e4cbfc72e8d8694b5326125a9cf6e7a0985 (diff) | |
download | nextcloud-server-9eea0620045979a1ed437749005256bdb223fce4.tar.gz nextcloud-server-9eea0620045979a1ed437749005256bdb223fce4.zip |
Merge pull request #24433 from owncloud/user_psr4
lib/private/user to PSR-4
Diffstat (limited to 'lib/private/User')
-rw-r--r-- | lib/private/User/Backend.php | 174 | ||||
-rw-r--r-- | lib/private/User/Database.php | 349 | ||||
-rw-r--r-- | lib/private/User/LoginException.php | 26 | ||||
-rw-r--r-- | lib/private/User/Manager.php | 371 | ||||
-rw-r--r-- | lib/private/User/NoUserException.php | 24 | ||||
-rw-r--r-- | lib/private/User/Session.php | 322 | ||||
-rw-r--r-- | lib/private/User/User.php | 420 |
7 files changed, 1686 insertions, 0 deletions
diff --git a/lib/private/User/Backend.php b/lib/private/User/Backend.php new file mode 100644 index 00000000000..d5f82dc9621 --- /dev/null +++ b/lib/private/User/Backend.php @@ -0,0 +1,174 @@ +<?php +/** + * @author Aldo "xoen" Giambelluca <xoen@xoen.org> + * @author Bart Visscher <bartv@thisnet.nl> + * @author Björn Schießle <schiessle@owncloud.com> + * @author Dominik Schmidt <dev@dominik-schmidt.de> + * @author Georg Ehrke <georg@owncloud.com> + * @author Jakob Sack <mail@jakobsack.de> + * @author Joas Schilling <nickvergessen@owncloud.com> + * @author Jörn Friedrich Dreyer <jfd@butonic.de> + * @author Lukas Reschke <lukas@owncloud.com> + * @author Morris Jobke <hey@morrisjobke.de> + * @author Robin Appelman <icewind@owncloud.com> + * @author Sam Tuke <mail@samtuke.com> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * @author Tigran Mkrtchyan <tigran.mkrtchyan@desy.de> + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OC\User; + +/** + * Abstract base class for user management. Provides methods for querying backend + * capabilities. + */ +abstract class Backend implements \OCP\UserInterface { + /** + * error code for functions not provided by the user backend + */ + const NOT_IMPLEMENTED = -501; + + /** + * actions that user backends can define + */ + const CREATE_USER = 1; // 1 << 0 + const SET_PASSWORD = 16; // 1 << 4 + const CHECK_PASSWORD = 256; // 1 << 8 + const GET_HOME = 4096; // 1 << 12 + const GET_DISPLAYNAME = 65536; // 1 << 16 + const SET_DISPLAYNAME = 1048576; // 1 << 20 + const PROVIDE_AVATAR = 16777216; // 1 << 24 + const COUNT_USERS = 268435456; // 1 << 28 + + protected $possibleActions = array( + self::CREATE_USER => 'createUser', + self::SET_PASSWORD => 'setPassword', + self::CHECK_PASSWORD => 'checkPassword', + self::GET_HOME => 'getHome', + self::GET_DISPLAYNAME => 'getDisplayName', + self::SET_DISPLAYNAME => 'setDisplayName', + self::PROVIDE_AVATAR => 'canChangeAvatar', + self::COUNT_USERS => 'countUsers', + ); + + /** + * Get all supported actions + * @return int bitwise-or'ed actions + * + * Returns the supported actions as int to be + * compared with self::CREATE_USER etc. + */ + public function getSupportedActions() { + $actions = 0; + foreach($this->possibleActions AS $action => $methodName) { + if(method_exists($this, $methodName)) { + $actions |= $action; + } + } + + return $actions; + } + + /** + * Check if backend implements actions + * @param int $actions bitwise-or'ed actions + * @return boolean + * + * Returns the supported actions as int to be + * compared with self::CREATE_USER etc. + */ + public function implementsActions($actions) { + return (bool)($this->getSupportedActions() & $actions); + } + + /** + * delete a user + * @param string $uid The username of the user to delete + * @return bool + * + * Deletes a user + */ + public function deleteUser( $uid ) { + return false; + } + + /** + * Get a list of all users + * + * @param string $search + * @param null|int $limit + * @param null|int $offset + * @return string[] an array of all uids + */ + public function getUsers($search = '', $limit = null, $offset = null) { + return array(); + } + + /** + * check if a user exists + * @param string $uid the username + * @return boolean + */ + public function userExists($uid) { + return false; + } + + /** + * get the user's home directory + * @param string $uid the username + * @return boolean + */ + public function getHome($uid) { + return false; + } + + /** + * get display name of the user + * @param string $uid user ID of the user + * @return string display name + */ + public function getDisplayName($uid) { + return $uid; + } + + /** + * Get a list of all display names and user ids. + * + * @param string $search + * @param string|null $limit + * @param string|null $offset + * @return array an array of all displayNames (value) and the corresponding uids (key) + */ + public function getDisplayNames($search = '', $limit = null, $offset = null) { + $displayNames = array(); + $users = $this->getUsers($search, $limit, $offset); + foreach ( $users as $user) { + $displayNames[$user] = $user; + } + return $displayNames; + } + + /** + * Check if a user list is available or not + * @return boolean if users can be listed or not + */ + public function hasUserListings() { + return false; + } +} diff --git a/lib/private/User/Database.php b/lib/private/User/Database.php new file mode 100644 index 00000000000..7a4b58e6f40 --- /dev/null +++ b/lib/private/User/Database.php @@ -0,0 +1,349 @@ +<?php +/** + * @author adrien <adrien.waksberg@believedigital.com> + * @author Aldo "xoen" Giambelluca <xoen@xoen.org> + * @author Arthur Schiwon <blizzz@owncloud.com> + * @author Bart Visscher <bartv@thisnet.nl> + * @author Björn Schießle <schiessle@owncloud.com> + * @author fabian <fabian@web2.0-apps.de> + * @author Georg Ehrke <georg@owncloud.com> + * @author Jakob Sack <mail@jakobsack.de> + * @author Joas Schilling <nickvergessen@owncloud.com> + * @author Jörn Friedrich Dreyer <jfd@butonic.de> + * @author Lukas Reschke <lukas@owncloud.com> + * @author Michael Gapczynski <GapczynskiM@gmail.com> + * @author Morris Jobke <hey@morrisjobke.de> + * @author nishiki <nishiki@yaegashi.fr> + * @author Robin Appelman <icewind@owncloud.com> + * @author Robin McCorkell <robin@mccorkell.me.uk> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * @author Victor Dubiniuk <dubiniuk@owncloud.com> + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ +/* + * + * The following SQL statement is just a help for developers and will not be + * executed! + * + * CREATE TABLE `users` ( + * `uid` varchar(64) COLLATE utf8_unicode_ci NOT NULL, + * `password` varchar(255) COLLATE utf8_unicode_ci NOT NULL, + * PRIMARY KEY (`uid`) + * ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + * + */ + +namespace OC\User; + +use OC\Cache\CappedMemoryCache; + +/** + * Class for user management in a SQL Database (e.g. MySQL, SQLite) + */ +class Database extends \OC\User\Backend implements \OCP\IUserBackend { + /** @var CappedMemoryCache */ + private $cache; + + /** + * OC_User_Database constructor. + */ + public function __construct() { + $this->cache = new CappedMemoryCache(); + } + + /** + * Create a new user + * @param string $uid The username of the user to create + * @param string $password The password of the new user + * @return bool + * + * Creates a new user. Basic checking of username is done in OC_User + * itself, not in its subclasses. + */ + public function createUser($uid, $password) { + if (!$this->userExists($uid)) { + $query = \OC_DB::prepare('INSERT INTO `*PREFIX*users` ( `uid`, `password` ) VALUES( ?, ? )'); + $result = $query->execute(array($uid, \OC::$server->getHasher()->hash($password))); + + return $result ? true : false; + } + + return false; + } + + /** + * delete a user + * @param string $uid The username of the user to delete + * @return bool + * + * Deletes a user + */ + public function deleteUser($uid) { + // Delete user-group-relation + $query = \OC_DB::prepare('DELETE FROM `*PREFIX*users` WHERE `uid` = ?'); + $result = $query->execute(array($uid)); + + if (isset($this->cache[$uid])) { + unset($this->cache[$uid]); + } + + return $result ? true : false; + } + + /** + * Set password + * @param string $uid The username + * @param string $password The new password + * @return bool + * + * Change the password of a user + */ + public function setPassword($uid, $password) { + if ($this->userExists($uid)) { + $query = \OC_DB::prepare('UPDATE `*PREFIX*users` SET `password` = ? WHERE `uid` = ?'); + $result = $query->execute(array(\OC::$server->getHasher()->hash($password), $uid)); + + return $result ? true : false; + } + + return false; + } + + /** + * Set display name + * @param string $uid The username + * @param string $displayName The new display name + * @return bool + * + * Change the display name of a user + */ + public function setDisplayName($uid, $displayName) { + if ($this->userExists($uid)) { + $query = \OC_DB::prepare('UPDATE `*PREFIX*users` SET `displayname` = ? WHERE LOWER(`uid`) = LOWER(?)'); + $query->execute(array($displayName, $uid)); + $this->cache[$uid]['displayname'] = $displayName; + + return true; + } + + return false; + } + + /** + * get display name of the user + * @param string $uid user ID of the user + * @return string display name + */ + public function getDisplayName($uid) { + $this->loadUser($uid); + return empty($this->cache[$uid]['displayname']) ? $uid : $this->cache[$uid]['displayname']; + } + + /** + * Get a list of all display names and user ids. + * + * @param string $search + * @param string|null $limit + * @param string|null $offset + * @return array an array of all displayNames (value) and the corresponding uids (key) + */ + public function getDisplayNames($search = '', $limit = null, $offset = null) { + $parameters = []; + $searchLike = ''; + if ($search !== '') { + $parameters[] = '%' . $search . '%'; + $parameters[] = '%' . $search . '%'; + $searchLike = ' WHERE LOWER(`displayname`) LIKE LOWER(?) OR ' + . 'LOWER(`uid`) LIKE LOWER(?)'; + } + + $displayNames = array(); + $query = \OC_DB::prepare('SELECT `uid`, `displayname` FROM `*PREFIX*users`' + . $searchLike .' ORDER BY `uid` ASC', $limit, $offset); + $result = $query->execute($parameters); + while ($row = $result->fetchRow()) { + $displayNames[$row['uid']] = $row['displayname']; + } + + return $displayNames; + } + + /** + * Check if the password is correct + * @param string $uid The username + * @param string $password The password + * @return string + * + * Check if the password is correct without logging in the user + * returns the user id or false + */ + public function checkPassword($uid, $password) { + $query = \OC_DB::prepare('SELECT `uid`, `password` FROM `*PREFIX*users` WHERE LOWER(`uid`) = LOWER(?)'); + $result = $query->execute(array($uid)); + + $row = $result->fetchRow(); + if ($row) { + $storedHash = $row['password']; + $newHash = ''; + if(\OC::$server->getHasher()->verify($password, $storedHash, $newHash)) { + if(!empty($newHash)) { + $this->setPassword($uid, $password); + } + return $row['uid']; + } + + } + + return false; + } + + /** + * Load an user in the cache + * @param string $uid the username + * @return boolean + */ + private function loadUser($uid) { + if (empty($this->cache[$uid])) { + $query = \OC_DB::prepare('SELECT `uid`, `displayname` FROM `*PREFIX*users` WHERE LOWER(`uid`) = LOWER(?)'); + $result = $query->execute(array($uid)); + + if ($result === false) { + \OCP\Util::writeLog('core', \OC_DB::getErrorMessage(), \OCP\Util::ERROR); + return false; + } + + while ($row = $result->fetchRow()) { + $this->cache[$uid]['uid'] = $row['uid']; + $this->cache[$uid]['displayname'] = $row['displayname']; + } + } + + return true; + } + + /** + * Get a list of all users + * + * @param string $search + * @param null|int $limit + * @param null|int $offset + * @return string[] an array of all uids + */ + public function getUsers($search = '', $limit = null, $offset = null) { + $parameters = []; + $searchLike = ''; + if ($search !== '') { + $parameters[] = '%' . $search . '%'; + $searchLike = ' WHERE LOWER(`uid`) LIKE LOWER(?)'; + } + + $query = \OC_DB::prepare('SELECT `uid` FROM `*PREFIX*users`' . $searchLike . ' ORDER BY `uid` ASC', $limit, $offset); + $result = $query->execute($parameters); + $users = array(); + while ($row = $result->fetchRow()) { + $users[] = $row['uid']; + } + return $users; + } + + /** + * check if a user exists + * @param string $uid the username + * @return boolean + */ + public function userExists($uid) { + $this->loadUser($uid); + return !empty($this->cache[$uid]); + } + + /** + * get the user's home directory + * @param string $uid the username + * @return string|false + */ + public function getHome($uid) { + if ($this->userExists($uid)) { + return \OC::$server->getConfig()->getSystemValue("datadirectory", \OC::$SERVERROOT . "/data") . '/' . $uid; + } + + return false; + } + + /** + * @return bool + */ + public function hasUserListings() { + return true; + } + + /** + * counts the users in the database + * + * @return int|bool + */ + public function countUsers() { + $query = \OC_DB::prepare('SELECT COUNT(*) FROM `*PREFIX*users`'); + $result = $query->execute(); + if ($result === false) { + \OCP\Util::writeLog('core', \OC_DB::getErrorMessage(), \OCP\Util::ERROR); + return false; + } + return $result->fetchOne(); + } + + /** + * returns the username for the given login name in the correct casing + * + * @param string $loginName + * @return string|false + */ + public function loginName2UserName($loginName) { + if ($this->userExists($loginName)) { + return $this->cache[$loginName]['uid']; + } + + return false; + } + + /** + * Backend name to be shown in user management + * @return string the name of the backend to be shown + */ + public function getBackendName(){ + return 'Database'; + } + + public static function preLoginNameUsedAsUserName($param) { + if(!isset($param['uid'])) { + throw new \Exception('key uid is expected to be set in $param'); + } + + $backends = \OC::$server->getUserManager()->getBackends(); + foreach ($backends as $backend) { + if ($backend instanceof \OC\User\Database) { + /** @var \OC\User\Database $backend */ + $uid = $backend->loginName2UserName($param['uid']); + if ($uid !== false) { + $param['uid'] = $uid; + return; + } + } + } + + } +} diff --git a/lib/private/User/LoginException.php b/lib/private/User/LoginException.php new file mode 100644 index 00000000000..84426f7f5da --- /dev/null +++ b/lib/private/User/LoginException.php @@ -0,0 +1,26 @@ +<?php +/** + * @author Morris Jobke <hey@morrisjobke.de> + * @author Robin Appelman <icewind@owncloud.com> + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OC\User; + +class LoginException extends \Exception { +} diff --git a/lib/private/User/Manager.php b/lib/private/User/Manager.php new file mode 100644 index 00000000000..a9d9425e24d --- /dev/null +++ b/lib/private/User/Manager.php @@ -0,0 +1,371 @@ +<?php +/** + * @author Arthur Schiwon <blizzz@owncloud.com> + * @author Joas Schilling <nickvergessen@owncloud.com> + * @author Jörn Friedrich Dreyer <jfd@butonic.de> + * @author Lukas Reschke <lukas@owncloud.com> + * @author Michael U <mdusher@users.noreply.github.com> + * @author Morris Jobke <hey@morrisjobke.de> + * @author RealRancor <Fisch.666@gmx.de> + * @author Robin Appelman <icewind@owncloud.com> + * @author Robin McCorkell <robin@mccorkell.me.uk> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * @author Vincent Chan <plus.vincchan@gmail.com> + * @author Volkan Gezer <volkangezer@gmail.com> + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OC\User; + +use OC\Hooks\PublicEmitter; +use OCP\IUser; +use OCP\IUserBackend; +use OCP\IUserManager; +use OCP\IConfig; + +/** + * Class Manager + * + * Hooks available in scope \OC\User: + * - preSetPassword(\OC\User\User $user, string $password, string $recoverPassword) + * - postSetPassword(\OC\User\User $user, string $password, string $recoverPassword) + * - preDelete(\OC\User\User $user) + * - postDelete(\OC\User\User $user) + * - preCreateUser(string $uid, string $password) + * - postCreateUser(\OC\User\User $user, string $password) + * - change(\OC\User\User $user) + * + * @package OC\User + */ +class Manager extends PublicEmitter implements IUserManager { + /** + * @var \OCP\UserInterface[] $backends + */ + private $backends = array(); + + /** + * @var \OC\User\User[] $cachedUsers + */ + private $cachedUsers = array(); + + /** + * @var \OCP\IConfig $config + */ + private $config; + + /** + * @param \OCP\IConfig $config + */ + public function __construct(IConfig $config = null) { + $this->config = $config; + $cachedUsers = &$this->cachedUsers; + $this->listen('\OC\User', 'postDelete', function ($user) use (&$cachedUsers) { + /** @var \OC\User\User $user */ + unset($cachedUsers[$user->getUID()]); + }); + $this->listen('\OC\User', 'postLogin', function ($user) { + /** @var \OC\User\User $user */ + $user->updateLastLoginTimestamp(); + }); + $this->listen('\OC\User', 'postRememberedLogin', function ($user) { + /** @var \OC\User\User $user */ + $user->updateLastLoginTimestamp(); + }); + } + + /** + * Get the active backends + * @return \OCP\UserInterface[] + */ + public function getBackends() { + return $this->backends; + } + + /** + * register a user backend + * + * @param \OCP\UserInterface $backend + */ + public function registerBackend($backend) { + $this->backends[] = $backend; + } + + /** + * remove a user backend + * + * @param \OCP\UserInterface $backend + */ + public function removeBackend($backend) { + $this->cachedUsers = array(); + if (($i = array_search($backend, $this->backends)) !== false) { + unset($this->backends[$i]); + } + } + + /** + * remove all user backends + */ + public function clearBackends() { + $this->cachedUsers = array(); + $this->backends = array(); + } + + /** + * get a user by user id + * + * @param string $uid + * @return \OC\User\User|null Either the user or null if the specified user does not exist + */ + public function get($uid) { + if (isset($this->cachedUsers[$uid])) { //check the cache first to prevent having to loop over the backends + return $this->cachedUsers[$uid]; + } + foreach ($this->backends as $backend) { + if ($backend->userExists($uid)) { + return $this->getUserObject($uid, $backend); + } + } + return null; + } + + /** + * get or construct the user object + * + * @param string $uid + * @param \OCP\UserInterface $backend + * @param bool $cacheUser If false the newly created user object will not be cached + * @return \OC\User\User + */ + protected function getUserObject($uid, $backend, $cacheUser = true) { + if (isset($this->cachedUsers[$uid])) { + return $this->cachedUsers[$uid]; + } + + $user = new User($uid, $backend, $this, $this->config); + if ($cacheUser) { + $this->cachedUsers[$uid] = $user; + } + return $user; + } + + /** + * check if a user exists + * + * @param string $uid + * @return bool + */ + public function userExists($uid) { + $user = $this->get($uid); + return ($user !== null); + } + + /** + * Check if the password is valid for the user + * + * @param string $loginName + * @param string $password + * @return mixed the User object on success, false otherwise + */ + public function checkPassword($loginName, $password) { + $loginName = str_replace("\0", '', $loginName); + $password = str_replace("\0", '', $password); + + foreach ($this->backends as $backend) { + if ($backend->implementsActions(\OC\User\Backend::CHECK_PASSWORD)) { + $uid = $backend->checkPassword($loginName, $password); + if ($uid !== false) { + return $this->getUserObject($uid, $backend); + } + } + } + + \OC::$server->getLogger()->warning('Login failed: \''. $loginName .'\' (Remote IP: \''. \OC::$server->getRequest()->getRemoteAddress(). '\')', ['app' => 'core']); + return false; + } + + /** + * search by user id + * + * @param string $pattern + * @param int $limit + * @param int $offset + * @return \OC\User\User[] + */ + public function search($pattern, $limit = null, $offset = null) { + $users = array(); + foreach ($this->backends as $backend) { + $backendUsers = $backend->getUsers($pattern, $limit, $offset); + if (is_array($backendUsers)) { + foreach ($backendUsers as $uid) { + $users[$uid] = $this->getUserObject($uid, $backend); + } + } + } + + uasort($users, function ($a, $b) { + /** + * @var \OC\User\User $a + * @var \OC\User\User $b + */ + return strcmp($a->getUID(), $b->getUID()); + }); + return $users; + } + + /** + * search by displayName + * + * @param string $pattern + * @param int $limit + * @param int $offset + * @return \OC\User\User[] + */ + public function searchDisplayName($pattern, $limit = null, $offset = null) { + $users = array(); + foreach ($this->backends as $backend) { + $backendUsers = $backend->getDisplayNames($pattern, $limit, $offset); + if (is_array($backendUsers)) { + foreach ($backendUsers as $uid => $displayName) { + $users[] = $this->getUserObject($uid, $backend); + } + } + } + + usort($users, function ($a, $b) { + /** + * @var \OC\User\User $a + * @var \OC\User\User $b + */ + return strcmp($a->getDisplayName(), $b->getDisplayName()); + }); + return $users; + } + + /** + * @param string $uid + * @param string $password + * @throws \Exception + * @return bool|\OC\User\User the created user or false + */ + public function createUser($uid, $password) { + $l = \OC::$server->getL10N('lib'); + // Check the name for bad characters + // Allowed are: "a-z", "A-Z", "0-9" and "_.@-'" + if (preg_match('/[^a-zA-Z0-9 _\.@\-\']/', $uid)) { + throw new \Exception($l->t('Only the following characters are allowed in a username:' + . ' "a-z", "A-Z", "0-9", and "_.@-\'"')); + } + // No empty username + if (trim($uid) == '') { + throw new \Exception($l->t('A valid username must be provided')); + } + // No whitespace at the beginning or at the end + if (strlen(trim($uid, "\t\n\r\0\x0B\xe2\x80\x8b")) !== strlen(trim($uid))) { + throw new \Exception($l->t('Username contains whitespace at the beginning or at the end')); + } + // No empty password + if (trim($password) == '') { + throw new \Exception($l->t('A valid password must be provided')); + } + + // Check if user already exists + if ($this->userExists($uid)) { + throw new \Exception($l->t('The username is already being used')); + } + + $this->emit('\OC\User', 'preCreateUser', array($uid, $password)); + foreach ($this->backends as $backend) { + if ($backend->implementsActions(\OC\User\Backend::CREATE_USER)) { + $backend->createUser($uid, $password); + $user = $this->getUserObject($uid, $backend); + $this->emit('\OC\User', 'postCreateUser', array($user, $password)); + return $user; + } + } + return false; + } + + /** + * returns how many users per backend exist (if supported by backend) + * + * @return array an array of backend class as key and count number as value + */ + public function countUsers() { + $userCountStatistics = array(); + foreach ($this->backends as $backend) { + if ($backend->implementsActions(\OC\User\Backend::COUNT_USERS)) { + $backendUsers = $backend->countUsers(); + if($backendUsers !== false) { + if($backend instanceof IUserBackend) { + $name = $backend->getBackendName(); + } else { + $name = get_class($backend); + } + if(isset($userCountStatistics[$name])) { + $userCountStatistics[$name] += $backendUsers; + } else { + $userCountStatistics[$name] = $backendUsers; + } + } + } + } + return $userCountStatistics; + } + + /** + * The callback is executed for each user on each backend. + * If the callback returns false no further users will be retrieved. + * + * @param \Closure $callback + * @param string $search + * @since 9.0.0 + */ + public function callForAllUsers(\Closure $callback, $search = '') { + foreach($this->getBackends() as $backend) { + $limit = 500; + $offset = 0; + do { + $users = $backend->getUsers($search, $limit, $offset); + foreach ($users as $uid) { + if (!$backend->userExists($uid)) { + continue; + } + $user = $this->getUserObject($uid, $backend, false); + $return = $callback($user); + if ($return === false) { + break; + } + } + $offset += $limit; + } while (count($users) >= $limit); + } + } + + /** + * @param string $email + * @return IUser[] + * @since 9.1.0 + */ + public function getByEmail($email) { + $userIds = $this->config->getUsersForUserValue('settings', 'email', $email); + + return array_map(function($uid) { + return $this->get($uid); + }, $userIds); + } +} diff --git a/lib/private/User/NoUserException.php b/lib/private/User/NoUserException.php new file mode 100644 index 00000000000..afd5c729fcf --- /dev/null +++ b/lib/private/User/NoUserException.php @@ -0,0 +1,24 @@ +<?php +/** + * @author Jörn Friedrich Dreyer <jfd@butonic.de> + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OC\User; + +class NoUserException extends \Exception {}
\ No newline at end of file diff --git a/lib/private/User/Session.php b/lib/private/User/Session.php new file mode 100644 index 00000000000..c7f8a6920de --- /dev/null +++ b/lib/private/User/Session.php @@ -0,0 +1,322 @@ +<?php +/** + * @author Arthur Schiwon <blizzz@owncloud.com> + * @author Bernhard Posselt <dev@bernhard-posselt.com> + * @author Joas Schilling <nickvergessen@owncloud.com> + * @author Jörn Friedrich Dreyer <jfd@butonic.de> + * @author Lukas Reschke <lukas@owncloud.com> + * @author Morris Jobke <hey@morrisjobke.de> + * @author Robin Appelman <icewind@owncloud.com> + * @author Robin McCorkell <robin@mccorkell.me.uk> + * @author Roeland Jago Douma <rullzer@owncloud.com> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * @author Vincent Petry <pvince81@owncloud.com> + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OC\User; + +use OC\Hooks\Emitter; +use OCP\ISession; +use OCP\IUserManager; +use OCP\IUserSession; + +/** + * Class Session + * + * Hooks available in scope \OC\User: + * - preSetPassword(\OC\User\User $user, string $password, string $recoverPassword) + * - postSetPassword(\OC\User\User $user, string $password, string $recoverPassword) + * - preDelete(\OC\User\User $user) + * - postDelete(\OC\User\User $user) + * - preCreateUser(string $uid, string $password) + * - postCreateUser(\OC\User\User $user) + * - preLogin(string $user, string $password) + * - postLogin(\OC\User\User $user, string $password) + * - preRememberedLogin(string $uid) + * - postRememberedLogin(\OC\User\User $user) + * - logout() + * + * @package OC\User + */ +class Session implements IUserSession, Emitter { + /** @var \OC\User\Manager $manager */ + private $manager; + + /** @var \OC\Session\Session $session */ + private $session; + + /** @var \OC\User\User $activeUser */ + protected $activeUser; + + /** + * @param IUserManager $manager + * @param ISession $session + */ + public function __construct(IUserManager $manager, ISession $session) { + $this->manager = $manager; + $this->session = $session; + } + + /** + * @param string $scope + * @param string $method + * @param callable $callback + */ + public function listen($scope, $method, callable $callback) { + $this->manager->listen($scope, $method, $callback); + } + + /** + * @param string $scope optional + * @param string $method optional + * @param callable $callback optional + */ + public function removeListener($scope = null, $method = null, callable $callback = null) { + $this->manager->removeListener($scope, $method, $callback); + } + + /** + * get the manager object + * + * @return \OC\User\Manager + */ + public function getManager() { + return $this->manager; + } + + /** + * get the session object + * + * @return ISession + */ + public function getSession() { + return $this->session; + } + + /** + * set the session object + * + * @param ISession $session + */ + public function setSession(ISession $session) { + if ($this->session instanceof ISession) { + $this->session->close(); + } + $this->session = $session; + $this->activeUser = null; + } + + /** + * set the currently active user + * + * @param \OC\User\User|null $user + */ + public function setUser($user) { + if (is_null($user)) { + $this->session->remove('user_id'); + } else { + $this->session->set('user_id', $user->getUID()); + } + $this->activeUser = $user; + } + + /** + * get the current active user + * + * @return \OCP\IUser|null Current user, otherwise null + */ + public function getUser() { + // FIXME: This is a quick'n dirty work-around for the incognito mode as + // described at https://github.com/owncloud/core/pull/12912#issuecomment-67391155 + if (\OC_User::isIncognitoMode()) { + return null; + } + if ($this->activeUser) { + return $this->activeUser; + } else { + $uid = $this->session->get('user_id'); + if ($uid !== null) { + $this->activeUser = $this->manager->get($uid); + return $this->activeUser; + } else { + return null; + } + } + } + + /** + * Checks whether the user is logged in + * + * @return bool if logged in + */ + public function isLoggedIn() { + $user = $this->getUser(); + if (is_null($user)) { + return false; + } + + return $user->isEnabled(); + } + + /** + * set the login name + * + * @param string|null $loginName for the logged in user + */ + public function setLoginName($loginName) { + if (is_null($loginName)) { + $this->session->remove('loginname'); + } else { + $this->session->set('loginname', $loginName); + } + } + + /** + * get the login name of the current user + * + * @return string + */ + public function getLoginName() { + if ($this->activeUser) { + return $this->session->get('loginname'); + } else { + $uid = $this->session->get('user_id'); + if ($uid) { + $this->activeUser = $this->manager->get($uid); + return $this->session->get('loginname'); + } else { + return null; + } + } + } + + /** + * try to login with the provided credentials + * + * @param string $uid + * @param string $password + * @return boolean|null + * @throws LoginException + */ + public function login($uid, $password) { + $this->session->regenerateId(); + $this->manager->emit('\OC\User', 'preLogin', array($uid, $password)); + $user = $this->manager->checkPassword($uid, $password); + if ($user !== false) { + if (!is_null($user)) { + if ($user->isEnabled()) { + $this->setUser($user); + $this->setLoginName($uid); + $this->manager->emit('\OC\User', 'postLogin', array($user, $password)); + if ($this->isLoggedIn()) { + return true; + } else { + // injecting l10n does not work - there is a circular dependency between session and \OCP\L10N\IFactory + $message = \OC::$server->getL10N('lib')->t('Login canceled by app'); + throw new LoginException($message); + } + } else { + // injecting l10n does not work - there is a circular dependency between session and \OCP\L10N\IFactory + $message = \OC::$server->getL10N('lib')->t('User disabled'); + throw new LoginException($message); + } + } + } + return false; + } + + /** + * perform login using the magic cookie (remember login) + * + * @param string $uid the username + * @param string $currentToken + * @return bool + */ + public function loginWithCookie($uid, $currentToken) { + $this->session->regenerateId(); + $this->manager->emit('\OC\User', 'preRememberedLogin', array($uid)); + $user = $this->manager->get($uid); + if (is_null($user)) { + // user does not exist + return false; + } + + // get stored tokens + $tokens = \OC::$server->getConfig()->getUserKeys($uid, 'login_token'); + // test cookies token against stored tokens + if (!in_array($currentToken, $tokens, true)) { + return false; + } + // replace successfully used token with a new one + \OC::$server->getConfig()->deleteUserValue($uid, 'login_token', $currentToken); + $newToken = \OC::$server->getSecureRandom()->generate(32); + \OC::$server->getConfig()->setUserValue($uid, 'login_token', $newToken, time()); + $this->setMagicInCookie($user->getUID(), $newToken); + + //login + $this->setUser($user); + $this->manager->emit('\OC\User', 'postRememberedLogin', array($user)); + return true; + } + + /** + * logout the user from the session + */ + public function logout() { + $this->manager->emit('\OC\User', 'logout'); + $this->setUser(null); + $this->setLoginName(null); + $this->unsetMagicInCookie(); + $this->session->clear(); + } + + /** + * Set cookie value to use in next page load + * + * @param string $username username to be set + * @param string $token + */ + public function setMagicInCookie($username, $token) { + $secureCookie = \OC::$server->getRequest()->getServerProtocol() === 'https'; + $expires = time() + \OC::$server->getConfig()->getSystemValue('remember_login_cookie_lifetime', 60 * 60 * 24 * 15); + setcookie("oc_username", $username, $expires, \OC::$WEBROOT, '', $secureCookie, true); + setcookie("oc_token", $token, $expires, \OC::$WEBROOT, '', $secureCookie, true); + setcookie("oc_remember_login", "1", $expires, \OC::$WEBROOT, '', $secureCookie, true); + } + + /** + * Remove cookie for "remember username" + */ + public function unsetMagicInCookie() { + //TODO: DI for cookies and IRequest + $secureCookie = \OC::$server->getRequest()->getServerProtocol() === 'https'; + + unset($_COOKIE["oc_username"]); //TODO: DI + unset($_COOKIE["oc_token"]); + unset($_COOKIE["oc_remember_login"]); + setcookie('oc_username', '', time() - 3600, \OC::$WEBROOT, '',$secureCookie, true); + setcookie('oc_token', '', time() - 3600, \OC::$WEBROOT, '', $secureCookie, true); + setcookie('oc_remember_login', '', time() - 3600, \OC::$WEBROOT, '', $secureCookie, true); + // old cookies might be stored under /webroot/ instead of /webroot + // and Firefox doesn't like it! + setcookie('oc_username', '', time() - 3600, \OC::$WEBROOT . '/', '', $secureCookie, true); + setcookie('oc_token', '', time() - 3600, \OC::$WEBROOT . '/', '', $secureCookie, true); + setcookie('oc_remember_login', '', time() - 3600, \OC::$WEBROOT . '/', '', $secureCookie, true); + } +} diff --git a/lib/private/User/User.php b/lib/private/User/User.php new file mode 100644 index 00000000000..66ecbd18534 --- /dev/null +++ b/lib/private/User/User.php @@ -0,0 +1,420 @@ +<?php +/** + * @author Arthur Schiwon <blizzz@owncloud.com> + * @author Bart Visscher <bartv@thisnet.nl> + * @author Björn Schießle <schiessle@owncloud.com> + * @author Joas Schilling <nickvergessen@owncloud.com> + * @author Jörn Friedrich Dreyer <jfd@butonic.de> + * @author Lukas Reschke <lukas@owncloud.com> + * @author Morris Jobke <hey@morrisjobke.de> + * @author Robin Appelman <icewind@owncloud.com> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OC\User; + +use OC\Hooks\Emitter; +use OC_Helper; +use OCP\IAvatarManager; +use OCP\IImage; +use OCP\IURLGenerator; +use OCP\IUser; +use OCP\IConfig; +use OCP\UserInterface; + +class User implements IUser { + /** @var string $uid */ + private $uid; + + /** @var string $displayName */ + private $displayName; + + /** @var UserInterface $backend */ + private $backend; + + /** @var bool $enabled */ + private $enabled; + + /** @var Emitter|Manager $emitter */ + private $emitter; + + /** @var string $home */ + private $home; + + /** @var int $lastLogin */ + private $lastLogin; + + /** @var \OCP\IConfig $config */ + private $config; + + /** @var IAvatarManager */ + private $avatarManager; + + /** @var IURLGenerator */ + private $urlGenerator; + + /** + * @param string $uid + * @param UserInterface $backend + * @param \OC\Hooks\Emitter $emitter + * @param IConfig|null $config + * @param IURLGenerator $urlGenerator + */ + public function __construct($uid, $backend, $emitter = null, IConfig $config = null, $urlGenerator = null) { + $this->uid = $uid; + $this->backend = $backend; + $this->emitter = $emitter; + if(is_null($config)) { + $config = \OC::$server->getConfig(); + } + $this->config = $config; + $this->urlGenerator = $urlGenerator; + $enabled = $this->config->getUserValue($uid, 'core', 'enabled', 'true'); + $this->enabled = ($enabled === 'true'); + $this->lastLogin = $this->config->getUserValue($uid, 'login', 'lastLogin', 0); + if (is_null($this->urlGenerator)) { + $this->urlGenerator = \OC::$server->getURLGenerator(); + } + } + + /** + * get the user id + * + * @return string + */ + public function getUID() { + return $this->uid; + } + + /** + * get the display name for the user, if no specific display name is set it will fallback to the user id + * + * @return string + */ + public function getDisplayName() { + if (!isset($this->displayName)) { + $displayName = ''; + if ($this->backend and $this->backend->implementsActions(\OC\User\Backend::GET_DISPLAYNAME)) { + // get display name and strip whitespace from the beginning and end of it + $backendDisplayName = $this->backend->getDisplayName($this->uid); + if (is_string($backendDisplayName)) { + $displayName = trim($backendDisplayName); + } + } + + if (!empty($displayName)) { + $this->displayName = $displayName; + } else { + $this->displayName = $this->uid; + } + } + return $this->displayName; + } + + /** + * set the displayname for the user + * + * @param string $displayName + * @return bool + */ + public function setDisplayName($displayName) { + $displayName = trim($displayName); + if ($this->backend->implementsActions(\OC\User\Backend::SET_DISPLAYNAME) && !empty($displayName)) { + $result = $this->backend->setDisplayName($this->uid, $displayName); + if ($result) { + $this->displayName = $displayName; + $this->triggerChange('displayName', $displayName); + } + return $result !== false; + } else { + return false; + } + } + + /** + * set the email address of the user + * + * @param string|null $mailAddress + * @return void + * @since 9.0.0 + */ + public function setEMailAddress($mailAddress) { + if($mailAddress === '') { + $this->config->deleteUserValue($this->uid, 'settings', 'email'); + } else { + $this->config->setUserValue($this->uid, 'settings', 'email', $mailAddress); + } + $this->triggerChange('eMailAddress', $mailAddress); + } + + /** + * returns the timestamp of the user's last login or 0 if the user did never + * login + * + * @return int + */ + public function getLastLogin() { + return $this->lastLogin; + } + + /** + * updates the timestamp of the most recent login of this user + */ + public function updateLastLoginTimestamp() { + $this->lastLogin = time(); + \OC::$server->getConfig()->setUserValue( + $this->uid, 'login', 'lastLogin', $this->lastLogin); + } + + /** + * Delete the user + * + * @return bool + */ + public function delete() { + if ($this->emitter) { + $this->emitter->emit('\OC\User', 'preDelete', array($this)); + } + $result = $this->backend->deleteUser($this->uid); + if ($result) { + + // FIXME: Feels like an hack - suggestions? + + // We have to delete the user from all groups + foreach (\OC_Group::getUserGroups($this->uid) as $i) { + \OC_Group::removeFromGroup($this->uid, $i); + } + // Delete the user's keys in preferences + \OC::$server->getConfig()->deleteAllUserValues($this->uid); + + // Delete user files in /data/ + \OC_Helper::rmdirr(\OC_User::getHome($this->uid)); + + // Delete the users entry in the storage table + \OC\Files\Cache\Storage::remove('home::' . $this->uid); + + \OC::$server->getCommentsManager()->deleteReferencesOfActor('users', $this->uid); + \OC::$server->getCommentsManager()->deleteReadMarksFromUser($this); + } + + if ($this->emitter) { + $this->emitter->emit('\OC\User', 'postDelete', array($this)); + } + return !($result === false); + } + + /** + * Set the password of the user + * + * @param string $password + * @param string $recoveryPassword for the encryption app to reset encryption keys + * @return bool + */ + public function setPassword($password, $recoveryPassword = null) { + if ($this->emitter) { + $this->emitter->emit('\OC\User', 'preSetPassword', array($this, $password, $recoveryPassword)); + } + if ($this->backend->implementsActions(\OC\User\Backend::SET_PASSWORD)) { + $result = $this->backend->setPassword($this->uid, $password); + if ($this->emitter) { + $this->emitter->emit('\OC\User', 'postSetPassword', array($this, $password, $recoveryPassword)); + } + return !($result === false); + } else { + return false; + } + } + + /** + * get the users home folder to mount + * + * @return string + */ + public function getHome() { + if (!$this->home) { + if ($this->backend->implementsActions(\OC\User\Backend::GET_HOME) and $home = $this->backend->getHome($this->uid)) { + $this->home = $home; + } elseif ($this->config) { + $this->home = $this->config->getSystemValue('datadirectory') . '/' . $this->uid; + } else { + $this->home = \OC::$SERVERROOT . '/data/' . $this->uid; + } + } + return $this->home; + } + + /** + * Get the name of the backend class the user is connected with + * + * @return string + */ + public function getBackendClassName() { + if($this->backend instanceof \OCP\IUserBackend) { + return $this->backend->getBackendName(); + } + return get_class($this->backend); + } + + /** + * check if the backend allows the user to change his avatar on Personal page + * + * @return bool + */ + public function canChangeAvatar() { + if ($this->backend->implementsActions(\OC\User\Backend::PROVIDE_AVATAR)) { + return $this->backend->canChangeAvatar($this->uid); + } + return true; + } + + /** + * check if the backend supports changing passwords + * + * @return bool + */ + public function canChangePassword() { + return $this->backend->implementsActions(\OC\User\Backend::SET_PASSWORD); + } + + /** + * check if the backend supports changing display names + * + * @return bool + */ + public function canChangeDisplayName() { + if ($this->config->getSystemValue('allow_user_to_change_display_name') === false) { + return false; + } + return $this->backend->implementsActions(\OC\User\Backend::SET_DISPLAYNAME); + } + + /** + * check if the user is enabled + * + * @return bool + */ + public function isEnabled() { + return $this->enabled; + } + + /** + * set the enabled status for the user + * + * @param bool $enabled + */ + public function setEnabled($enabled) { + $this->enabled = $enabled; + $enabled = ($enabled) ? 'true' : 'false'; + $this->config->setUserValue($this->uid, 'core', 'enabled', $enabled); + } + + /** + * get the users email address + * + * @return string|null + * @since 9.0.0 + */ + public function getEMailAddress() { + return $this->config->getUserValue($this->uid, 'settings', 'email', null); + } + + /** + * get the users' quota + * + * @return string + * @since 9.0.0 + */ + public function getQuota() { + $quota = $this->config->getUserValue($this->uid, 'files', 'quota', 'default'); + if($quota === 'default') { + $quota = $this->config->getAppValue('files', 'default_quota', 'none'); + } + return $quota; + } + + /** + * set the users' quota + * + * @param string $quota + * @return void + * @since 9.0.0 + */ + public function setQuota($quota) { + if($quota !== 'none' and $quota !== 'default') { + $quota = OC_Helper::computerFileSize($quota); + $quota = OC_Helper::humanFileSize($quota); + } + $this->config->setUserValue($this->uid, 'files', 'quota', $quota); + $this->triggerChange('quota', $quota); + } + + /** + * get the avatar image if it exists + * + * @param int $size + * @return IImage|null + * @since 9.0.0 + */ + public function getAvatarImage($size) { + // delay the initialization + if (is_null($this->avatarManager)) { + $this->avatarManager = \OC::$server->getAvatarManager(); + } + + $avatar = $this->avatarManager->getAvatar($this->uid); + $image = $avatar->get(-1); + if ($image) { + return $image; + } + + return null; + } + + /** + * get the federation cloud id + * + * @return string + * @since 9.0.0 + */ + public function getCloudId() { + $uid = $this->getUID(); + $server = $this->urlGenerator->getAbsoluteURL('/'); + return $uid . '@' . rtrim( $this->removeProtocolFromUrl($server), '/'); + } + + /** + * @param string $url + * @return string + */ + private function removeProtocolFromUrl($url) { + if (strpos($url, 'https://') === 0) { + return substr($url, strlen('https://')); + } else if (strpos($url, 'http://') === 0) { + return substr($url, strlen('http://')); + } + + return $url; + } + + public function triggerChange($feature, $value = null) { + if ($this->emitter) { + $this->emitter->emit('\OC\User', 'changeUser', array($this, $feature, $value)); + } + } +} |