store last check timestamp in token instead of session

This commit is contained in:
Christoph Wurst 2016-06-17 13:59:15 +02:00
parent c4149c59c2
commit 0c0a216f42
No known key found for this signature in database
GPG Key ID: FEECD2543CA6EAF0
8 changed files with 161 additions and 78 deletions

View File

@ -1120,6 +1120,15 @@
<length>4</length> <length>4</length>
</field> </field>
<field>
<name>last_check</name>
<type>integer</type>
<default>0</default>
<notnull>true</notnull>
<unsigned>true</unsigned>
<length>4</length>
</field>
<index> <index>
<name>authtoken_token_index</name> <name>authtoken_token_index</name>
<unique>true</unique> <unique>true</unique>

View File

@ -74,6 +74,11 @@ class DefaultToken extends Entity implements IToken {
*/ */
protected $lastActivity; protected $lastActivity;
/**
* @var int
*/
protected $lastCheck;
public function getId() { public function getId() {
return $this->id; return $this->id;
} }
@ -109,4 +114,22 @@ class DefaultToken extends Entity implements IToken {
]; ];
} }
/**
* Get the timestamp of the last password check
*
* @return int
*/
public function getLastCheck() {
return parent::getLastCheck();
}
/**
* Get the timestamp of the last password check
*
* @param int $time
*/
public function setLastCheck($time) {
return parent::setLastCheck($time);
}
} }

View File

