Merge pull request #8681 from owncloud/logintimestamp
Record login timestamp per user. Required for new user managament.
This commit is contained in:
commit
ce9d5df6df
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
/**
|
||||
* Copyright (c) 2014 Arthur Schiwon <blizzz@owncloud.com>
|
||||
* This file is licensed under the Affero General Public License version 3 or
|
||||
* later.
|
||||
* See the COPYING-README file.
|
||||
*/
|
||||
|
||||
namespace OC\Core\Command\User;
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
|
||||
class LastSeen extends Command {
|
||||
protected function configure() {
|
||||
$this
|
||||
->setName('user:lastseen')
|
||||
->setDescription('shows when the user was logged it last time')
|
||||
->addArgument(
|
||||
'uid',
|
||||
InputArgument::REQUIRED,
|
||||
'the username'
|
||||
);
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||
$userManager = \OC::$server->getUserManager();
|
||||
$user = $userManager->get($input->getArgument('uid'));
|
||||
if(is_null($user)) {
|
||||
$output->writeln('User does not exist');
|
||||
return;
|
||||
}
|
||||
|
||||
$lastLogin = $user->getLastLogin();
|
||||
if($lastLogin === 0) {
|
||||
$output->writeln('User ' . $user->getUID() .
|
||||
' has never logged in, yet.');
|
||||
} else {
|
||||
$date = new \DateTime();
|
||||
$date->setTimestamp($lastLogin);
|
||||
$output->writeln($user->getUID() .
|
||||
'`s last login: ' . $date->format('d.m.Y H:i'));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,3 +17,4 @@ $application->add(new OC\Core\Command\App\Enable());
|
|||
$application->add(new OC\Core\Command\App\ListApps());
|
||||
$application->add(new OC\Core\Command\Maintenance\Repair(new \OC\Repair()));
|
||||
$application->add(new OC\Core\Command\User\Report());
|
||||
$application->add(new OC\Core\Command\User\LastSeen());
|
||||
|
|
24
lib/base.php
24
lib/base.php
|
@ -884,30 +884,24 @@ class OC {
|
|||
if (defined("DEBUG") && DEBUG) {
|
||||
OC_Log::write('core', 'Trying to login from cookie', OC_Log::DEBUG);
|
||||
}
|
||||
// confirm credentials in cookie
|
||||
if (isset($_COOKIE['oc_token']) && OC_User::userExists($_COOKIE['oc_username'])) {
|
||||
// delete outdated cookies
|
||||
|
||||
if(OC_User::userExists($_COOKIE['oc_username'])) {
|
||||
self::cleanupLoginTokens($_COOKIE['oc_username']);
|
||||
// get stored tokens
|
||||
$tokens = OC_Preferences::getKeys($_COOKIE['oc_username'], 'login_token');
|
||||
// test cookies token against stored tokens
|
||||
if (in_array($_COOKIE['oc_token'], $tokens, true)) {
|
||||
// replace successfully used token with a new one
|
||||
OC_Preferences::deleteKey($_COOKIE['oc_username'], 'login_token', $_COOKIE['oc_token']);
|
||||
$token = OC_Util::generateRandomBytes(32);
|
||||
OC_Preferences::setValue($_COOKIE['oc_username'], 'login_token', $token, time());
|
||||
OC_User::setMagicInCookie($_COOKIE['oc_username'], $token);
|
||||
// login
|
||||
OC_User::setUserId($_COOKIE['oc_username']);
|
||||
// verify whether the supplied "remember me" token was valid
|
||||
$granted = OC_User::loginWithCookie(
|
||||
$_COOKIE['oc_username'], $_COOKIE['oc_token']);
|
||||
if($granted === true) {
|
||||
OC_Util::redirectToDefaultPage();
|
||||
// doesn't return
|
||||
}
|
||||
OC_Log::write('core', 'Authentication cookie rejected for user ' .
|
||||
$_COOKIE['oc_username'], OC_Log::WARN);
|
||||
// if you reach this point you have changed your password
|
||||
// or you are an attacker
|
||||
// we can not delete tokens here because users may reach
|
||||
// this point multiple times after a password change
|
||||
OC_Log::write('core', 'Authentication cookie rejected for user ' . $_COOKIE['oc_username'], OC_Log::WARN);
|
||||
}
|
||||
|
||||
OC_User::unsetMagicInCookie();
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -235,6 +235,17 @@ class OC_User {
|
|||
return self::getUserSession()->login($uid, $password);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to login a user using the magic cookie (remember login)
|
||||
*
|
||||
* @param string $uid The username of the user to log in
|
||||
* @param string $token
|
||||
* @return bool
|
||||
*/
|
||||
public static function loginWithCookie($uid, $token) {
|
||||
return self::getUserSession()->loginWithCookie($uid, $token);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to login a user, assuming authentication
|
||||
* has already happened (e.g. via Single Sign On).
|
||||
|
|
|
@ -52,6 +52,12 @@ class Manager extends PublicEmitter {
|
|||
unset($cachedUsers[$i]);
|
||||
}
|
||||
});
|
||||
$this->listen('\OC\User', 'postLogin', function ($user) {
|
||||
$user->updateLastLoginTimestamp();
|
||||
});
|
||||
$this->listen('\OC\User', 'postRememberedLogin', function ($user) {
|
||||
$user->updateLastLoginTimestamp();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -22,7 +22,9 @@ use OC\Hooks\Emitter;
|
|||
* - preCreateUser(string $uid, string $password)
|
||||
* - postCreateUser(\OC\User\User $user)
|
||||
* - preLogin(string $user, string $password)
|
||||
* - postLogin(\OC\User\User $user)
|
||||
* - postLogin(\OC\User\User $user, string $password)
|
||||
* - preRememberedLogin(string $uid)
|
||||
* - postRememberedLogin(\OC\User\User $user)
|
||||
* - logout()
|
||||
*
|
||||
* @package OC\User
|
||||
|
@ -170,6 +172,39 @@ class Session implements Emitter, \OCP\IUserSession {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* perform login using the magic cookie (remember login)
|
||||
*
|
||||
* @param string $uid the username
|
||||
* @param string $currentToken
|
||||
* @return bool
|
||||
*/
|
||||
public function loginWithCookie($uid, $currentToken) {
|
||||
$this->manager->emit('\OC\User', 'preRememberedLogin', array($uid));
|
||||
$user = $this->manager->get($uid);
|
||||
if(is_null($user)) {
|
||||
// user does not exist
|
||||
return false;
|
||||
}
|
||||
|
||||
// get stored tokens
|
||||
$tokens = \OC_Preferences::getKeys($uid, 'login_token');
|
||||
// test cookies token against stored tokens
|
||||
if(!in_array($currentToken, $tokens, true)) {
|
||||
return false;
|
||||
}
|
||||
// replace successfully used token with a new one
|
||||
\OC_Preferences::deleteKey($uid, 'login_token', $currentToken);
|
||||
$newToken = \OC_Util::generateRandomBytes(32);
|
||||
\OC_Preferences::setValue($uid, 'login_token', $newToken, time());
|
||||
$this->setMagicInCookie($user->getUID(), $newToken);
|
||||
|
||||
//login
|
||||
$this->setUser($user);
|
||||
$this->manager->emit('\OC\User', 'postRememberedLogin', array($user));
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* logout the user from the session
|
||||
*/
|
||||
|
|
|
@ -42,6 +42,11 @@ class User {
|
|||
*/
|
||||
private $home;
|
||||
|
||||
/**
|
||||
* @var int $lastLogin
|
||||
*/
|
||||
private $lastLogin;
|
||||
|
||||
/**
|
||||
* @var \OC\AllConfig $config
|
||||
*/
|
||||
|
@ -64,6 +69,7 @@ class User {
|
|||
} else {
|
||||
$this->enabled = true;
|
||||
}
|
||||
$this->lastLogin = \OC_Preferences::getValue($uid, 'login', 'lastLogin', 0);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -107,6 +113,25 @@ class User {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the timestamp of the user's last login or 0 if the user did never
|
||||
* login
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getLastLogin() {
|
||||
return $this->lastLogin;
|
||||
}
|
||||
|
||||
/**
|
||||
* updates the timestamp of the most recent login of this user
|
||||
*/
|
||||
public function updateLastLoginTimestamp() {
|
||||
$this->lastLogin = time();
|
||||
\OC_Preferences::setValue(
|
||||
$this->uid, 'login', 'lastLogin', $this->lastLogin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the user
|
||||
*
|
||||
|
|
|
@ -67,7 +67,17 @@ class Session extends \PHPUnit_Framework_TestCase {
|
|||
},
|
||||
'foo'));
|
||||
|
||||
$manager = $this->getMock('\OC\User\Manager');
|
||||
$managerMethods = get_class_methods('\OC\User\Manager');
|
||||
//keep following methods intact in order to ensure hooks are
|
||||
//working
|
||||
$doNotMock = array('__construct', 'emit', 'listen');
|
||||
foreach($doNotMock as $methodName) {
|
||||
$i = array_search($methodName, $managerMethods, true);
|
||||
if($i !== false) {
|
||||
unset($managerMethods[$i]);
|
||||
}
|
||||
}
|
||||
$manager = $this->getMock('\OC\User\Manager', $managerMethods, array());
|
||||
|
||||
$backend = $this->getMock('OC_User_Dummy');
|
||||
|
||||
|
@ -78,6 +88,8 @@ class Session extends \PHPUnit_Framework_TestCase {
|
|||
$user->expects($this->any())
|
||||
->method('getUID')
|
||||
->will($this->returnValue('foo'));
|
||||
$user->expects($this->once())
|
||||
->method('updateLastLoginTimestamp');
|
||||
|
||||
$manager->expects($this->once())
|
||||
->method('checkPassword')
|
||||
|
@ -94,7 +106,17 @@ class Session extends \PHPUnit_Framework_TestCase {
|
|||
$session->expects($this->never())
|
||||
->method('set');
|
||||
|
||||
$manager = $this->getMock('\OC\User\Manager');
|
||||
$managerMethods = get_class_methods('\OC\User\Manager');
|
||||
//keep following methods intact in order to ensure hooks are
|
||||
//working
|
||||
$doNotMock = array('__construct', 'emit', 'listen');
|
||||
foreach($doNotMock as $methodName) {
|
||||
$i = array_search($methodName, $managerMethods, true);
|
||||
if($i !== false) {
|
||||
unset($managerMethods[$i]);
|
||||
}
|
||||
}
|
||||
$manager = $this->getMock('\OC\User\Manager', $managerMethods, array());
|
||||
|
||||
$backend = $this->getMock('OC_User_Dummy');
|
||||
|
||||
|
@ -102,6 +124,8 @@ class Session extends \PHPUnit_Framework_TestCase {
|
|||
$user->expects($this->once())
|
||||
->method('isEnabled')
|
||||
->will($this->returnValue(false));
|
||||
$user->expects($this->never())
|
||||
->method('updateLastLoginTimestamp');
|
||||
|
||||
$manager->expects($this->once())
|
||||
->method('checkPassword')
|
||||
|
@ -117,13 +141,25 @@ class Session extends \PHPUnit_Framework_TestCase {
|
|||
$session->expects($this->never())
|
||||
->method('set');
|
||||
|
||||
$manager = $this->getMock('\OC\User\Manager');
|
||||
$managerMethods = get_class_methods('\OC\User\Manager');
|
||||
//keep following methods intact in order to ensure hooks are
|
||||
//working
|
||||
$doNotMock = array('__construct', 'emit', 'listen');
|
||||
foreach($doNotMock as $methodName) {
|
||||
$i = array_search($methodName, $managerMethods, true);
|
||||
if($i !== false) {
|
||||
unset($managerMethods[$i]);
|
||||
}
|
||||
}
|
||||
$manager = $this->getMock('\OC\User\Manager', $managerMethods, array());
|
||||
|
||||
$backend = $this->getMock('OC_User_Dummy');
|
||||
|
||||
$user = $this->getMock('\OC\User\User', array(), array('foo', $backend));
|
||||
$user->expects($this->never())
|
||||
->method('isEnabled');
|
||||
$user->expects($this->never())
|
||||
->method('updateLastLoginTimestamp');
|
||||
|
||||
$manager->expects($this->once())
|
||||
->method('checkPassword')
|
||||
|
@ -151,4 +187,144 @@ class Session extends \PHPUnit_Framework_TestCase {
|
|||
$userSession = new \OC\User\Session($manager, $session);
|
||||
$userSession->login('foo', 'bar');
|
||||
}
|
||||
|
||||
public function testRememberLoginValidToken() {
|
||||
$session = $this->getMock('\OC\Session\Memory', array(), array(''));
|
||||
$session->expects($this->exactly(1))
|
||||
->method('set')
|
||||
->with($this->callback(function($key) {
|
||||
switch($key) {
|
||||
case 'user_id':
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
},
|
||||
'foo'));
|
||||
|
||||
$managerMethods = get_class_methods('\OC\User\Manager');
|
||||
//keep following methods intact in order to ensure hooks are
|
||||
//working
|
||||
$doNotMock = array('__construct', 'emit', 'listen');
|
||||
foreach($doNotMock as $methodName) {
|
||||
$i = array_search($methodName, $managerMethods, true);
|
||||
if($i !== false) {
|
||||
unset($managerMethods[$i]);
|
||||
}
|
||||
}
|
||||
$manager = $this->getMock('\OC\User\Manager', $managerMethods, array());
|
||||
|
||||
$backend = $this->getMock('OC_User_Dummy');
|
||||
|
||||
$user = $this->getMock('\OC\User\User', array(), array('foo', $backend));
|
||||
|
||||
$user->expects($this->any())
|
||||
->method('getUID')
|
||||
->will($this->returnValue('foo'));
|
||||
$user->expects($this->once())
|
||||
->method('updateLastLoginTimestamp');
|
||||
|
||||
$manager->expects($this->once())
|
||||
->method('get')
|
||||
->with('foo')
|
||||
->will($this->returnValue($user));
|
||||
|
||||
//prepare login token
|
||||
$token = 'goodToken';
|
||||
\OC_Preferences::setValue('foo', 'login_token', $token, time());
|
||||
|
||||
$userSession = $this->getMock(
|
||||
'\OC\User\Session',
|
||||
//override, otherwise tests will fail because of setcookie()
|
||||
array('setMagicInCookie'),
|
||||
//there are passed as parameters to the constructor
|
||||
array($manager, $session));
|
||||
|
||||
$granted = $userSession->loginWithCookie('foo', $token);
|
||||
|
||||
$this->assertSame($granted, true);
|
||||
}
|
||||
|
||||
public function testRememberLoginInvalidToken() {
|
||||
$session = $this->getMock('\OC\Session\Memory', array(), array(''));
|
||||
$session->expects($this->never())
|
||||
->method('set');
|
||||
|
||||
$managerMethods = get_class_methods('\OC\User\Manager');
|
||||
//keep following methods intact in order to ensure hooks are
|
||||
//working
|
||||
$doNotMock = array('__construct', 'emit', 'listen');
|
||||
foreach($doNotMock as $methodName) {
|
||||
$i = array_search($methodName, $managerMethods, true);
|
||||
if($i !== false) {
|
||||
unset($managerMethods[$i]);
|
||||
}
|
||||
}
|
||||
$manager = $this->getMock('\OC\User\Manager', $managerMethods, array());
|
||||
|
||||
$backend = $this->getMock('OC_User_Dummy');
|
||||
|
||||
$user = $this->getMock('\OC\User\User', array(), array('foo', $backend));
|
||||
|
||||
$user->expects($this->any())
|
||||
->method('getUID')
|
||||
->will($this->returnValue('foo'));
|
||||
$user->expects($this->never())
|
||||
->method('updateLastLoginTimestamp');
|
||||
|
||||
$manager->expects($this->once())
|
||||
->method('get')
|
||||
->with('foo')
|
||||
->will($this->returnValue($user));
|
||||
|
||||
//prepare login token
|
||||
$token = 'goodToken';
|
||||
\OC_Preferences::setValue('foo', 'login_token', $token, time());
|
||||
|
||||
$userSession = new \OC\User\Session($manager, $session);
|
||||
$granted = $userSession->loginWithCookie('foo', 'badToken');
|
||||
|
||||
$this->assertSame($granted, false);
|
||||
}
|
||||
|
||||
public function testRememberLoginInvalidUser() {
|
||||
$session = $this->getMock('\OC\Session\Memory', array(), array(''));
|
||||
$session->expects($this->never())
|
||||
->method('set');
|
||||
|
||||
$managerMethods = get_class_methods('\OC\User\Manager');
|
||||
//keep following methods intact in order to ensure hooks are
|
||||
//working
|
||||
$doNotMock = array('__construct', 'emit', 'listen');
|
||||
foreach($doNotMock as $methodName) {
|
||||
$i = array_search($methodName, $managerMethods, true);
|
||||
if($i !== false) {
|
||||
unset($managerMethods[$i]);
|
||||
}
|
||||
}
|
||||
$manager = $this->getMock('\OC\User\Manager', $managerMethods, array());
|
||||
|
||||
$backend = $this->getMock('OC_User_Dummy');
|
||||
|
||||
$user = $this->getMock('\OC\User\User', array(), array('foo', $backend));
|
||||
|
||||
$user->expects($this->never())
|
||||
->method('getUID');
|
||||
$user->expects($this->never())
|
||||
->method('updateLastLoginTimestamp');
|
||||
|
||||
$manager->expects($this->once())
|
||||
->method('get')
|
||||
->with('foo')
|
||||
->will($this->returnValue(null));
|
||||
|
||||
//prepare login token
|
||||
$token = 'goodToken';
|
||||
\OC_Preferences::setValue('foo', 'login_token', $token, time());
|
||||
|
||||
$userSession = new \OC\User\Session($manager, $session);
|
||||
$granted = $userSession->loginWithCookie('foo', $token);
|
||||
|
||||
$this->assertSame($granted, false);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue