Merge pull request #24912 from owncloud/session-tokens-apache-auth

Create session tokens for apache auth users
This commit is contained in:
Vincent Petry 2016-06-01 10:56:10 +02:00
commit 7b4459d28d
11 changed files with 116 additions and 13 deletions

View File

@ -1084,8 +1084,7 @@
<name>password</name> <name>password</name>
<type>clob</type> <type>clob</type>
<default></default> <default></default>
<notnull>true</notnull> <notnull>false</notnull>
<length>4000</length>
</field> </field>
<field> <field>

View File

@ -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 PasswordlessTokenException extends Exception {
}

View File

@ -27,7 +27,6 @@ use OCP\AppFramework\Db\Entity;
* @method void setId(int $id) * @method void setId(int $id)
* @method void setUid(string $uid); * @method void setUid(string $uid);
* @method void setLoginName(string $loginName) * @method void setLoginName(string $loginName)
* @method string getLoginName()
* @method void setPassword(string $password) * @method void setPassword(string $password)
* @method void setName(string $name) * @method void setName(string $name)
* @method string getName() * @method string getName()

View File

@ -23,6 +23,7 @@ namespace OC\Authentication\Token;
use Exception; use Exception;
use OC\Authentication\Exceptions\InvalidTokenException; use OC\Authentication\Exceptions\InvalidTokenException;
use OC\Authentication\Exceptions\PasswordlessTokenException;
use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Utility\ITimeFactory; use OCP\AppFramework\Utility\ITimeFactory;
use OCP\IConfig; use OCP\IConfig;
@ -68,7 +69,7 @@ class DefaultTokenProvider implements IProvider {
* @param string $token * @param string $token
* @param string $uid * @param string $uid
* @param string $loginName * @param string $loginName
* @param string $password * @param string|null $password
* @param string $name * @param string $name
* @param int $type token type * @param int $type token type
* @return IToken * @return IToken
@ -77,7 +78,9 @@ class DefaultTokenProvider implements IProvider {
$dbToken = new DefaultToken(); $dbToken = new DefaultToken();
$dbToken->setUid($uid); $dbToken->setUid($uid);
$dbToken->setLoginName($loginName); $dbToken->setLoginName($loginName);
$dbToken->setPassword($this->encryptPassword($password, $token)); if (!is_null($password)) {
$dbToken->setPassword($this->encryptPassword($password, $token));
}
$dbToken->setName($name); $dbToken->setName($name);
$dbToken->setToken($this->hashToken($token)); $dbToken->setToken($this->hashToken($token));
$dbToken->setType($type); $dbToken->setType($type);
@ -136,10 +139,15 @@ class DefaultTokenProvider implements IProvider {
* @param IToken $savedToken * @param IToken $savedToken
* @param string $tokenId session token * @param string $tokenId session token
* @throws InvalidTokenException * @throws InvalidTokenException
* @throws PasswordlessTokenException
* @return string * @return string
*/ */
public function getPassword(IToken $savedToken, $tokenId) { 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);
} }
/** /**

View File

@ -22,6 +22,7 @@
namespace OC\Authentication\Token; namespace OC\Authentication\Token;
use OC\Authentication\Exceptions\InvalidTokenException; use OC\Authentication\Exceptions\InvalidTokenException;
use OC\Authentication\Exceptions\PasswordlessTokenException;
use OCP\IUser; use OCP\IUser;
interface IProvider { interface IProvider {
@ -32,7 +33,7 @@ interface IProvider {
* @param string $token * @param string $token
* @param string $uid * @param string $uid
* @param string $loginName * @param string $loginName
* @param string $password * @param string|null $password
* @param string $name * @param string $name
* @param int $type token type * @param int $type token type
* @return IToken * @return IToken
@ -94,6 +95,7 @@ interface IProvider {
* @param IToken $token * @param IToken $token
* @param string $tokenId * @param string $tokenId
* @throws InvalidTokenException * @throws InvalidTokenException
* @throws PasswordlessTokenException
* @return string * @return string
*/ */
public function getPassword(IToken $token, $tokenId); public function getPassword(IToken $token, $tokenId);

View File

@ -32,6 +32,7 @@ namespace OC\User;
use OC; use OC;
use OC\Authentication\Exceptions\InvalidTokenException; use OC\Authentication\Exceptions\InvalidTokenException;
use OC\Authentication\Exceptions\PasswordlessTokenException;
use OC\Authentication\Token\IProvider; use OC\Authentication\Token\IProvider;
use OC\Authentication\Token\IToken; use OC\Authentication\Token\IToken;
use OC\Hooks\Emitter; use OC\Hooks\Emitter;
@ -46,6 +47,7 @@ use OCP\IUser;
use OCP\IUserManager; use OCP\IUserManager;
use OCP\IUserSession; use OCP\IUserSession;
use OCP\Session\Exceptions\SessionNotAvailableException; use OCP\Session\Exceptions\SessionNotAvailableException;
use OCP\Util;
/** /**
* Class Session * Class Session
@ -220,6 +222,10 @@ class Session implements IUserSession, Emitter {
// An invalid token password was used -> log user out // An invalid token password was used -> log user out
$this->logout(); $this->logout();
return; 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 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 // When logging in with token, the password must be decrypted first before passing to login hook
try { try {
$token = $this->tokenProvider->getToken($password); $token = $this->tokenProvider->getToken($password);
$password = $this->tokenProvider->getPassword($token, $password); try {
$this->manager->emit('\OC\User', 'preLogin', array($uid, $password)); $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) { } catch (InvalidTokenException $ex) {
// Invalid token, nothing to do // Invalid token, nothing to do
} }
@ -359,7 +369,7 @@ class Session implements IUserSession, Emitter {
} }
protected function isTwoFactorEnforced($username) { protected function isTwoFactorEnforced($username) {
\OCP\Util::emitHook( Util::emitHook(
'\OCA\Files_Sharing\API\Server2Server', '\OCA\Files_Sharing\API\Server2Server',
'preLoginNameUsedAsUserName', 'preLoginNameUsedAsUserName',
array('uid' => &$username) array('uid' => &$username)
@ -452,7 +462,7 @@ class Session implements IUserSession, Emitter {
* @param string $password * @param string $password
* @return boolean * @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))) { if (is_null($this->manager->get($uid))) {
// User does not exist // User does not exist
return false; return false;

View File

@ -180,6 +180,8 @@ class OC_User {
self::setUserId($uid); self::setUserId($uid);
self::setDisplayName($uid); self::setDisplayName($uid);
self::getUserSession()->setLoginName($uid); self::getUserSession()->setLoginName($uid);
$request = OC::$server->getRequest();
self::getUserSession()->createSessionToken($request, $uid, $uid);
// setup the filesystem // setup the filesystem
OC_Util::setupFS($uid); OC_Util::setupFS($uid);
// first call the post_login hooks, the login-process needs to be // first call the post_login hooks, the login-process needs to be

View File

@ -23,6 +23,7 @@ namespace OC\Settings\Controller;
use OC\AppFramework\Http; use OC\AppFramework\Http;
use OC\Authentication\Exceptions\InvalidTokenException; use OC\Authentication\Exceptions\InvalidTokenException;
use OC\Authentication\Exceptions\PasswordlessTokenException;
use OC\Authentication\Token\IProvider; use OC\Authentication\Token\IProvider;
use OC\Authentication\Token\IToken; use OC\Authentication\Token\IToken;
use OCP\AppFramework\Controller; use OCP\AppFramework\Controller;
@ -101,7 +102,11 @@ class AuthSettingsController extends Controller {
try { try {
$sessionToken = $this->tokenProvider->getToken($sessionId); $sessionToken = $this->tokenProvider->getToken($sessionId);
$loginName = $sessionToken->getLoginName(); $loginName = $sessionToken->getLoginName();
$password = $this->tokenProvider->getPassword($sessionToken, $sessionId); try {
$password = $this->tokenProvider->getPassword($sessionToken, $sessionId);
} catch (PasswordlessTokenException $ex) {
$password = null;
}
} catch (InvalidTokenException $ex) { } catch (InvalidTokenException $ex) {
$resp = new JSONResponse(); $resp = new JSONResponse();
$resp->setStatus(Http::STATUS_SERVICE_UNAVAILABLE); $resp->setStatus(Http::STATUS_SERVICE_UNAVAILABLE);

View File

@ -134,6 +134,17 @@ class DefaultTokenProviderTest extends TestCase {
$this->assertEquals('passme', $actual); $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 * @expectedException \OC\Authentication\Exceptions\InvalidTokenException
*/ */

View File

@ -621,4 +621,42 @@ class SessionTest extends \Test\TestCase {
$this->invokePrivate($userSession, 'validateSession', [$user]); $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]);
}
} }

View File

@ -25,7 +25,7 @@
// We only can count up. The 4. digit is only for the internal patchlevel to trigger DB upgrades // 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 // between betas, final and RCs. This is _not_ the public version number. Reset minor/patchlevel
// when updating major/minor version number. // when updating major/minor version number.
$OC_Version = array(9, 1, 0, 6); $OC_Version = array(9, 1, 0, 7);
// The human readable string // The human readable string
$OC_VersionString = '9.1.0 beta 1'; $OC_VersionString = '9.1.0 beta 1';