From c58d8159d7bdee93a67a917e16b750fe99df9f99 Mon Sep 17 00:00:00 2001 From: Christoph Wurst Date: Tue, 31 May 2016 10:48:14 +0200 Subject: [PATCH] Create session tokens for apache auth users --- db_structure.xml | 3 +- .../Exceptions/PasswordlessTokenException.php | 29 ++++++++++++++ .../Authentication/Token/DefaultToken.php | 1 - .../Token/DefaultTokenProvider.php | 14 +++++-- .../Authentication/Token/IProvider.php | 4 +- lib/private/User/Session.php | 18 +++++++-- lib/private/legacy/user.php | 2 + .../Controller/AuthSettingsController.php | 7 +++- .../Token/DefaultTokenProviderTest.php | 11 ++++++ tests/lib/User/SessionTest.php | 38 +++++++++++++++++++ version.php | 2 +- 11 files changed, 116 insertions(+), 13 deletions(-) create mode 100644 lib/private/Authentication/Exceptions/PasswordlessTokenException.php diff --git a/db_structure.xml b/db_structure.xml index 9188ad346d..b7dacc05d9 100644 --- a/db_structure.xml +++ b/db_structure.xml @@ -1084,8 +1084,7 @@ password clob - true - 4000 + false diff --git a/lib/private/Authentication/Exceptions/PasswordlessTokenException.php b/lib/private/Authentication/Exceptions/PasswordlessTokenException.php new file mode 100644 index 0000000000..dbe0ee8fbc --- /dev/null +++ b/lib/private/Authentication/Exceptions/PasswordlessTokenException.php @@ -0,0 +1,29 @@ + + * + * @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 + * + */ + +namespace OC\Authentication\Exceptions; + +use Exception; + +class PasswordlessTokenException extends Exception { + +} diff --git a/lib/private/Authentication/Token/DefaultToken.php b/lib/private/Authentication/Token/DefaultToken.php index 4be43fada0..299291e34a 100644 --- a/lib/private/Authentication/Token/DefaultToken.php +++ b/lib/private/Authentication/Token/DefaultToken.php @@ -27,7 +27,6 @@ use OCP\AppFramework\Db\Entity; * @method void setId(int $id) * @method void setUid(string $uid); * @method void setLoginName(string $loginName) - * @method string getLoginName() * @method void setPassword(string $password) * @method void setName(string $name) * @method string getName() diff --git a/lib/private/Authentication/Token/DefaultTokenProvider.php b/lib/private/Authentication/Token/DefaultTokenProvider.php index dd6e264ed3..84effc5f87 100644 --- a/lib/private/Authentication/Token/DefaultTokenProvider.php +++ b/lib/private/Authentication/Token/DefaultTokenProvider.php @@ -23,6 +23,7 @@ namespace OC\Authentication\Token; use Exception; use OC\Authentication\Exceptions\InvalidTokenException; +use OC\Authentication\Exceptions\PasswordlessTokenException; use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Utility\ITimeFactory; use OCP\IConfig; @@ -68,7 +69,7 @@ class DefaultTokenProvider implements IProvider { * @param string $token * @param string $uid * @param string $loginName - * @param string $password + * @param string|null $password * @param string $name * @param int $type token type * @return IToken @@ -77,7 +78,9 @@ class DefaultTokenProvider implements IProvider { $dbToken = new DefaultToken(); $dbToken->setUid($uid); $dbToken->setLoginName($loginName); - $dbToken->setPassword($this->encryptPassword($password, $token)); + if (!is_null($password)) { + $dbToken->setPassword($this->encryptPassword($password, $token)); + } $dbToken->setName($name); $dbToken->setToken($this->hashToken($token)); $dbToken->setType($type); @@ -136,10 +139,15 @@ class DefaultTokenProvider implements IProvider { * @param IToken $savedToken * @param string $tokenId session token * @throws InvalidTokenException + * @throws PasswordlessTokenException * @return string */ public function getPassword(IToken $savedToken, $tokenId) { - return $this->decryptPassword($savedToken->getPassword(), $tokenId); + $password = $savedToken->getPassword(); + if (is_null($password)) { + throw new PasswordlessTokenException(); + } + return $this->decryptPassword($password, $tokenId); } /** diff --git a/lib/private/Authentication/Token/IProvider.php b/lib/private/Authentication/Token/IProvider.php index bdfde62d32..fece7dcb56 100644 --- a/lib/private/Authentication/Token/IProvider.php +++ b/lib/private/Authentication/Token/IProvider.php @@ -22,6 +22,7 @@ namespace OC\Authentication\Token; use OC\Authentication\Exceptions\InvalidTokenException; +use OC\Authentication\Exceptions\PasswordlessTokenException; use OCP\IUser; interface IProvider { @@ -32,7 +33,7 @@ interface IProvider { * @param string $token * @param string $uid * @param string $loginName - * @param string $password + * @param string|null $password * @param string $name * @param int $type token type * @return IToken @@ -94,6 +95,7 @@ interface IProvider { * @param IToken $token * @param string $tokenId * @throws InvalidTokenException + * @throws PasswordlessTokenException * @return string */ public function getPassword(IToken $token, $tokenId); diff --git a/lib/private/User/Session.php b/lib/private/User/Session.php index 20f1812077..362468d410 100644 --- a/lib/private/User/Session.php +++ b/lib/private/User/Session.php @@ -32,6 +32,7 @@ namespace OC\User; use OC; use OC\Authentication\Exceptions\InvalidTokenException; +use OC\Authentication\Exceptions\PasswordlessTokenException; use OC\Authentication\Token\IProvider; use OC\Authentication\Token\IToken; use OC\Hooks\Emitter; @@ -46,6 +47,7 @@ use OCP\IUser; use OCP\IUserManager; use OCP\IUserSession; use OCP\Session\Exceptions\SessionNotAvailableException; +use OCP\Util; /** * Class Session @@ -220,6 +222,10 @@ class Session implements IUserSession, Emitter { // An invalid token password was used -> log user out $this->logout(); return; + } catch (PasswordlessTokenException $ex) { + // Token has no password, nothing to check + $this->session->set('last_login_check', $now); + return; } if ($this->manager->checkPassword($token->getLoginName(), $pwd) === false @@ -297,8 +303,12 @@ class Session implements IUserSession, Emitter { // When logging in with token, the password must be decrypted first before passing to login hook try { $token = $this->tokenProvider->getToken($password); - $password = $this->tokenProvider->getPassword($token, $password); - $this->manager->emit('\OC\User', 'preLogin', array($uid, $password)); + try { + $password = $this->tokenProvider->getPassword($token, $password); + $this->manager->emit('\OC\User', 'preLogin', array($uid, $password)); + } catch (PasswordlessTokenException $ex) { + $this->manager->emit('\OC\User', 'preLogin', array($uid, '')); + } } catch (InvalidTokenException $ex) { // Invalid token, nothing to do } @@ -359,7 +369,7 @@ class Session implements IUserSession, Emitter { } protected function isTwoFactorEnforced($username) { - \OCP\Util::emitHook( + Util::emitHook( '\OCA\Files_Sharing\API\Server2Server', 'preLoginNameUsedAsUserName', array('uid' => &$username) @@ -452,7 +462,7 @@ class Session implements IUserSession, Emitter { * @param string $password * @return boolean */ - public function createSessionToken(IRequest $request, $uid, $loginName, $password) { + public function createSessionToken(IRequest $request, $uid, $loginName, $password = null) { if (is_null($this->manager->get($uid))) { // User does not exist return false; diff --git a/lib/private/legacy/user.php b/lib/private/legacy/user.php index 6e113bd474..d4d84ed3fa 100644 --- a/lib/private/legacy/user.php +++ b/lib/private/legacy/user.php @@ -180,6 +180,8 @@ class OC_User { self::setUserId($uid); self::setDisplayName($uid); self::getUserSession()->setLoginName($uid); + $request = OC::$server->getRequest(); + self::getUserSession()->createSessionToken($request, $uid, $uid); // setup the filesystem OC_Util::setupFS($uid); // first call the post_login hooks, the login-process needs to be diff --git a/settings/Controller/AuthSettingsController.php b/settings/Controller/AuthSettingsController.php index 2417f5f821..fba663b034 100644 --- a/settings/Controller/AuthSettingsController.php +++ b/settings/Controller/AuthSettingsController.php @@ -23,6 +23,7 @@ namespace OC\Settings\Controller; use OC\AppFramework\Http; use OC\Authentication\Exceptions\InvalidTokenException; +use OC\Authentication\Exceptions\PasswordlessTokenException; use OC\Authentication\Token\IProvider; use OC\Authentication\Token\IToken; use OCP\AppFramework\Controller; @@ -101,7 +102,11 @@ class AuthSettingsController extends Controller { try { $sessionToken = $this->tokenProvider->getToken($sessionId); $loginName = $sessionToken->getLoginName(); - $password = $this->tokenProvider->getPassword($sessionToken, $sessionId); + try { + $password = $this->tokenProvider->getPassword($sessionToken, $sessionId); + } catch (PasswordlessTokenException $ex) { + $password = null; + } } catch (InvalidTokenException $ex) { $resp = new JSONResponse(); $resp->setStatus(Http::STATUS_SERVICE_UNAVAILABLE); diff --git a/tests/lib/Authentication/Token/DefaultTokenProviderTest.php b/tests/lib/Authentication/Token/DefaultTokenProviderTest.php index e04424e462..98cee20806 100644 --- a/tests/lib/Authentication/Token/DefaultTokenProviderTest.php +++ b/tests/lib/Authentication/Token/DefaultTokenProviderTest.php @@ -134,6 +134,17 @@ class DefaultTokenProviderTest extends TestCase { $this->assertEquals('passme', $actual); } + /** + * @expectedException \OC\Authentication\Exceptions\PasswordlessTokenException + */ + public function testGetPasswordPasswordLessToken() { + $token = 'token1234'; + $tk = new DefaultToken(); + $tk->setPassword(null); + + $this->tokenProvider->getPassword($tk, $token); + } + /** * @expectedException \OC\Authentication\Exceptions\InvalidTokenException */ diff --git a/tests/lib/User/SessionTest.php b/tests/lib/User/SessionTest.php index 5ff2a16acb..36f14e8549 100644 --- a/tests/lib/User/SessionTest.php +++ b/tests/lib/User/SessionTest.php @@ -621,4 +621,42 @@ class SessionTest extends \Test\TestCase { $this->invokePrivate($userSession, 'validateSession', [$user]); } + public function testValidateSessionNoPassword() { + $userManager = $this->getMock('\OCP\IUserManager'); + $session = $this->getMock('\OCP\ISession'); + $timeFactory = $this->getMock('\OCP\AppFramework\Utility\ITimeFactory'); + $tokenProvider = $this->getMock('\OC\Authentication\Token\IProvider'); + $userSession = $this->getMockBuilder('\OC\User\Session') + ->setConstructorArgs([$userManager, $session, $timeFactory, $tokenProvider, $this->config]) + ->setMethods(['logout']) + ->getMock(); + + $user = $this->getMock('\OCP\IUser'); + $token = $this->getMock('\OC\Authentication\Token\IToken'); + + $session->expects($this->once()) + ->method('getId') + ->will($this->returnValue('sessionid')); + $tokenProvider->expects($this->once()) + ->method('getToken') + ->with('sessionid') + ->will($this->returnValue($token)); + $session->expects($this->once()) + ->method('get') + ->with('last_login_check') + ->will($this->returnValue(1000)); + $timeFactory->expects($this->once()) + ->method('getTime') + ->will($this->returnValue(5000)); + $tokenProvider->expects($this->once()) + ->method('getPassword') + ->with($token, 'sessionid') + ->will($this->throwException(new \OC\Authentication\Exceptions\PasswordlessTokenException())); + $session->expects($this->once()) + ->method('set') + ->with('last_login_check', 5000); + + $this->invokePrivate($userSession, 'validateSession', [$user]); + } + } diff --git a/version.php b/version.php index ffbafb72fe..967f269027 100644 --- a/version.php +++ b/version.php @@ -25,7 +25,7 @@ // We only can count up. The 4. digit is only for the internal patchlevel to trigger DB upgrades // between betas, final and RCs. This is _not_ the public version number. Reset minor/patchlevel // when updating major/minor version number. -$OC_Version = array(9, 1, 0, 6); +$OC_Version = array(9, 1, 0, 7); // The human readable string $OC_VersionString = '9.1.0 beta 1';