diff options
-rw-r--r-- | core/Application.php | 13 | ||||
-rw-r--r-- | core/Controller/LoginController.php | 75 | ||||
-rw-r--r-- | core/routes.php | 3 | ||||
-rw-r--r-- | core/templates/login.php | 2 | ||||
-rw-r--r-- | db_structure.xml | 60 | ||||
-rw-r--r-- | lib/base.php | 176 | ||||
-rw-r--r-- | lib/private/Authentication/Exceptions/InvalidTokenException.php | 29 | ||||
-rw-r--r-- | lib/private/Authentication/Token/DefaultToken.php | 58 | ||||
-rw-r--r-- | lib/private/Authentication/Token/DefaultTokenMapper.php | 43 | ||||
-rw-r--r-- | lib/private/Authentication/Token/DefaultTokenProvider.php | 91 | ||||
-rw-r--r-- | lib/private/Authentication/Token/IProvider.php | 35 | ||||
-rw-r--r-- | lib/private/Authentication/Token/IToken.php | 36 | ||||
-rw-r--r-- | lib/private/Server.php | 19 | ||||
-rw-r--r-- | lib/private/User/Session.php | 178 | ||||
-rw-r--r-- | lib/private/legacy/user.php | 25 | ||||
-rw-r--r-- | lib/private/legacy/util.php | 3 |
16 files changed, 596 insertions, 250 deletions
diff --git a/core/Application.php b/core/Application.php index 0a54386a2ce..faadad32989 100644 --- a/core/Application.php +++ b/core/Application.php @@ -1,6 +1,7 @@ <?php /** * @author Bernhard Posselt <dev@bernhard-posselt.com> + * @author Christoph Wurst <christoph@owncloud.com> * @author Lukas Reschke <lukas@owncloud.com> * @author Morris Jobke <hey@morrisjobke.de> * @author Roeland Jago Douma <rullzer@owncloud.com> @@ -28,12 +29,13 @@ namespace OC\Core; use OC\AppFramework\Utility\SimpleContainer; use OC\AppFramework\Utility\TimeFactory; +use OC\Core\Controller\AvatarController; use OC\Core\Controller\LoginController; -use \OCP\AppFramework\App; use OC\Core\Controller\LostController; use OC\Core\Controller\UserController; -use OC\Core\Controller\AvatarController; -use \OCP\Util; +use OC_Defaults; +use OCP\AppFramework\App; +use OCP\Util; /** * Class Application @@ -132,6 +134,9 @@ class Application extends App { $container->registerService('UserSession', function(SimpleContainer $c) { return $c->query('ServerContainer')->getUserSession(); }); + $container->registerService('Session', function(SimpleContainer $c) { + return $c->query('ServerContainer')->getSession(); + }); $container->registerService('Cache', function(SimpleContainer $c) { return $c->query('ServerContainer')->getCache(); }); @@ -139,7 +144,7 @@ class Application extends App { return $c->query('ServerContainer')->getUserFolder(); }); $container->registerService('Defaults', function() { - return new \OC_Defaults; + return new OC_Defaults; }); $container->registerService('Mailer', function(SimpleContainer $c) { return $c->query('ServerContainer')->getMailer(); diff --git a/core/Controller/LoginController.php b/core/Controller/LoginController.php index 796706d364a..fe1ad41aedb 100644 --- a/core/Controller/LoginController.php +++ b/core/Controller/LoginController.php @@ -1,5 +1,7 @@ <?php + /** + * @author Christoph Wurst <christoph@owncloud.com> * @author Lukas Reschke <lukas@owncloud.com> * * @copyright Copyright (c) 2016, ownCloud, Inc. @@ -21,7 +23,11 @@ namespace OC\Core\Controller; -use OC\Setup; +use OC; +use OC\User\Session; +use OC_App; +use OC_User; +use OC_Util; use OCP\AppFramework\Controller; use OCP\AppFramework\Http\RedirectResponse; use OCP\AppFramework\Http\TemplateResponse; @@ -31,17 +37,21 @@ use OCP\ISession; use OCP\IURLGenerator; use OCP\IUser; use OCP\IUserManager; -use OCP\IUserSession; class LoginController extends Controller { + /** @var IUserManager */ private $userManager; + /** @var IConfig */ private $config; + /** @var ISession */ private $session; - /** @var IUserSession */ + + /** @var Session */ private $userSession; + /** @var IURLGenerator */ private $urlGenerator; @@ -51,16 +61,12 @@ class LoginController extends Controller { * @param IUserManager $userManager * @param IConfig $config * @param ISession $session - * @param IUserSession $userSession + * @param Session $userSession * @param IURLGenerator $urlGenerator */ - function __construct($appName, - IRequest $request, - IUserManager $userManager, - IConfig $config, - ISession $session, - IUserSession $userSession, - IURLGenerator $urlGenerator) { + function __construct($appName, IRequest $request, IUserManager $userManager, + IConfig $config, ISession $session, Session $userSession, + IURLGenerator $urlGenerator) { parent::__construct($appName, $request); $this->userManager = $userManager; $this->config = $config; @@ -96,18 +102,16 @@ class LoginController extends Controller { * * @return TemplateResponse */ - public function showLoginForm($user, - $redirect_url, - $remember_login) { - if($this->userSession->isLoggedIn()) { - return new RedirectResponse(\OC_Util::getDefaultPageUrl()); + public function showLoginForm($user, $redirect_url, $remember_login) { + if ($this->userSession->isLoggedIn()) { + return new RedirectResponse(OC_Util::getDefaultPageUrl()); } $parameters = array(); $loginMessages = $this->session->get('loginMessages'); $errors = []; $messages = []; - if(is_array($loginMessages)) { + if (is_array($loginMessages)) { list($errors, $messages) = $loginMessages; } $this->session->remove('loginMessages'); @@ -137,8 +141,8 @@ class LoginController extends Controller { } } - $parameters['alt_login'] = \OC_App::getAlternativeLogIns(); - $parameters['rememberLoginAllowed'] = \OC_Util::rememberLoginAllowed(); + $parameters['alt_login'] = OC_App::getAlternativeLogIns(); + $parameters['rememberLoginAllowed'] = OC_Util::rememberLoginAllowed(); $parameters['rememberLoginState'] = !empty($remember_login) ? $remember_login : 0; if (!is_null($user) && $user !== '') { @@ -150,11 +154,36 @@ class LoginController extends Controller { } return new TemplateResponse( - $this->appName, - 'login', - $parameters, - 'guest' + $this->appName, 'login', $parameters, 'guest' ); } + /** + * @NoCSRFRequired + * @PublicPage + * @UseSession + * + * @param string $user + * @param string $password + * @param string $redirect_url + * @return RedirectResponse + */ + public function tryLogin($user, $password, $redirect_url) { + // TODO: Add all the insane error handling + if ($this->userManager->checkPassword($user, $password) === false) { + return new RedirectResponse($this->urlGenerator->linkToRoute('login#showLoginForm')); + } + $this->userSession->createSessionToken($user, $password); + if (!is_null($redirect_url) && $this->userSession->isLoggedIn()) { + $location = OC::$server->getURLGenerator()->getAbsoluteURL(urldecode($redirect_url)); + // Deny the redirect if the URL contains a @ + // This prevents unvalidated redirects like ?redirect_url=:user@domain.com + if (strpos($location, '@') === false) { + return new RedirectResponse($location); + } + } + // TODO: Show invalid login warning + return new RedirectResponse($this->urlGenerator->linkTo('files', 'index')); + } + } diff --git a/core/routes.php b/core/routes.php index a9c800af4e8..e86cd702b86 100644 --- a/core/routes.php +++ b/core/routes.php @@ -42,9 +42,10 @@ $application->registerRoutes($this, [ ['name' => 'avatar#postCroppedAvatar', 'url' => '/avatar/cropped', 'verb' => 'POST'], ['name' => 'avatar#getTmpAvatar', 'url' => '/avatar/tmp', 'verb' => 'GET'], ['name' => 'avatar#postAvatar', 'url' => '/avatar/', 'verb' => 'POST'], + ['name' => 'login#tryLogin', 'url' => '/login', 'verb' => 'POST'], ['name' => 'login#showLoginForm', 'url' => '/login', 'verb' => 'GET'], ['name' => 'login#logout', 'url' => '/logout', 'verb' => 'GET'], - ] + ], ]); // Post installation check diff --git a/core/templates/login.php b/core/templates/login.php index 86c186928cc..45814fc71d9 100644 --- a/core/templates/login.php +++ b/core/templates/login.php @@ -9,7 +9,7 @@ script('core', [ ?> <!--[if IE 8]><style>input[type="checkbox"]{padding:0;}</style><![endif]--> -<form method="post" name="login" action="<?php p(OC::$WEBROOT) ?>/"> +<form method="post" name="login"> <fieldset> <?php if (!empty($_['redirect_url'])) { print_unescaped('<input type="hidden" name="redirect_url" value="' . \OCP\Util::sanitizeHTML($_['redirect_url']) . '">'); diff --git a/db_structure.xml b/db_structure.xml index 99541a4f901..cde8f52dc67 100644 --- a/db_structure.xml +++ b/db_structure.xml @@ -1032,6 +1032,66 @@ </table> <table> + <name>*dbprefix*authtoken</name> + + <declaration> + + <field> + <name>id</name> + <type>integer</type> + <default>0</default> + <notnull>true</notnull> + <autoincrement>1</autoincrement> + <unsigned>true</unsigned> + <length>4</length> + </field> + + <!-- Foreign Key users::uid --> + <field> + <name>uid</name> + <type>text</type> + <default></default> + <notnull>true</notnull> + <length>64</length> + </field> + + <field> + <name>password</name> + <type>text</type> + <default></default> + <notnull>true</notnull> + <length>100</length> + </field> + + <field> + <name>name</name> + <type>text</type> + <default></default> + <notnull>true</notnull> + <length>100</length> + </field> + + <field> + <name>token</name> + <type>text</type> + <default></default> + <notnull>true</notnull> + <length>100</length> + </field> + + <index> + <name>authtoken_token_index</name> + <unique>true</unique> + <field> + <name>token</name> + <sorting>ascending</sorting> + </field> + </index> + + </declaration> + </table> + + <table> <!-- List of tags (category) + a unique tag id (id) per user (uid) and type. diff --git a/lib/base.php b/lib/base.php index 0ea8a117e98..fd8f39e0b8d 100644 --- a/lib/base.php +++ b/lib/base.php @@ -856,7 +856,10 @@ class OC { } else { // For guests: Load only filesystem and logging OC_App::loadApps(array('filesystem', 'logging')); - \OC_User::tryBasicAuthLogin(); + $userSession = self::$server->getUserSession(); + if (!$userSession->tryTokenLogin()) { + $userSession->tryBasicAuthLogin(); + } } } @@ -878,17 +881,6 @@ class OC { } } - // Handle redirect URL for logged in users - if (isset($_REQUEST['redirect_url']) && OC_User::isLoggedIn()) { - $location = \OC::$server->getURLGenerator()->getAbsoluteURL(urldecode($_REQUEST['redirect_url'])); - - // Deny the redirect if the URL contains a @ - // This prevents unvalidated redirects like ?redirect_url=:user@domain.com - if (strpos($location, '@') === false) { - header('Location: ' . $location); - return; - } - } // Handle WebDAV if ($_SERVER['REQUEST_METHOD'] == 'PROPFIND') { // not allowed any more to prevent people @@ -904,11 +896,12 @@ class OC { OC_App::loadApps(); OC_User::setupBackends(); OC_Util::setupFS(); + // FIXME // Redirect to default application OC_Util::redirectToDefaultPage(); } else { // Not handled and not logged in - self::handleLogin(); + header('Location: '.\OC::$server->getURLGenerator()->linkToRouteAbsolute('core.login.showLoginForm')); } } @@ -932,163 +925,6 @@ class OC { } } } - - protected static function handleLogin() { - OC_App::loadApps(array('prelogin')); - $error = array(); - $messages = []; - - try { - // auth possible via apache module? - if (OC::tryApacheAuth()) { - $error[] = 'apacheauthfailed'; - } // remember was checked after last login - elseif (OC::tryRememberLogin()) { - $error[] = 'invalidcookie'; - } // logon via web form - elseif (OC::tryFormLogin()) { - $error[] = 'invalidpassword'; - } - } catch (\OC\User\LoginException $e) { - $messages[] = $e->getMessage(); - } catch (\Exception $ex) { - \OCP\Util::logException('handleLogin', $ex); - // do not disclose information. show generic error - $error[] = 'internalexception'; - } - - if(!\OC::$server->getUserSession()->isLoggedIn()) { - $loginMessages = array(array_unique($error), $messages); - \OC::$server->getSession()->set('loginMessages', $loginMessages); - // Read current user and append if possible - $args = []; - if(isset($_POST['user'])) { - $args['user'] = $_POST['user']; - } - - $redirectionTarget = \OC::$server->getURLGenerator()->linkToRoute('core.login.showLoginForm', $args); - header('Location: ' . $redirectionTarget); - } - } - - /** - * Remove outdated and therefore invalid tokens for a user - * @param string $user - */ - protected static function cleanupLoginTokens($user) { - $config = \OC::$server->getConfig(); - $cutoff = time() - $config->getSystemValue('remember_login_cookie_lifetime', 60 * 60 * 24 * 15); - $tokens = $config->getUserKeys($user, 'login_token'); - foreach ($tokens as $token) { - $time = $config->getUserValue($user, 'login_token', $token); - if ($time < $cutoff) { - $config->deleteUserValue($user, 'login_token', $token); - } - } - } - - /** - * Try to login a user via HTTP authentication - * @return bool|void - */ - protected static function tryApacheAuth() { - $return = OC_User::handleApacheAuth(); - - // if return is true we are logged in -> redirect to the default page - if ($return === true) { - $_REQUEST['redirect_url'] = \OC::$server->getRequest()->getRequestUri(); - OC_Util::redirectToDefaultPage(); - exit; - } - - // in case $return is null apache based auth is not enabled - return is_null($return) ? false : true; - } - - /** - * Try to login a user using the remember me cookie. - * @return bool Whether the provided cookie was valid - */ - protected static function tryRememberLogin() { - if (!isset($_COOKIE["oc_remember_login"]) - || !isset($_COOKIE["oc_token"]) - || !isset($_COOKIE["oc_username"]) - || !$_COOKIE["oc_remember_login"] - || !OC_Util::rememberLoginAllowed() - ) { - return false; - } - - if (\OC::$server->getConfig()->getSystemValue('debug', false)) { - \OCP\Util::writeLog('core', 'Trying to login from cookie', \OCP\Util::DEBUG); - } - - if(OC_User::userExists($_COOKIE['oc_username'])) { - self::cleanupLoginTokens($_COOKIE['oc_username']); - // verify whether the supplied "remember me" token was valid - $granted = OC_User::loginWithCookie( - $_COOKIE['oc_username'], $_COOKIE['oc_token']); - if($granted === true) { - OC_Util::redirectToDefaultPage(); - // doesn't return - } - \OCP\Util::writeLog('core', 'Authentication cookie rejected for user ' . - $_COOKIE['oc_username'], \OCP\Util::WARN); - // if you reach this point you have changed your password - // or you are an attacker - // we can not delete tokens here because users may reach - // this point multiple times after a password change - } - - OC_User::unsetMagicInCookie(); - return true; - } - - /** - * Tries to login a user using the form based authentication - * @return bool|void - */ - protected static function tryFormLogin() { - if (!isset($_POST["user"]) || !isset($_POST['password'])) { - return false; - } - - if(!(\OC::$server->getRequest()->passesCSRFCheck())) { - return false; - } - OC_App::loadApps(); - - //setup extra user backends - OC_User::setupBackends(); - - if (OC_User::login((string)$_POST["user"], (string)$_POST["password"])) { - $userId = OC_User::getUser(); - - // setting up the time zone - if (isset($_POST['timezone-offset'])) { - self::$server->getSession()->set('timezone', (string)$_POST['timezone-offset']); - self::$server->getConfig()->setUserValue($userId, 'core', 'timezone', (string)$_POST['timezone']); - } - - self::cleanupLoginTokens($userId); - if (!empty($_POST["remember_login"])) { - $config = self::$server->getConfig(); - if ($config->getSystemValue('debug', false)) { - self::$server->getLogger()->debug('Setting remember login to cookie', array('app' => 'core')); - } - $token = \OC::$server->getSecureRandom()->generate(32); - $config->setUserValue($userId, 'login_token', $token, time()); - OC_User::setMagicInCookie($userId, $token); - } else { - OC_User::unsetMagicInCookie(); - } - OC_Util::redirectToDefaultPage(); - exit(); - } - return true; - } - } - OC::init(); diff --git a/lib/private/Authentication/Exceptions/InvalidTokenException.php b/lib/private/Authentication/Exceptions/InvalidTokenException.php new file mode 100644 index 00000000000..3e52d3b78f0 --- /dev/null +++ b/lib/private/Authentication/Exceptions/InvalidTokenException.php @@ -0,0 +1,29 @@ +<?php + +/** + * @author Christoph Wurst <christoph@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\Authentication\Exceptions; + +use Exception; + +class InvalidTokenException extends Exception { + +} diff --git a/lib/private/Authentication/Token/DefaultToken.php b/lib/private/Authentication/Token/DefaultToken.php new file mode 100644 index 00000000000..28aee555601 --- /dev/null +++ b/lib/private/Authentication/Token/DefaultToken.php @@ -0,0 +1,58 @@ +<?php + +/** + * @author Christoph Wurst <christoph@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\Authentication\Token; + +use OCP\AppFramework\Db\Entity; + +class DefaultToken extends Entity implements IToken { + + /** + * @var string user UID + */ + protected $uid; + + /** + * @var string encrypted user password + */ + protected $password; + + /** + * @var string token name (e.g. browser/OS) + */ + protected $name; + + /** + * @var string + */ + protected $token; + + /** + * Get the token ID + * + * @return string + */ + public function getId() { + return $token; + } + +} diff --git a/lib/private/Authentication/Token/DefaultTokenMapper.php b/lib/private/Authentication/Token/DefaultTokenMapper.php new file mode 100644 index 00000000000..35989d0d350 --- /dev/null +++ b/lib/private/Authentication/Token/DefaultTokenMapper.php @@ -0,0 +1,43 @@ +<?php + +/** + * @author Christoph Wurst <christoph@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\Authentication\Token; + +use OCP\AppFramework\Db\Mapper; +use OCP\IDBConnection; + +class DefaultTokenMapper extends Mapper { + + public function __construct(IDBConnection $db) { + parent::__construct($db, 'authtoken'); + } + + public function getTokenUser($token) { + $sql = 'SELECT `uid` ' + . 'FROM `' . $this->getTableName() . '` ' + . 'WHERE `token` = ?'; + return $this->findEntity($sql, [ + $token + ]); + } + +} diff --git a/lib/private/Authentication/Token/DefaultTokenProvider.php b/lib/private/Authentication/Token/DefaultTokenProvider.php new file mode 100644 index 00000000000..c8aa396526b --- /dev/null +++ b/lib/private/Authentication/Token/DefaultTokenProvider.php @@ -0,0 +1,91 @@ +<?php + +/** + * @author Christoph Wurst <christoph@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\Authentication\Token; + +use OC\Authentication\Exceptions\InvalidTokenException; +use OCP\AppFramework\Db\DoesNotExistException; +use OCP\IConfig; +use OCP\ILogger; +use OCP\Security\ICrypto; + +class DefaultTokenProvider implements IProvider { + + /** @var DefaultTokenMapper */ + private $mapper; + + /** @var ICrypto */ + private $crypto; + + /** @var IConfig */ + private $config; + + /** @var ILogger $logger */ + private $logger; + + public function __construct(DefaultTokenMapper $mapper, ICrypto $crypto, + IConfig $config, ILogger $logger) { + $this->mapper = $mapper; + $this->crypto = $crypto; + $this->config = $config; + $this->logger = $logger; + } + + /** + * Create and persist a new token + * + * @param string $token + * @param string $uid + * @param string $password + * @return DefaultToken + */ + public function generateToken($token, $uid, $password, $name) { + $dbToken = new DefaultToken(); + $dbToken->setUid($uid); + $secret = $this->config->getSystemValue('secret'); + $dbToken->setPassword($this->crypto->encrypt($password . $secret)); + $dbToken->setName($name); + $dbToken->setToken(hash('sha512', $token)); + + $this->mapper->insert($dbToken); + + return $dbToken; + } + + /** + * @param string $token + * @throws InvalidTokenException + * @return string user UID + */ + public function validateToken($token) { + $this->logger->debug('validating default token <' . $token . '>'); + try { + $dbToken = $this->mapper->getTokenUser(hash('sha512', $token)); + $this->logger->debug('valid token for ' . $dbToken->getUid()); + return $dbToken->getUid(); + } catch (DoesNotExistException $ex) { + $this->logger->warning('invalid token'); + throw new InvalidTokenException(); + } + } + +} diff --git a/lib/private/Authentication/Token/IProvider.php b/lib/private/Authentication/Token/IProvider.php new file mode 100644 index 00000000000..4fceef19a1c --- /dev/null +++ b/lib/private/Authentication/Token/IProvider.php @@ -0,0 +1,35 @@ +<?php + +/** + * @author Christoph Wurst <christoph@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\Authentication\Token; + +use OC\Authentication\Exceptions\InvalidTokenException; + +interface IProvider { + + /** + * @param string $token + * @throws InvalidTokenException + * @return string user UID + */ + public function validateToken($token); +} diff --git a/lib/private/Authentication/Token/IToken.php b/lib/private/Authentication/Token/IToken.php new file mode 100644 index 00000000000..10b54c0d2a8 --- /dev/null +++ b/lib/private/Authentication/Token/IToken.php @@ -0,0 +1,36 @@ +<?php + +/** + * @author Christoph Wurst <christoph@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\Authentication\Token; + +/** + * @since 9.1.0 + */ +interface IToken { + + /** + * Get the token ID + * + * @return string + */ + public function getId(); +} diff --git a/lib/private/Server.php b/lib/private/Server.php index bbe6b88876f..a438523dbcd 100644 --- a/lib/private/Server.php +++ b/lib/private/Server.php @@ -5,6 +5,7 @@ * @author Bernhard Posselt <dev@bernhard-posselt.com> * @author Bernhard Reiter <ockham@raz.or.at> * @author Björn Schießle <schiessle@owncloud.com> + * @author Christoph Wurst <christoph@owncloud.com> * @author Christopher Schäpers <kondou@ts.unde.re> * @author Joas Schilling <nickvergessen@owncloud.com> * @author Jörn Friedrich Dreyer <jfd@butonic.de> @@ -208,12 +209,26 @@ class Server extends ServerContainer implements IServerContainer { }); return $groupManager; }); + $this->registerService('DefaultTokenMapper', function (Server $c) { + $dbConnection = $c->getDatabaseConnection(); + return new Authentication\Token\DefaultTokenMapper($dbConnection); + }); + $this->registerService('DefaultTokenProvider', function (Server $c) { + $mapper = $c->query('DefaultTokenMapper'); + $crypto = $c->getCrypto(); + $config = $c->getConfig(); + $logger = $c->getLogger(); + return new \OC\Authentication\Token\DefaultTokenProvider($mapper, $crypto, $config, $logger); + }); $this->registerService('UserSession', function (Server $c) { $manager = $c->getUserManager(); - $session = new \OC\Session\Memory(''); + $defaultTokenProvider = $c->query('DefaultTokenProvider'); + $tokenProviders = [ + $defaultTokenProvider, + ]; - $userSession = new \OC\User\Session($manager, $session); + $userSession = new \OC\User\Session($manager, $session, $defaultTokenProvider, $tokenProviders); $userSession->listen('\OC\User', 'preCreateUser', function ($uid, $password) { \OC_Hook::emit('OC_User', 'pre_createUser', array('run' => true, 'uid' => $uid, 'password' => $password)); }); diff --git a/lib/private/User/Session.php b/lib/private/User/Session.php index c7f8a6920de..2afe340353b 100644 --- a/lib/private/User/Session.php +++ b/lib/private/User/Session.php @@ -1,7 +1,9 @@ <?php + /** * @author Arthur Schiwon <blizzz@owncloud.com> * @author Bernhard Posselt <dev@bernhard-posselt.com> + * @author Christoph Wurst <christoph@owncloud.com> * @author Joas Schilling <nickvergessen@owncloud.com> * @author Jörn Friedrich Dreyer <jfd@butonic.de> * @author Lukas Reschke <lukas@owncloud.com> @@ -31,8 +33,17 @@ namespace OC\User; +use OC; +use OC\Authentication\Exceptions\InvalidTokenException; +use OC\Authentication\Token\DefaultTokenProvider; +use OC\Authentication\Token\IProvider; use OC\Hooks\Emitter; +use OC\Session\Session; +use OC_User; +use OCA\DAV\Connector\Sabre\Auth; +use OCP\IRequest; use OCP\ISession; +use OCP\IUser; use OCP\IUserManager; use OCP\IUserSession; @@ -55,22 +66,42 @@ use OCP\IUserSession; * @package OC\User */ class Session implements IUserSession, Emitter { - /** @var \OC\User\Manager $manager */ + + /* + * @var Manager $manager + */ private $manager; - /** @var \OC\Session\Session $session */ + /* + * @var Session $session + */ private $session; - /** @var \OC\User\User $activeUser */ + /* + * @var DefaultTokenProvider + */ + private $tokenProvider; + + /** + * @var IProvider[] + */ + private $tokenProviders; + + /** + * @var User $activeUser protected $activeUser; /** * @param IUserManager $manager * @param ISession $session + * @param IProvider[] $tokenProviders */ - public function __construct(IUserManager $manager, ISession $session) { + public function __construct(IUserManager $manager, ISession $session, + DefaultTokenProvider $tokenProvider, array $tokenProviders = []) { $this->manager = $manager; $this->session = $session; + $this->tokenProvider = $tokenProvider; + $this->tokenProviders = $tokenProviders; } /** @@ -87,14 +118,15 @@ class Session implements IUserSession, Emitter { * @param string $method optional * @param callable $callback optional */ - public function removeListener($scope = null, $method = null, callable $callback = null) { + public function removeListener($scope = null, $method = null, + callable $callback = null) { $this->manager->removeListener($scope, $method, $callback); } /** * get the manager object * - * @return \OC\User\Manager + * @return Manager */ public function getManager() { return $this->manager; @@ -125,7 +157,7 @@ class Session implements IUserSession, Emitter { /** * set the currently active user * - * @param \OC\User\User|null $user + * @param User|null $user */ public function setUser($user) { if (is_null($user)) { @@ -139,12 +171,12 @@ class Session implements IUserSession, Emitter { /** * get the current active user * - * @return \OCP\IUser|null Current user, otherwise null + * @return 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()) { + if (OC_User::isIncognitoMode()) { return null; } if ($this->activeUser) { @@ -242,6 +274,101 @@ class Session implements IUserSession, Emitter { } /** + * Tries to login the user with HTTP Basic Authentication + */ + public function tryBasicAuthLogin() { + if (!empty($_SERVER['PHP_AUTH_USER']) && !empty($_SERVER['PHP_AUTH_PW'])) { + $result = $this->login($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']); + if ($result === true) { + /** + * Add DAV authenticated. This should in an ideal world not be + * necessary but the iOS App reads cookies from anywhere instead + * only the DAV endpoint. + * This makes sure that the cookies will be valid for the whole scope + * @see https://github.com/owncloud/core/issues/22893 + */ + $this->session->set( + Auth::DAV_AUTHENTICATED, $this->getUser()->getUID() + ); + } + } + } + + private function loginWithToken($uid) { + //$this->manager->emit('\OC\User', 'preTokenLogin', array($uid)); + $user = $this->manager->get($uid); + if (is_null($user)) { + // user does not exist + return false; + } + + //login + $this->setUser($user); + //$this->manager->emit('\OC\User', 'postTokenLogin', array($user)); + return true; + } + + /** + * Create a new session token for the given user credentials + * + * @param string $uid user UID + * @param string $password + * @return boolean + */ + public function createSessionToken($uid, $password) { + if (is_null($this->manager->get($uid))) { + // User does not exist + return false; + } + $name = isset($request->server['HTTP_USER_AGENT']) ? $request->server['HTTP_USER_AGENT'] : 'unknown device'; + $token = $this->tokenProvider->generateToken($token, $uid, $password, $name); + return $this->loginWithToken($uid); + } + + /** + * @param string $token + * @return boolean + */ + private function validateToken(IRequest $request, $token) { + // TODO: hash token + foreach ($this->tokenProviders as $provider) { + try { + $user = $provider->validateToken($token); + if (!is_null($user)) { + $result = $this->loginWithToken($user); + if ($result) { + // Login success + return true; + } + } + } catch (InvalidTokenException $ex) { + + } + } + return false; + } + + /** + * Tries to login the user with auth token header + * + * @todo check remember me cookie + */ + public function tryTokenLogin() { + // TODO: resolve cyclic dependency and inject IRequest somehow + $request = \OC::$server->getRequest(); + $authHeader = $request->getHeader('Authorization'); + if (strpos($authHeader, 'token ') === false) { + // No auth header, let's try session id + // TODO: use ISession::getId(), https://github.com/owncloud/core/pull/24229 + $sessionId = session_id(); + return $this->validateToken($request, $sessionId); + } else { + $token = substr($authHeader, 6); + return $this->validateToken($request, $token); + } + } + + /** * perform login using the magic cookie (remember login) * * @param string $uid the username @@ -258,15 +385,15 @@ class Session implements IUserSession, Emitter { } // get stored tokens - $tokens = \OC::$server->getConfig()->getUserKeys($uid, 'login_token'); + $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()); + 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 @@ -293,11 +420,11 @@ class Session implements IUserSession, Emitter { * @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); + $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); } /** @@ -305,18 +432,19 @@ class Session implements IUserSession, Emitter { */ public function unsetMagicInCookie() { //TODO: DI for cookies and IRequest - $secureCookie = \OC::$server->getRequest()->getServerProtocol() === 'https'; + $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); + 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); + 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/legacy/user.php b/lib/private/legacy/user.php index 7855b5e7059..575011d3985 100644 --- a/lib/private/legacy/user.php +++ b/lib/private/legacy/user.php @@ -6,6 +6,7 @@ * @author Bart Visscher <bartv@thisnet.nl> * @author Bartek Przybylski <bart.p.pl@gmail.com> * @author Björn Schießle <schiessle@owncloud.com> + * @author Christoph Wurst <christoph@owncloud.com> * @author Florian Preinstorfer <nblock@archlinux.us> * @author Georg Ehrke <georg@owncloud.com> * @author Jakob Sack <mail@jakobsack.de> @@ -155,6 +156,8 @@ class OC_User { * @return boolean|null * * Log in a user and regenerate a new session - if the password is ok + * + * @deprecated Use \OCP\IUserSession::login */ public static function login($loginName, $password) { @@ -284,28 +287,6 @@ class OC_User { } /** - * Tries to login the user with HTTP Basic Authentication - */ - public static function tryBasicAuthLogin() { - if (!empty($_SERVER['PHP_AUTH_USER']) && !empty($_SERVER['PHP_AUTH_PW'])) { - $result = \OC_User::login($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']); - if($result === true) { - /** - * Add DAV authenticated. This should in an ideal world not be - * necessary but the iOS App reads cookies from anywhere instead - * only the DAV endpoint. - * This makes sure that the cookies will be valid for the whole scope - * @see https://github.com/owncloud/core/issues/22893 - */ - \OC::$server->getSession()->set( - \OCA\DAV\Connector\Sabre\Auth::DAV_AUTHENTICATED, - \OC::$server->getUserSession()->getUser()->getUID() - ); - } - } - } - - /** * Check if the user is logged in, considers also the HTTP basic credentials * * @deprecated use \OC::$server->getUserSession()->isLoggedIn() diff --git a/lib/private/legacy/util.php b/lib/private/legacy/util.php index b3432470f03..4f7a8668dfc 100644 --- a/lib/private/legacy/util.php +++ b/lib/private/legacy/util.php @@ -957,8 +957,7 @@ class OC_Util { public static function checkLoggedIn() { // Check if we are a user if (!OC_User::isLoggedIn()) { - header('Location: ' . \OC::$server->getURLGenerator()->linkToRoute( - 'core.login.showLoginForm', + header('Location: ' . \OCP\Util::linkToAbsolute('', 'index.php', [ 'redirect_url' => \OC::$server->getRequest()->getRequestUri() ] |