Add nonce to the logout link (#3264)
* Add nonce to the logout link * Add tests for cookies being set or reset * More tests: check nonces are different for different actions & users Fixes #3170
This commit is contained in:
parent
55db480f43
commit
1de256d869
1
.gitignore
vendored
1
.gitignore
vendored
@ -31,6 +31,7 @@ includes/**/tests/
|
|||||||
build/
|
build/
|
||||||
coverage/
|
coverage/
|
||||||
phpunit.xml
|
phpunit.xml
|
||||||
|
.phpunit.result.cache
|
||||||
tests/yourls-tests-config.php
|
tests/yourls-tests-config.php
|
||||||
tests/vendor/
|
tests/vendor/
|
||||||
tests/data/auth/config-test-auth-hashed.php
|
tests/data/auth/config-test-auth-hashed.php
|
||||||
|
@ -40,11 +40,6 @@ switch( $action ) {
|
|||||||
echo json_encode(array('success'=>$query));
|
echo json_encode(array('success'=>$query));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'logout':
|
|
||||||
// unused for the moment
|
|
||||||
yourls_logout();
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
yourls_do_action( 'yourls_ajax_'.$action );
|
yourls_do_action( 'yourls_ajax_'.$action );
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ function yourls_maybe_require_auth() {
|
|||||||
/**
|
/**
|
||||||
* Check for valid user via login form or stored cookie. Returns true or an error message
|
* Check for valid user via login form or stored cookie. Returns true or an error message
|
||||||
*
|
*
|
||||||
|
* @return bool|string|mixed true if valid user, error message otherwise. Can also call yourls_die() or redirect to login page. Oh my.
|
||||||
*/
|
*/
|
||||||
function yourls_is_valid_user() {
|
function yourls_is_valid_user() {
|
||||||
// Allow plugins to short-circuit the whole function
|
// Allow plugins to short-circuit the whole function
|
||||||
@ -32,7 +33,9 @@ function yourls_is_valid_user() {
|
|||||||
$unfiltered_valid = false;
|
$unfiltered_valid = false;
|
||||||
|
|
||||||
// Logout request
|
// Logout request
|
||||||
if( isset( $_GET['action'] ) && $_GET['action'] == 'logout' ) {
|
if( isset( $_GET['action'] ) && $_GET['action'] == 'logout' && isset( $_REQUEST['nonce'] ) ) {
|
||||||
|
// The logout nonce is associated to fake user 'logout' since at this point we don't know the real user
|
||||||
|
yourls_verify_nonce('admin_logout', $_REQUEST['nonce'], 'logout');
|
||||||
yourls_do_action( 'logout' );
|
yourls_do_action( 'logout' );
|
||||||
yourls_store_cookie( null );
|
yourls_store_cookie( null );
|
||||||
return yourls__( 'Logged out successfully' );
|
return yourls__( 'Logged out successfully' );
|
||||||
@ -444,6 +447,8 @@ function yourls_store_cookie( $user = null ) {
|
|||||||
if ( $domain == 'localhost' )
|
if ( $domain == 'localhost' )
|
||||||
$domain = '';
|
$domain = '';
|
||||||
|
|
||||||
|
yourls_do_action( 'pre_setcookie', $user, $time, $path, $domain, $secure, $httponly );
|
||||||
|
|
||||||
if ( !headers_sent( $filename, $linenum ) ) {
|
if ( !headers_sent( $filename, $linenum ) ) {
|
||||||
yourls_setcookie( yourls_cookie_name(), yourls_cookie_value( $user ), $time, $path, $domain, $secure, $httponly );
|
yourls_setcookie( yourls_cookie_name(), yourls_cookie_value( $user ), $time, $path, $domain, $secure, $httponly );
|
||||||
} else {
|
} else {
|
||||||
@ -576,7 +581,7 @@ function yourls_salt( $string ) {
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
function yourls_create_nonce( $action, $user = false ) {
|
function yourls_create_nonce( $action, $user = false ) {
|
||||||
if( false == $user )
|
if( false === $user )
|
||||||
$user = defined( 'YOURLS_USER' ) ? YOURLS_USER : '-1';
|
$user = defined( 'YOURLS_USER' ) ? YOURLS_USER : '-1';
|
||||||
$tick = yourls_tick();
|
$tick = yourls_tick();
|
||||||
$nonce = substr( yourls_salt($tick . $action . $user), 0, 10 );
|
$nonce = substr( yourls_salt($tick . $action . $user), 0, 10 );
|
||||||
@ -612,24 +617,25 @@ function yourls_nonce_url( $action, $url = false, $name = 'nonce', $user = false
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
function yourls_verify_nonce( $action, $nonce = false, $user = false, $return = '' ) {
|
function yourls_verify_nonce( $action, $nonce = false, $user = false, $return = '' ) {
|
||||||
// get user
|
// Get user
|
||||||
if( false == $user )
|
if( false === $user ) {
|
||||||
$user = defined( 'YOURLS_USER' ) ? YOURLS_USER : '-1';
|
$user = defined('YOURLS_USER') ? YOURLS_USER : '-1';
|
||||||
|
}
|
||||||
|
|
||||||
// get current nonce value
|
// Get nonce value from $_REQUEST if not specified
|
||||||
if( false == $nonce && isset( $_REQUEST['nonce'] ) )
|
if( false === $nonce && isset( $_REQUEST['nonce'] ) ) {
|
||||||
$nonce = $_REQUEST['nonce'];
|
$nonce = $_REQUEST['nonce'];
|
||||||
|
}
|
||||||
|
|
||||||
// Allow plugins to short-circuit the rest of the function
|
// Allow plugins to short-circuit the rest of the function
|
||||||
$valid = yourls_apply_filter( 'verify_nonce', false, $action, $nonce, $user, $return );
|
if (yourls_apply_filter( 'verify_nonce', false, $action, $nonce, $user, $return ) === true) {
|
||||||
if ($valid) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// what nonce should be
|
// What nonce should be
|
||||||
$valid = yourls_create_nonce( $action, $user );
|
$valid = yourls_create_nonce( $action, $user );
|
||||||
|
|
||||||
if( $nonce == $valid ) {
|
if( $nonce === $valid ) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
if( $return )
|
if( $return )
|
||||||
|
@ -750,7 +750,11 @@ function yourls_html_menu() {
|
|||||||
|
|
||||||
// Build menu links
|
// Build menu links
|
||||||
if( defined( 'YOURLS_USER' ) ) {
|
if( defined( 'YOURLS_USER' ) ) {
|
||||||
$logout_link = yourls_apply_filter( 'logout_link', sprintf( yourls__('Hello <strong>%s</strong>'), YOURLS_USER ) . ' (<a href="' . yourls_admin_url( 'index.php' ) . '?action=logout" title="' . yourls_esc_attr__( 'Logout' ) . '">' . yourls__( 'Logout' ) . '</a>)' );
|
// Create a logout link with a nonce associated to fake user 'logout' : the user is not yet defined
|
||||||
|
// when the logout check is done -- see yourls_is_valid_user()
|
||||||
|
$logout_url = yourls_nonce_url( 'admin_logout',
|
||||||
|
yourls_add_query_arg(['action' => 'logout'], yourls_admin_url('index.php')), 'nonce', 'logout');
|
||||||
|
$logout_link = yourls_apply_filter('logout_link', sprintf( yourls__('Hello <strong>%s</strong>'), YOURLS_USER ) . ' (<a href="' . $logout_url . '" title="' . yourls_esc_attr__( 'Logout' ) . '">' . yourls__( 'Logout' ) . '</a>)' );
|
||||||
} else {
|
} else {
|
||||||
$logout_link = yourls_apply_filter( 'logout_link', '' );
|
$logout_link = yourls_apply_filter( 'logout_link', '' );
|
||||||
}
|
}
|
||||||
|
@ -11,15 +11,20 @@ class Auth_Login_Cookie_Tests extends PHPUnit\Framework\TestCase {
|
|||||||
|
|
||||||
protected $cookie;
|
protected $cookie;
|
||||||
protected $request;
|
protected $request;
|
||||||
|
protected $backup_yourls_actions;
|
||||||
|
|
||||||
protected function setUp(): void {
|
protected function setUp(): void {
|
||||||
$this->cookie = $_COOKIE;
|
$this->cookie = $_COOKIE;
|
||||||
$this->request = $_REQUEST;
|
$this->request = $_REQUEST;
|
||||||
|
global $yourls_actions;
|
||||||
|
$this->backup_yourls_actions = $yourls_actions;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function tearDown(): void {
|
protected function tearDown(): void {
|
||||||
$_COOKIE = $this->cookie;
|
$_COOKIE = $this->cookie;
|
||||||
$_REQUEST = $this->request;
|
$_REQUEST = $this->request;
|
||||||
|
global $yourls_actions;
|
||||||
|
$yourls_actions = $this->backup_yourls_actions;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function setUpBeforeClass(): void {
|
public static function setUpBeforeClass(): void {
|
||||||
@ -52,7 +57,7 @@ class Auth_Login_Cookie_Tests extends PHPUnit\Framework\TestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test login with valid cookie
|
* Test login with valid cookie - also check that cookie is set
|
||||||
*/
|
*/
|
||||||
public function test_login_valid_cookie() {
|
public function test_login_valid_cookie() {
|
||||||
global $yourls_user_passwords;
|
global $yourls_user_passwords;
|
||||||
@ -60,19 +65,23 @@ class Auth_Login_Cookie_Tests extends PHPUnit\Framework\TestCase {
|
|||||||
$_COOKIE[yourls_cookie_name()] = yourls_cookie_value( $random_user );
|
$_COOKIE[yourls_cookie_name()] = yourls_cookie_value( $random_user );
|
||||||
unset($_REQUEST);
|
unset($_REQUEST);
|
||||||
|
|
||||||
|
$this->assertSame( 0, yourls_did_action('pre_setcookie') );
|
||||||
$this->assertTrue(yourls_check_auth_cookie());
|
$this->assertTrue(yourls_check_auth_cookie());
|
||||||
$this->assertTrue(yourls_is_valid_user());
|
$this->assertTrue(yourls_is_valid_user());
|
||||||
|
$this->assertSame( 1, yourls_did_action('pre_setcookie') );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test login with invalid cookie
|
* Test login with invalid cookie - also check that no cookie is set
|
||||||
*/
|
*/
|
||||||
public function test_login_invalid_cookie() {
|
public function test_login_invalid_cookie() {
|
||||||
$_COOKIE[yourls_cookie_name()] = yourls_cookie_value( rand_str() );
|
$_COOKIE[yourls_cookie_name()] = yourls_cookie_value( rand_str() );
|
||||||
unset($_REQUEST);
|
unset($_REQUEST);
|
||||||
|
|
||||||
|
$this->assertSame( 0, yourls_did_action('pre_setcookie') );
|
||||||
$this->assertFalse(yourls_check_auth_cookie());
|
$this->assertFalse(yourls_check_auth_cookie());
|
||||||
$this->assertNotTrue(yourls_is_valid_user());
|
$this->assertNotTrue(yourls_is_valid_user());
|
||||||
|
$this->assertSame( 0, yourls_did_action('pre_setcookie') );
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -9,10 +9,12 @@ class Logout_Func_Tests extends PHPUnit\Framework\TestCase {
|
|||||||
|
|
||||||
protected $backup_get;
|
protected $backup_get;
|
||||||
protected $backup_request;
|
protected $backup_request;
|
||||||
|
private static $user;
|
||||||
|
|
||||||
protected function setUp(): void {
|
protected function setUp(): void {
|
||||||
$this->backup_get = $_GET;
|
$this->backup_get = $_GET;
|
||||||
$this->backup_request = $_REQUEST;
|
$this->backup_request = $_REQUEST;
|
||||||
|
self::$user = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function tearDown(): void {
|
protected function tearDown(): void {
|
||||||
@ -20,33 +22,47 @@ class Logout_Func_Tests extends PHPUnit\Framework\TestCase {
|
|||||||
$_REQUEST = $this->backup_request;
|
$_REQUEST = $this->backup_request;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function setUpBeforeClass(): void {
|
||||||
|
yourls_add_action( 'pre_setcookie', function ($in) {
|
||||||
|
self::$user = $in[0]; // $in[0] is the user ID passed to yourls_setcookie()
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function tearDownAfterClass(): void {
|
||||||
|
yourls_remove_all_actions('pre_setcookie');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check logout procedure - phase 1
|
* Check logout procedure - phase 1 - we're logging in
|
||||||
*/
|
*/
|
||||||
public function test_logout_user_is_logged_in() {
|
public function test_logout_user_is_logged_in() {
|
||||||
$_REQUEST['nonce'] = yourls_create_nonce('admin_login');
|
$_REQUEST['nonce'] = yourls_create_nonce('admin_login');
|
||||||
$valid = yourls_is_valid_user();
|
$valid = yourls_is_valid_user();
|
||||||
$this->assertTrue($valid);
|
$this->assertTrue($valid);
|
||||||
|
$this->assertSame(self::$user, 'yourls');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check logout procedure - phase 2
|
* Check logout procedure - phase 2 - we're logging out and checking that cookie was reset
|
||||||
* @depends test_logout_user_is_logged_in
|
* @depends test_logout_user_is_logged_in
|
||||||
*/
|
*/
|
||||||
public function test_logout_user_logs_out() {
|
public function test_logout_user_logs_out() {
|
||||||
$_GET['action'] = 'logout';
|
$_GET['action'] = 'logout';
|
||||||
|
$_REQUEST['nonce'] = yourls_create_nonce('admin_logout', 'logout');
|
||||||
$invalid = yourls_is_valid_user();
|
$invalid = yourls_is_valid_user();
|
||||||
$this->assertNotTrue( $invalid );
|
$this->assertNotTrue( $invalid );
|
||||||
|
$this->assertSame(self::$user, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check logout procedure - phase 3
|
* Check logout procedure - phase 3 - check we can log in again
|
||||||
* @depends test_logout_user_logs_out
|
* @depends test_logout_user_logs_out
|
||||||
*/
|
*/
|
||||||
public function test_logout_user_is_logged_in_back() {
|
public function test_logout_user_is_logged_in_back() {
|
||||||
$_REQUEST['nonce'] = yourls_create_nonce('admin_login');
|
$_REQUEST['nonce'] = yourls_create_nonce('admin_login');
|
||||||
$valid = yourls_is_valid_user();
|
$valid = yourls_is_valid_user();
|
||||||
$this->assertTrue( $valid );
|
$this->assertTrue( $valid );
|
||||||
|
$this->assertSame(self::$user, 'yourls');
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -11,4 +11,6 @@ class Misc_Auth_Tests extends PHPUnit\Framework\TestCase {
|
|||||||
$this->assertIsBool(yourls_skip_password_hashing());
|
$this->assertIsBool(yourls_skip_password_hashing());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -47,7 +47,6 @@ class Auth_Nonce_Tests extends PHPUnit\Framework\TestCase {
|
|||||||
public function test_create_nonce_url() {
|
public function test_create_nonce_url() {
|
||||||
$url = yourls_nonce_url( rand_str(), rand_str(), rand_str(), rand_str() );
|
$url = yourls_nonce_url( rand_str(), rand_str(), rand_str(), rand_str() );
|
||||||
$this->assertTrue( is_string($url) );
|
$this->assertTrue( is_string($url) );
|
||||||
// $this->assertIsString($url);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -77,4 +76,23 @@ class Auth_Nonce_Tests extends PHPUnit\Framework\TestCase {
|
|||||||
$this->assertTrue(yourls_verify_nonce(rand_str(), rand_str(), rand_str()));
|
$this->assertTrue(yourls_verify_nonce(rand_str(), rand_str(), rand_str()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check nonces are different for different actions & users
|
||||||
|
*/
|
||||||
|
public function test_nonce_different_for_different_actions_and_users() {
|
||||||
|
$action1 = rand_str();
|
||||||
|
$action2 = rand_str();
|
||||||
|
$user1 = rand_str();
|
||||||
|
$user2 = rand_str();
|
||||||
|
|
||||||
|
$nonce_a1 = yourls_create_nonce($action1);
|
||||||
|
$nonce_a2 = yourls_create_nonce($action2);
|
||||||
|
$nonce_a1_u1 = yourls_create_nonce($action1, $user1);
|
||||||
|
$nonce_a1_u2 = yourls_create_nonce($action1, $user2);
|
||||||
|
|
||||||
|
$this->assertNotEquals($nonce_a1, $nonce_a2);
|
||||||
|
$this->assertNotEquals($nonce_a1_u1, $nonce_a1_u2);
|
||||||
|
$this->assertNotEquals($nonce_a1, $nonce_a1_u1);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user