@ -70,7 +70,7 @@ class DefaultTokenMapper extends Mapper {
public function getToken($token) { public function getToken($token) {
/* @var $qb IQueryBuilder */ /* @var $qb IQueryBuilder */
$qb = $this->db->getQueryBuilder(); $qb = $this->db->getQueryBuilder();
$result = $qb->select('id', 'uid', 'login_name', 'password', 'name', 'type', 'token', 'last_activity') $result = $qb->select('id', 'uid', 'login_name', 'password', 'name', 'type', 'token', 'last_activity', 'last_check')
->from('authtoken') ->from('authtoken')
->where($qb->expr()->eq('token', $qb->createParameter('token'))) ->where($qb->expr()->eq('token', $qb->createParameter('token')))
->setParameter('token', $token) ->setParameter('token', $token)
@ -95,7 +95,7 @@ class DefaultTokenMapper extends Mapper {
public function getTokenByUser(IUser $user) { public function getTokenByUser(IUser $user) {
/* @var $qb IQueryBuilder */ /* @var $qb IQueryBuilder */
$qb = $this->db->getQueryBuilder(); $qb = $this->db->getQueryBuilder();
$qb->select('id', 'uid', 'login_name', 'password', 'name', 'type', 'token', 'last_activity') $qb->select('id', 'uid', 'login_name', 'password', 'name', 'type', 'token', 'last_activity', 'last_check')
->from('authtoken') ->from('authtoken')
->where($qb->expr()->eq('uid', $qb->createNamedParameter($user->getUID()))) ->where($qb->expr()->eq('uid', $qb->createNamedParameter($user->getUID())))
->setMaxResults(1000); ->setMaxResults(1000);

View File

@ -91,6 +91,18 @@ class DefaultTokenProvider implements IProvider {
return $dbToken; return $dbToken;
} }
/**
* Save the updated token
*
* @param IToken $token
*/
public function updateToken(IToken $token) {
if (!($token instanceof DefaultToken)) {
throw new InvalidTokenException();
}
$this->mapper->update($token);
}
/** /**
* Update token activity timestamp * Update token activity timestamp
* *
@ -181,21 +193,6 @@ class DefaultTokenProvider implements IProvider {
$this->mapper->invalidateOld($olderThan); $this->mapper->invalidateOld($olderThan);
} }
/**
* @param string $token
* @throws InvalidTokenException
* @return DefaultToken user UID
*/
public function validateToken($token) {
try {
$dbToken = $this->mapper->getToken($this->hashToken($token));
$this->logger->debug('valid default token for ' . $dbToken->getUID());
return $dbToken;
} catch (DoesNotExistException $ex) {
throw new InvalidTokenException();
}
}
/** /**
* @param string $token * @param string $token
* @return string * @return string

View File

@ -49,13 +49,6 @@ interface IProvider {
*/ */
public function getToken($tokenId) ; public function getToken($tokenId) ;
/**
* @param string $token
* @throws InvalidTokenException
* @return IToken
*/
public function validateToken($token);
/** /**
* Invalidate (delete) the given session token * Invalidate (delete) the given session token
* *
@ -71,6 +64,13 @@ interface IProvider {
*/ */
public function invalidateTokenById(IUser $user, $id); public function invalidateTokenById(IUser $user, $id);
/**
* Save the updated token
*
* @param IToken $token
*/
public function updateToken(IToken $token);
/** /**
* Update token activity timestamp * Update token activity timestamp
* *

View File

@ -55,4 +55,18 @@ interface IToken extends JsonSerializable {
* @return string * @return string
*/ */
public function getPassword(); public function getPassword();
/**
* Get the timestamp of the last password check
*
* @return int
*/
public function getLastCheck();
/**
* Get the timestamp of the last password check
*
* @param int $time
*/
public function setLastCheck($time);
} }

View File

@ -192,52 +192,22 @@ class Session implements IUserSession, Emitter {
if (is_null($this->activeUser)) { if (is_null($this->activeUser)) {
return null; return null;
} }
$this->validateSession($this->activeUser); $this->validateSession();
} }
return $this->activeUser; return $this->activeUser;
} }
protected function validateSession(IUser $user) { protected function validateSession() {
try { try {
$sessionId = $this->session->getId(); $sessionId = $this->session->getId();
} catch (SessionNotAvailableException $ex) { } catch (SessionNotAvailableException $ex) {
return; return;
} }
try {
$token = $this->tokenProvider->getToken($sessionId); if (!$this->validateToken($sessionId)) {
} catch (InvalidTokenException $ex) {
// Session was invalidated // Session was invalidated
$this->logout(); $this->logout();
return;
} }
// Check whether login credentials are still valid and the user was not disabled
// This check is performed each 5 minutes
$lastCheck = $this->session->get('last_login_check') ? : 0;
$now = $this->timeFacory->getTime();
if ($lastCheck < ($now - 60 * 5)) {
try {
$pwd = $this->tokenProvider->getPassword($token, $sessionId);
} catch (InvalidTokenException $ex) {
// 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
|| !$user->isEnabled()) {
// Password has changed or user was disabled -> log user out
$this->logout();
return;
}
$this->session->set('last_login_check', $now);
}
$this->tokenProvider->updateTokenActivity($token);
} }
/** /**
@ -297,20 +267,22 @@ class Session implements IUserSession, Emitter {
public function login($uid, $password) { public function login($uid, $password) {
$this->session->regenerateId(); $this->session->regenerateId();
if ($this->validateToken($password)) { if ($this->validateToken($password)) {
$user = $this->getUser();
// 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);
try { try {
$password = $this->tokenProvider->getPassword($token, $password); $loginPassword = $this->tokenProvider->getPassword($token, $password);
$this->manager->emit('\OC\User', 'preLogin', array($uid, $password)); $this->manager->emit('\OC\User', 'preLogin', array($uid, $loginPassword));
} catch (PasswordlessTokenException $ex) { } catch (PasswordlessTokenException $ex) {
$this->manager->emit('\OC\User', 'preLogin', array($uid, '')); $this->manager->emit('\OC\User', 'preLogin', array($uid, ''));
} }
} catch (InvalidTokenException $ex) { } catch (InvalidTokenException $ex) {
// Invalid token, nothing to do // Invalid token, nothing to do
} }
$this->loginWithToken($password);
$user = $this->getUser();
$this->tokenProvider->updateTokenActivity($token);
} else { } else {
$this->manager->emit('\OC\User', 'preLogin', array($uid, $password)); $this->manager->emit('\OC\User', 'preLogin', array($uid, $password));
$user = $this->manager->checkPassword($uid, $password); $user = $this->manager->checkPassword($uid, $password);
@ -459,8 +431,21 @@ class Session implements IUserSession, Emitter {
return false; return false;
} }
private function loginWithToken($uid) { private function loginWithToken($token) {
// TODO: $this->manager->emit('\OC\User', 'preTokenLogin', array($uid)); try {
$dbToken = $this->tokenProvider->getToken($token);
} catch (InvalidTokenException $ex) {
return false;
}
$uid = $dbToken->getUID();
try {
$password = $this->tokenProvider->getPassword($dbToken, $token);
$this->manager->emit('\OC\User', 'preLogin', array($uid, $password));
} catch (PasswordlessTokenException $ex) {
$this->manager->emit('\OC\User', 'preLogin', array($uid, ''));
}
$user = $this->manager->get($uid); $user = $this->manager->get($uid);
if (is_null($user)) { if (is_null($user)) {
// user does not exist // user does not exist
@ -473,7 +458,9 @@ class Session implements IUserSession, Emitter {
//login //login
$this->setUser($user); $this->setUser($user);
// TODO: $this->manager->emit('\OC\User', 'postTokenLogin', array($user)); $this->tokenProvider->updateTokenActivity($dbToken);
$this->manager->emit('\OC\User', 'postLogin', array($user, $password));
return true; return true;
} }
@ -530,26 +517,74 @@ class Session implements IUserSession, Emitter {
} }
/** /**
* @param IToken $dbToken
* @param string $token
* @return boolean
*/
private function checkTokenCredentials(IToken $dbToken, $token) {
// Check whether login credentials are still valid and the user was not disabled
// This check is performed each 5 minutes
$lastCheck = $dbToken->getLastCheck() ? : 0;
$now = $this->timeFacory->getTime();
if ($lastCheck > ($now - 60 * 5)) {
// Checked performed recently, nothing to do now
return true;
}
try {
$pwd = $this->tokenProvider->getPassword($dbToken, $token);
} catch (InvalidTokenException $ex) {
// An invalid token password was used -> log user out
$this->logout();
return false;
} catch (PasswordlessTokenException $ex) {
// Token has no password
if (!is_null($this->activeUser) && !$this->activeUser->isEnabled()) {
$this->tokenProvider->invalidateToken($token);
$this->logout();
return false;
}
$dbToken->setLastCheck($now);
$this->tokenProvider->updateToken($dbToken);
return true;
}
if ($this->manager->checkPassword($dbToken->getLoginName(), $pwd) === false
|| (!is_null($this->activeUser) && !$this->activeUser->isEnabled())) {
$this->tokenProvider->invalidateToken($token);
// Password has changed or user was disabled -> log user out
$this->logout();
return false;
}
$dbToken->setLastCheck($now);
$this->tokenProvider->updateToken($dbToken);
return true;
}
/**
* Check if the given token exists and performs password/user-enabled checks
*
* Invalidates the token if checks fail
*
* @param string $token * @param string $token
* @return boolean * @return boolean
*/ */
private function validateToken($token) { private function validateToken($token) {
try { try {
$token = $this->tokenProvider->validateToken($token); $dbToken = $this->tokenProvider->getToken($token);
if (!is_null($token)) {
$result = $this->loginWithToken($token->getUID());
if ($result) {
// Login success
$this->tokenProvider->updateTokenActivity($token);
return true;
}
}
} catch (InvalidTokenException $ex) { } catch (InvalidTokenException $ex) {
}
return false; return false;
} }
if (!$this->checkTokenCredentials($dbToken, $token)) {
return false;
}
return true;
}
/** /**
* Tries to login the user with auth token header * Tries to login the user with auth token header
* *
@ -562,10 +597,15 @@ class Session implements IUserSession, Emitter {
// No auth header, let's try session id // No auth header, let's try session id
try { try {
$sessionId = $this->session->getId(); $sessionId = $this->session->getId();
return $this->validateToken($sessionId);
} catch (SessionNotAvailableException $ex) { } catch (SessionNotAvailableException $ex) {
return false; return false;
} }
if (!$this->validateToken($sessionId)) {
return false;
}
return $this->loginWithToken($sessionId);
} else { } else {
$token = substr($authHeader, 6); $token = substr($authHeader, 6);
return $this->validateToken($token); return $this->validateToken($token);

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, 8); $OC_Version = array(9, 1, 0, 9);
// The human readable string // The human readable string
$OC_VersionString = '9.1.0 beta 2'; $OC_VersionString = '9.1.0 beta 2';