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>
</field>
<field>
<name>last_check</name>
<type>integer</type>
<default>0</default>
<notnull>true</notnull>
<unsigned>true</unsigned>
<length>4</length>
</field>
<index>
<name>authtoken_token_index</name>
<unique>true</unique>

View file

@ -74,6 +74,11 @@ class DefaultToken extends Entity implements IToken {
*/
protected $lastActivity;
/**
* @var int
*/
protected $lastCheck;
public function getId() {
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) {
/* @var $qb IQueryBuilder */
$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')
->where($qb->expr()->eq('token', $qb->createParameter('token')))
->setParameter('token', $token)
@ -95,7 +95,7 @@ class DefaultTokenMapper extends Mapper {
public function getTokenByUser(IUser $user) {
/* @var $qb IQueryBuilder */
$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')
->where($qb->expr()->eq('uid', $qb->createNamedParameter($user->getUID())))
->setMaxResults(1000);

View file

@ -91,6 +91,18 @@ class DefaultTokenProvider implements IProvider {
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
*
@ -181,21 +193,6 @@ class DefaultTokenProvider implements IProvider {
$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
* @return string

View file

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

View file

@ -55,4 +55,18 @@ interface IToken extends JsonSerializable {
* @return string
*/
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)) {
return null;
}
$this->validateSession($this->activeUser);
$this->validateSession();
}
return $this->activeUser;
}
protected function validateSession(IUser $user) {
protected function validateSession() {
try {
$sessionId = $this->session->getId();
} catch (SessionNotAvailableException $ex) {
return;
}
try {
$token = $this->tokenProvider->getToken($sessionId);
} catch (InvalidTokenException $ex) {
if (!$this->validateToken($sessionId)) {
// Session was invalidated
$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) {
$this->session->regenerateId();
if ($this->validateToken($password)) {
$user = $this->getUser();
// When logging in with token, the password must be decrypted first before passing to login hook
try {
$token = $this->tokenProvider->getToken($password);
try {
$password = $this->tokenProvider->getPassword($token, $password);
$this->manager->emit('\OC\User', 'preLogin', array($uid, $password));
$loginPassword = $this->tokenProvider->getPassword($token, $password);
$this->manager->emit('\OC\User', 'preLogin', array($uid, $loginPassword));
} catch (PasswordlessTokenException $ex) {
$this->manager->emit('\OC\User', 'preLogin', array($uid, ''));
}
} catch (InvalidTokenException $ex) {
// Invalid token, nothing to do
}
$this->loginWithToken($password);
$user = $this->getUser();
$this->tokenProvider->updateTokenActivity($token);
} else {
$this->manager->emit('\OC\User', 'preLogin', array($uid, $password));
$user = $this->manager->checkPassword($uid, $password);
@ -459,8 +431,21 @@ class Session implements IUserSession, Emitter {
return false;
}
private function loginWithToken($uid) {
// TODO: $this->manager->emit('\OC\User', 'preTokenLogin', array($uid));
private function loginWithToken($token) {
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);
if (is_null($user)) {
// user does not exist
@ -473,7 +458,9 @@ class Session implements IUserSession, Emitter {
//login
$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;
}
@ -530,24 +517,72 @@ 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
* @return boolean
*/
private function validateToken($token) {
try {
$token = $this->tokenProvider->validateToken($token);
if (!is_null($token)) {
$result = $this->loginWithToken($token->getUID());
if ($result) {
// Login success
$this->tokenProvider->updateTokenActivity($token);
return true;
}
}
$dbToken = $this->tokenProvider->getToken($token);
} catch (InvalidTokenException $ex) {
return false;
}
return false;
if (!$this->checkTokenCredentials($dbToken, $token)) {
return false;
}
return true;
}
/**
@ -562,10 +597,15 @@ class Session implements IUserSession, Emitter {
// No auth header, let's try session id
try {
$sessionId = $this->session->getId();
return $this->validateToken($sessionId);
} catch (SessionNotAvailableException $ex) {
return false;
}
if (!$this->validateToken($sessionId)) {
return false;
}
return $this->loginWithToken($sessionId);
} else {
$token = substr($authHeader, 6);
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
// 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, 8);
$OC_Version = array(9, 1, 0, 9);
// The human readable string
$OC_VersionString = '9.1.0 beta 2';