From 3ab922601a2e6b9b170007461b9e0718c70bddcd Mon Sep 17 00:00:00 2001 From: Christoph Wurst Date: Tue, 26 Apr 2016 11:32:35 +0200 Subject: [PATCH] Check if session token is valid and log user out if the check fails * Update last_activity timestamp of the session token * Check user backend credentials once in 5 minutes --- core/Controller/LoginController.php | 1 - db_structure.xml | 4 +- .../Authentication/Token/DefaultToken.php | 7 +- .../Token/DefaultTokenMapper.php | 6 +- .../Token/DefaultTokenProvider.php | 67 +++++++++++++++++-- lib/private/User/Session.php | 45 +++++++++++-- lib/private/legacy/user.php | 2 +- 7 files changed, 107 insertions(+), 25 deletions(-) diff --git a/core/Controller/LoginController.php b/core/Controller/LoginController.php index fe1ad41aedb..e13d8ae10d2 100644 --- a/core/Controller/LoginController.php +++ b/core/Controller/LoginController.php @@ -26,7 +26,6 @@ namespace OC\Core\Controller; use OC; use OC\User\Session; use OC_App; -use OC_User; use OC_Util; use OCP\AppFramework\Controller; use OCP\AppFramework\Http\RedirectResponse; diff --git a/db_structure.xml b/db_structure.xml index 68a812a6b8f..dcbf426e5b8 100644 --- a/db_structure.xml +++ b/db_structure.xml @@ -1057,10 +1057,10 @@ password - text + clob true - 100 + 4000 diff --git a/lib/private/Authentication/Token/DefaultToken.php b/lib/private/Authentication/Token/DefaultToken.php index 9bdae789afd..6b859d7d063 100644 --- a/lib/private/Authentication/Token/DefaultToken.php +++ b/lib/private/Authentication/Token/DefaultToken.php @@ -51,13 +51,8 @@ class DefaultToken extends Entity implements IToken { */ protected $lastActivity; - /** - * Get the token ID - * - * @return string - */ public function getId() { - return $this->token; + return $this->id; } } diff --git a/lib/private/Authentication/Token/DefaultTokenMapper.php b/lib/private/Authentication/Token/DefaultTokenMapper.php index 9a73192c0d8..d54d2489399 100644 --- a/lib/private/Authentication/Token/DefaultTokenMapper.php +++ b/lib/private/Authentication/Token/DefaultTokenMapper.php @@ -61,10 +61,10 @@ class DefaultTokenMapper extends Mapper { * * @param string $token * @throws DoesNotExistException - * @return string + * @return DefaultToken */ - public function getTokenUser($token) { - $sql = 'SELECT `uid` ' + public function getToken($token) { + $sql = 'SELECT `id`, `uid`, `password`, `name`, `token`, `last_activity` ' . 'FROM `' . $this->getTableName() . '` ' . 'WHERE `token` = ?'; return $this->findEntity($sql, [ diff --git a/lib/private/Authentication/Token/DefaultTokenProvider.php b/lib/private/Authentication/Token/DefaultTokenProvider.php index 71f798da370..b3564e0e81b 100644 --- a/lib/private/Authentication/Token/DefaultTokenProvider.php +++ b/lib/private/Authentication/Token/DefaultTokenProvider.php @@ -48,8 +48,7 @@ class DefaultTokenProvider implements IProvider { * @param IConfig $config * @param ILogger $logger */ - public function __construct(DefaultTokenMapper $mapper, ICrypto $crypto, - IConfig $config, ILogger $logger) { + public function __construct(DefaultTokenMapper $mapper, ICrypto $crypto, IConfig $config, ILogger $logger) { $this->mapper = $mapper; $this->crypto = $crypto; $this->config = $config; @@ -67,8 +66,7 @@ class DefaultTokenProvider implements IProvider { 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->setPassword($this->encryptPassword($password, $token)); $dbToken->setName($name); $dbToken->setToken($this->hashToken($token)); $dbToken->setLastActivity(time()); @@ -78,6 +76,37 @@ class DefaultTokenProvider implements IProvider { return $dbToken; } + /** + * Update token activity timestamp + * + * @param DefaultToken $token + */ + public function updateToken(DefaultToken $token) { + $token->setLastActivity(time()); + + $this->mapper->update($token); + } + + /** + * @param string $token + * @throws InvalidTokenException + */ + public function getToken($token) { + try { + return $this->mapper->getToken($this->hashToken($token)); + } catch (DoesNotExistException $ex) { + throw new InvalidTokenException(); + } + } + + /** + * @param DefaultToken $savedToken + * @param string $token session token + */ + public function getPassword(DefaultToken $savedToken, $token) { + return $this->decryptPassword($savedToken->getPassword(), $token); + } + /** * Invalidate (delete) the given session token * @@ -104,7 +133,7 @@ class DefaultTokenProvider implements IProvider { public function validateToken($token) { $this->logger->debug('validating default token <' . $token . '>'); try { - $dbToken = $this->mapper->getTokenUser($this->hashToken($token)); + $dbToken = $this->mapper->getToken($this->hashToken($token)); $this->logger->debug('valid token for ' . $dbToken->getUid()); return $dbToken->getUid(); } catch (DoesNotExistException $ex) { @@ -121,4 +150,32 @@ class DefaultTokenProvider implements IProvider { return hash('sha512', $token); } + /** + * Encrypt the given password + * + * The token is used as key + * + * @param string $password + * @param string $token + * @return string encrypted password + */ + private function encryptPassword($password, $token) { + $secret = $this->config->getSystemValue('secret'); + return $this->crypto->encrypt($password, $token . $secret); + } + + /** + * Decrypt the given password + * + * The token is used as key + * + * @param string $password + * @param string $token + * @return string the decrypted key + */ + private function decryptPassword($password, $token) { + $secret = $this->config->getSystemValue('secret'); + return $this->crypto->decrypt($password, $token . $secret); + } + } diff --git a/lib/private/User/Session.php b/lib/private/User/Session.php index 9db503e6add..7d4594e7205 100644 --- a/lib/private/User/Session.php +++ b/lib/private/User/Session.php @@ -96,8 +96,7 @@ class Session implements IUserSession, Emitter { * @param ISession $session * @param IProvider[] $tokenProviders */ - public function __construct(IUserManager $manager, ISession $session, - DefaultTokenProvider $tokenProvider, array $tokenProviders = []) { + public function __construct(IUserManager $manager, ISession $session, DefaultTokenProvider $tokenProvider, array $tokenProviders = []) { $this->manager = $manager; $this->session = $session; $this->tokenProvider = $tokenProvider; @@ -118,8 +117,7 @@ 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); } @@ -183,8 +181,7 @@ class Session implements IUserSession, Emitter { return $this->activeUser; } else { $uid = $this->session->get('user_id'); - if ($uid !== null) { - $this->activeUser = $this->manager->get($uid); + if ($uid !== null && $this->isValidSession($uid)) { return $this->activeUser; } else { return null; @@ -192,6 +189,41 @@ class Session implements IUserSession, Emitter { } } + private function isValidSession($uid) { + $this->activeUser = $this->manager->get($uid); + if (is_null($this->activeUser)) { + // User does not exist + return false; + } + // TODO: use ISession::getId(), https://github.com/owncloud/core/pull/24229 + $sessionId = session_id(); + try { + $token = $this->tokenProvider->getToken($sessionId); + } catch (InvalidTokenException $ex) { + // Session was inalidated + $this->logout(); + return false; + } + + // Check whether login credentials are still valid + // This check is performed each 5 minutes + $lastCheck = $this->session->get('last_login_check') ? : 0; + if ($lastCheck < (time() - 60 * 5)) { + $pwd = $this->tokenProvider->getPassword($token, $sessionId); + if ($this->manager->checkPassword($uid, $pwd) === false) { + // Password has changed -> log user out + $this->logout(); + return false; + } + $this->session->set('last_login_check', time()); + } + + // Session is valid, so the token can be refreshed + $this->tokenProvider->updateToken($token); + + return true; + } + /** * Checks whether the user is logged in * @@ -334,7 +366,6 @@ class Session implements IUserSession, Emitter { * @return boolean */ private function validateToken(IRequest $request, $token) { - // TODO: hash token foreach ($this->tokenProviders as $provider) { try { $user = $provider->validateToken($token); diff --git a/lib/private/legacy/user.php b/lib/private/legacy/user.php index 575011d3985..ca408d347bd 100644 --- a/lib/private/legacy/user.php +++ b/lib/private/legacy/user.php @@ -68,7 +68,7 @@ class OC_User { private static $_setupedBackends = array(); - // bool, stores if a user want to access a resource anonymously, e.g if he opens a public link + // bool, stores if a user want to access a resource anonymously, e.g if they open a public link private static $incognitoMode = false; /**