Skip to content
Permalink
Browse files Browse the repository at this point in the history
Improve handling of logout
- add separate script for handling logout
- no longer require old_usr for all authentication methods
  (this avoids potential information leak)
- require valid token for logout

Signed-off-by: Michal Čihař <michal@cihar.com>
  • Loading branch information
nijel committed May 23, 2016
1 parent 381c80d commit 11eb574
Show file tree
Hide file tree
Showing 10 changed files with 175 additions and 104 deletions.
1 change: 1 addition & 0 deletions ChangeLog
Expand Up @@ -12,6 +12,7 @@ phpMyAdmin - ChangeLog
- issue #11705 Fix occassional 200 errors on Windows
- issue #12219 Fix locking issues when importing SQL
- issue #12231 Avoid confusing warning when mysql extension is missing
- issue Improve handling of logout

4.6.1 (2016-05-02)
- issue #12120 PMA_Util not found in insert_edit.lib.php
Expand Down
3 changes: 1 addition & 2 deletions libraries/navigation/NavigationHeader.php
Expand Up @@ -167,8 +167,7 @@ private function _links()
if ($GLOBALS['server'] != 0) {
// Logout for advanced authentication
if ($GLOBALS['cfg']['Server']['auth_type'] != 'config') {
$link = 'index.php' . $GLOBALS['url_query'];
$link .= '&amp;old_usr=' . urlencode($GLOBALS['PHP_AUTH_USER']);
$link = 'logout.php' . $GLOBALS['url_query'];
$retval .= PMA\libraries\Util::getNavigationLink(
$link,
$showText,
Expand Down
40 changes: 40 additions & 0 deletions libraries/plugins/AuthenticationPlugin.php
Expand Up @@ -52,6 +52,46 @@ public function storeUserCredentials()
*/
abstract public function authFails();

/**
* Perform logout
*
* @return void
*/
public function logOut()
{
global $PHP_AUTH_USER, $PHP_AUTH_PW;

/* Obtain redirect URL (before doing logout) */
if (! empty($GLOBALS['cfg']['Server']['LogoutURL'])) {
$redirect_url = $GLOBALS['cfg']['Server']['LogoutURL'];
} else {
$redirect_url = $this->getLoginFormURL();
}

/* Clear credentials */
$PHP_AUTH_USER = '';
$PHP_AUTH_PW = '';

/* delete user's choices that were stored in session */
$_SESSION = array();
if (!defined('TESTSUITE')) {
session_destroy();
}

/* Redirect to login form (or configured URL) */
PMA_sendHeaderLocation($redirect_url);
}

/**
* Returns URL for login form.
*
* @return string
*/
public function getLoginFormURL()
{
return './index.php';
}

/**
* Returns error message for failed authentication.
*
Expand Down
66 changes: 26 additions & 40 deletions libraries/plugins/auth/AuthenticationCookie.php
Expand Up @@ -67,18 +67,6 @@ public function auth()
}
}

/* Perform logout to custom URL */
if (! empty($_REQUEST['old_usr'])
&& ! empty($GLOBALS['cfg']['Server']['LogoutURL'])
) {
PMA_sendHeaderLocation($GLOBALS['cfg']['Server']['LogoutURL']);
if (defined('TESTSUITE')) {
return true;
} else {
exit;
}
}

// No recall if blowfish secret is not configured as it would produce
// garbage
if ($GLOBALS['cfg']['LoginCookieRecall']
Expand Down Expand Up @@ -295,34 +283,6 @@ public function authCheck()
}
// END Swekey Integration

if (! empty($_REQUEST['old_usr'])) {
// The user wants to be logged out
// -> delete his choices that were stored in session

// according to the PHP manual we should do this before the destroy:
//$_SESSION = array();

if (! defined('TESTSUITE')) {
session_destroy();
}
// -> delete password cookie(s)
if ($GLOBALS['cfg']['LoginCookieDeleteAll']) {
foreach ($GLOBALS['cfg']['Servers'] as $key => $val) {
$GLOBALS['PMA_Config']->removeCookie('pmaPass-' . $key);
if (isset($_COOKIE['pmaPass-' . $key])) {
unset($_COOKIE['pmaPass-' . $key]);
}
}
} else {
$GLOBALS['PMA_Config']->removeCookie(
'pmaPass-' . $GLOBALS['server']
);
if (isset($_COOKIE['pmaPass-' . $GLOBALS['server']])) {
unset($_COOKIE['pmaPass-' . $GLOBALS['server']]);
}
}
}

if (! empty($_REQUEST['pma_username'])) {

// Verify Captcha if it is required.
Expand Down Expand Up @@ -831,4 +791,30 @@ public function handlePasswordChange($password)
{
$this->storePasswordCookie($password);
}

/**
* Perform logout
*
* @return void
*/
public function logOut()
{
// -> delete password cookie(s)
if ($GLOBALS['cfg']['LoginCookieDeleteAll']) {
foreach ($GLOBALS['cfg']['Servers'] as $key => $val) {
$GLOBALS['PMA_Config']->removeCookie('pmaPass-' . $key);
if (isset($_COOKIE['pmaPass-' . $key])) {
unset($_COOKIE['pmaPass-' . $key]);
}
}
} else {
$GLOBALS['PMA_Config']->removeCookie(
'pmaPass-' . $GLOBALS['server']
);
if (isset($_COOKIE['pmaPass-' . $GLOBALS['server']])) {
unset($_COOKIE['pmaPass-' . $GLOBALS['server']]);
}
}
parent::logOut();
}
}
22 changes: 10 additions & 12 deletions libraries/plugins/auth/AuthenticationHttp.php
Expand Up @@ -49,18 +49,6 @@ public function auth()
*/
public function authForm()
{
/* Perform logout to custom URL */
if (!empty($_REQUEST['old_usr'])
&& !empty($GLOBALS['cfg']['Server']['LogoutURL'])
) {
PMA_sendHeaderLocation($GLOBALS['cfg']['Server']['LogoutURL']);
if (!defined('TESTSUITE')) {
exit;
} else {
return false;
}
}

if (empty($GLOBALS['cfg']['Server']['auth_http_realm'])) {
if (empty($GLOBALS['cfg']['Server']['verbose'])) {
$server_message = $GLOBALS['cfg']['Server']['host'];
Expand Down Expand Up @@ -262,4 +250,14 @@ public function authFails()

return true;
}

/**
* Returns URL for login form.
*
* @return string
*/
public function getLoginFormURL()
{
return './index.php?old_usr=' . $GLOBALS['PHP_AUTH_USER'];
}
}
20 changes: 2 additions & 18 deletions libraries/plugins/auth/AuthenticationSignon.php
Expand Up @@ -28,11 +28,6 @@ public function auth()
unset($_SESSION['LAST_SIGNON_URL']);
if (empty($GLOBALS['cfg']['Server']['SignonURL'])) {
PMA_fatalError('You must set SignonURL!');
} elseif (!empty($_REQUEST['old_usr'])
&& !empty($GLOBALS['cfg']['Server']['LogoutURL'])
) {
/* Perform logout to custom URL */
PMA_sendHeaderLocation($GLOBALS['cfg']['Server']['LogoutURL']);
} else {
PMA_sendHeaderLocation($GLOBALS['cfg']['Server']['SignonURL']);
}
Expand Down Expand Up @@ -82,9 +77,6 @@ public function authCheck()
/* No configuration updates */
$single_signon_cfgupdate = array();

/* Are we requested to do logout? */
$do_logout = !empty($_REQUEST['old_usr']);

/* Handle script based auth */
if (!empty($script_name)) {
if (!file_exists($script_name)) {
Expand Down Expand Up @@ -117,18 +109,10 @@ public function authCheck()

/* Grab credentials if they exist */
if (isset($_SESSION['PMA_single_signon_user'])) {
if ($do_logout) {
$PHP_AUTH_USER = '';
} else {
$PHP_AUTH_USER = $_SESSION['PMA_single_signon_user'];
}
$PHP_AUTH_USER = $_SESSION['PMA_single_signon_user'];
}
if (isset($_SESSION['PMA_single_signon_password'])) {
if ($do_logout) {
$PHP_AUTH_PW = '';
} else {
$PHP_AUTH_PW = $_SESSION['PMA_single_signon_password'];
}
$PHP_AUTH_PW = $_SESSION['PMA_single_signon_password'];
}
if (isset($_SESSION['PMA_single_signon_host'])) {
$single_signon_host = $_SESSION['PMA_single_signon_host'];
Expand Down
15 changes: 15 additions & 0 deletions logout.php
@@ -0,0 +1,15 @@
<?php
/* vim: set expandtab sw=4 ts=4 sts=4: */
/**
* Logout script
*
* @package PhpMyAdmin
*/
require_once 'libraries/common.inc.php';

if ($token_mismatch) {
PMA_sendHeaderLocation('./index.php');
} else {
$auth_plugin->logOut();
}

60 changes: 47 additions & 13 deletions test/classes/plugin/auth/AuthenticationCookieTest.php
Expand Up @@ -373,18 +373,16 @@ public function testAuthCaptcha()
*/
public function testAuthHeader()
{
$GLOBALS['cfg']['LoginCookieDeleteAll'] = false;
$GLOBALS['cfg']['Servers'] = array(1);

$restoreInstance = PMA\libraries\Response::getInstance();

$mockResponse = $this->getMockBuilder('PMA\libraries\Response')
->disableOriginalConstructor()
->setMethods(array('isAjax', 'headersSent', 'header'))
->getMock();

$mockResponse->expects($this->once())
->method('isAjax')
->with()
->will($this->returnValue(false));

$mockResponse->expects($this->any())
->method('headersSent')
->with()
Expand All @@ -398,12 +396,9 @@ public function testAuthHeader()
$attrInstance->setAccessible(true);
$attrInstance->setValue($mockResponse);

$_REQUEST['old_usr'] = 'user1';
$GLOBALS['cfg']['Server']['LogoutURL'] = 'http://www.phpmyadmin.net/logout';

$this->assertTrue(
$this->object->auth()
);
$this->object->logOut();

$attrInstance->setValue($restoreInstance);
}
Expand Down Expand Up @@ -454,20 +449,40 @@ public function testAuthCheckCaptcha()
*/
public function testLogoutDelete()
{
$restoreInstance = PMA\libraries\Response::getInstance();

$mockResponse = $this->getMockBuilder('PMA\libraries\Response')
->disableOriginalConstructor()
->setMethods(array('isAjax', 'headersSent', 'header'))
->getMock();

$mockResponse->expects($this->any())
->method('headersSent')
->with()
->will($this->returnValue(false));

$mockResponse->expects($this->once())
->method('header')
->with('Location: ./index.php' . ((SID) ? '?' . SID : ''));

$attrInstance = new ReflectionProperty('PMA\libraries\Response', '_instance');
$attrInstance->setAccessible(true);
$attrInstance->setValue($mockResponse);

$GLOBALS['cfg']['Server']['auth_swekey_config'] = '';
$GLOBALS['cfg']['CaptchaLoginPrivateKey'] = '';
$GLOBALS['cfg']['CaptchaLoginPublicKey'] = '';
$_REQUEST['old_usr'] = 'pmaolduser';
$GLOBALS['cfg']['LoginCookieDeleteAll'] = true;
$GLOBALS['cfg']['Servers'] = array(1);

$_COOKIE['pmaPass-0'] = 'test';

$this->object->authCheck();
$this->object->logOut();

$this->assertFalse(
isset($_COOKIE['pmaPass-0'])
);
$attrInstance->setValue($restoreInstance);
}

/**
Expand All @@ -477,21 +492,40 @@ public function testLogoutDelete()
*/
public function testLogout()
{
$restoreInstance = PMA\libraries\Response::getInstance();

$mockResponse = $this->getMockBuilder('PMA\libraries\Response')
->disableOriginalConstructor()
->setMethods(array('isAjax', 'headersSent', 'header'))
->getMock();

$mockResponse->expects($this->any())
->method('headersSent')
->with()
->will($this->returnValue(false));

$mockResponse->expects($this->once())
->method('header')
->with('Location: ./index.php' . ((SID) ? '?' . SID : ''));

$attrInstance = new ReflectionProperty('PMA\libraries\Response', '_instance');
$attrInstance->setAccessible(true);
$attrInstance->setValue($mockResponse);
$GLOBALS['cfg']['Server']['auth_swekey_config'] = '';
$GLOBALS['cfg']['CaptchaLoginPrivateKey'] = '';
$GLOBALS['cfg']['CaptchaLoginPublicKey'] = '';
$_REQUEST['old_usr'] = 'pmaolduser';
$GLOBALS['cfg']['LoginCookieDeleteAll'] = false;
$GLOBALS['cfg']['Servers'] = array(1);
$GLOBALS['server'] = 1;

$_COOKIE['pmaPass-1'] = 'test';

$this->object->authCheck();
$this->object->logOut();

$this->assertFalse(
isset($_COOKIE['pmaPass-1'])
);
$attrInstance->setValue($restoreInstance);
}

/**
Expand Down
10 changes: 7 additions & 3 deletions test/classes/plugin/auth/AuthenticationHttpTest.php
Expand Up @@ -123,9 +123,13 @@ public function doMockResponse($set_minimal, $body_id, $set_title)
call_user_func_array(array($header_method, 'withConsecutive'), $headers);

try {
$this->assertFalse(
$this->object->auth()
);
if (!empty($_REQUEST['old_usr'])) {
$this->object->logOut();
} else {
$this->assertFalse(
$this->object->auth()
);
}
} finally {
$attrInstance->setValue($restoreInstance);
}
Expand Down

0 comments on commit 11eb574

Please sign in to comment.