Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Record login timestamp per user. Required for new user managament. #8681

Merged
merged 13 commits into from
May 28, 2014
Merged
47 changes: 47 additions & 0 deletions core/command/user/lastseen.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php
/**
* Copyright (c) 2014 Arthur Schiwon <[email protected]>
* 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.');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That statement is not entirely correct as this could also be triggered if an user has only not logged in since this feature has been added.

It may give the administrator a plausible misbelieve that the user has never logged in while this is not the case. Can we adjust this somehow?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"has never logged in, yet." → "has never logged in (since availability of ownCloud 7.0.0)."

OK?

} else {
$date = new \DateTime();
$date->setTimestamp($lastLogin);
$output->writeln($user->getUID() .
'`s last login: ' . $date->format('d.m.Y H:i'));
}
}
}
1 change: 1 addition & 0 deletions core/register_command.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@
$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 changes: 9 additions & 15 deletions lib/base.php
Original file line number Diff line number Diff line change
Expand Up @@ -883,30 +883,24 @@ protected static function tryRememberLogin() {
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']);
// confirm credentials in cookie
$granted = OC_User::loginWithCookie(
$_COOKIE['oc_username'], $_COOKIE['oc_token']);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where did that code goto? OC_User::loginWithCookie?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to lib/private/user/session.php line 188 (invoked by OC_User::loginWithCookie)

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;
}
Expand Down
11 changes: 11 additions & 0 deletions lib/private/user.php
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,17 @@ public static function login($uid, $password) {
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).
Expand Down
6 changes: 6 additions & 0 deletions lib/private/user/manager.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ public function __construct($config = null) {
unset($cachedUsers[$i]);
}
});
$this->listen('\OC\User', 'postLogin', function ($user) {
$user->updateLastLoginTimestamp();
});
$this->listen('\OC\User', 'postRememberedLogin', function ($user) {
$user->updateLastLoginTimestamp();
});
}

/**
Expand Down
32 changes: 32 additions & 0 deletions lib/private/user/session.php
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,38 @@ public function login($uid, $password) {
}
}

/**
* perform login using the magic cookie (remember login)
*
* @param string $uid the username
* @param string $currentToken
* @return bool
*/
public function loginWithCookie($uid, $currentToken) {
$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));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you also add a preRememberedLogin.

I need this for the two-factor authentication app. Thanks!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you document this hook somewhere? I see that in user.php some hooks are documented at the beginning of the file.

return true;
}

/**
* logout the user from the session
*/
Expand Down
27 changes: 27 additions & 0 deletions lib/private/user/user.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ class User {
*/
private $home;

/**
* @var int $lastLogin
*/
private $lastLogin;

/**
* @var \OC\AllConfig $config
*/
Expand All @@ -64,6 +69,7 @@ public function __construct($uid, $backend, $emitter = null, $config = null) {
} else {
$this->enabled = true;
}
$this->lastLogin = \OC_Preferences::getValue($uid, 'login', 'lastLogin', 0);
}

/**
Expand Down Expand Up @@ -107,6 +113,27 @@ public function setDisplayName($displayName) {
}
}

/**
* 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
*
* @return null
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this returns void and not null. Therefore the @return statement can be dropped.

See http://www.phpdoc.org/docs/latest/for-users/phpdoc/tags/return.html:

It is RECOMMENDED when documenting to use this tag with every function and method. Exceptions to this recommendation are:

  • constructors, the @return tag MAY be omitted here, in which case @return self is implied.
  • functions and methods without a return value, the @return tag MAY be omitted here, in which case @return void is implied.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if no specific return is done (→ void) null will be returned, anyway, but I get the point and will drop it.

*/
public function updateLastLoginTimestamp() {
$this->lastLogin = time();
\OC_Preferences::setValue(
$this->uid, 'login', 'lastLogin', $this->lastLogin);
}

/**
* Delete the user
*
Expand Down
Loading