Skip to content

Commit

Permalink
store last check timestamp in token instead of session
Browse files Browse the repository at this point in the history
  • Loading branch information
ChristophWurst committed Jun 17, 2016
1 parent c4149c5 commit 0c0a216
Show file tree
Hide file tree
Showing 8 changed files with 160 additions and 77 deletions.
9 changes: 9 additions & 0 deletions db_structure.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
23 changes: 23 additions & 0 deletions lib/private/Authentication/Token/DefaultToken.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ class DefaultToken extends Entity implements IToken {
*/
protected $lastActivity;

/**
* @var int
*/
protected $lastCheck;

public function getId() {
return $this->id;
}
Expand Down Expand Up @@ -109,4 +114,22 @@ public function jsonSerialize() {
];
}

/**
* 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);
}

}
4 changes: 2 additions & 2 deletions lib/private/Authentication/Token/DefaultTokenMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public function invalidateOld($olderThan) {
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)
Expand All @@ -95,7 +95,7 @@ public function getToken($token) {
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);
Expand Down
27 changes: 12 additions & 15 deletions lib/private/Authentication/Token/DefaultTokenProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,18 @@ public function generateToken($token, $uid, $loginName, $password, $name, $type
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
*
Expand Down Expand Up @@ -181,21 +193,6 @@ public function invalidateOldTokens() {
$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
Expand Down
14 changes: 7 additions & 7 deletions lib/private/Authentication/Token/IProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,6 @@ public function generateToken($token, $uid, $loginName, $password, $name, $type
*/
public function getToken($tokenId) ;

/**
* @param string $token
* @throws InvalidTokenException
* @return IToken
*/
public function validateToken($token);

/**
* Invalidate (delete) the given session token
*
Expand All @@ -71,6 +64,13 @@ public function invalidateToken($token);
*/
public function invalidateTokenById(IUser $user, $id);

/**
* Save the updated token
*
* @param IToken $token
*/
public function updateToken(IToken $token);

/**
* Update token activity timestamp
*
Expand Down
14 changes: 14 additions & 0 deletions lib/private/Authentication/Token/IToken.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,18 @@ public function getLoginName();
* @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);
}
144 changes: 92 additions & 52 deletions lib/private/User/Session.php
Original file line number Diff line number Diff line change
Expand Up @@ -192,52 +192,22 @@ public function getUser() {
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);
}

/**
Expand Down Expand Up @@ -297,20 +267,22 @@ public function getLoginName() {
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);
Expand Down Expand Up @@ -459,8 +431,21 @@ public function tryBasicAuthLogin(IRequest $request) {
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
Expand All @@ -473,7 +458,9 @@ private function loginWithToken($uid) {

//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;
}

Expand Down Expand Up @@ -530,24 +517,72 @@ private function getPassword($password) {
}

/**
* @param IToken $dbToken
* @param string $token
* @return boolean
*/
private function validateToken($token) {
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 {
$token = $this->tokenProvider->validateToken($token);
if (!is_null($token)) {
$result = $this->loginWithToken($token->getUID());
if ($result) {
// Login success
$this->tokenProvider->updateTokenActivity($token);
return true;
}
$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 {
$dbToken = $this->tokenProvider->getToken($token);
} catch (InvalidTokenException $ex) {
return false;
}

if (!$this->checkTokenCredentials($dbToken, $token)) {
return false;
}
return false;

return true;
}

/**
Expand All @@ -562,10 +597,15 @@ public function tryTokenLogin(IRequest $request) {
// 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);
Expand Down
2 changes: 1 addition & 1 deletion version.php
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down

0 comments on commit 0c0a216

Please sign in to comment.