diff --git a/src/wp-includes/class-wp-xmlrpc-server.php b/src/wp-includes/class-wp-xmlrpc-server.php index 9d7a643bec..bf6c7b1cb5 100644 --- a/src/wp-includes/class-wp-xmlrpc-server.php +++ b/src/wp-includes/class-wp-xmlrpc-server.php @@ -45,6 +45,14 @@ class wp_xmlrpc_server extends IXR_Server { */ public $error; + /** + * Flags that the user authentication has failed in this instance of wp_xmlrpc_server. + * + * @access protected + * @var bool + */ + protected $auth_failed = false; + /** * Register all of the XMLRPC methods that XMLRPC server understands. * @@ -251,11 +259,18 @@ class wp_xmlrpc_server extends IXR_Server { return false; } - $user = wp_authenticate($username, $password); + if ( $this->auth_failed ) { + $user = new WP_Error( 'login_prevented' ); + } else { + $user = wp_authenticate( $username, $password ); + } - if (is_wp_error($user)) { + if ( is_wp_error( $user ) ) { $this->error = new IXR_Error( 403, __( 'Incorrect username or password.' ) ); + // Flag that authentication has failed once on this wp_xmlrpc_server instance + $this->auth_failed = true; + /** * Filter the XML-RPC user login error message. * diff --git a/tests/phpunit/includes/testcase-xmlrpc.php b/tests/phpunit/includes/testcase-xmlrpc.php index 5fefb51422..9d268ce562 100644 --- a/tests/phpunit/includes/testcase-xmlrpc.php +++ b/tests/phpunit/includes/testcase-xmlrpc.php @@ -11,11 +11,14 @@ class WP_XMLRPC_UnitTestCase extends WP_UnitTestCase { add_filter( 'pre_option_enable_xmlrpc', '__return_true' ); - $this->myxmlrpcserver = new wp_xmlrpc_server(); + $this->myxmlrpcserver = new WP_XMLRPC_Server_UnitTestable(); } function tearDown() { remove_filter( 'pre_option_enable_xmlrpc', '__return_true' ); + + $this->myxmlrpcserver->reset_failed_auth(); + $this->remove_added_uploads(); parent::tearDown(); @@ -29,3 +32,9 @@ class WP_XMLRPC_UnitTestCase extends WP_UnitTestCase { )); } } + +class WP_XMLRPC_Server_UnitTestable extends wp_xmlrpc_server { + public function reset_failed_auth() { + $this->auth_failed = false; + } +} \ No newline at end of file diff --git a/tests/phpunit/tests/xmlrpc/basic.php b/tests/phpunit/tests/xmlrpc/basic.php index abfacc22c6..abe9e69eb6 100644 --- a/tests/phpunit/tests/xmlrpc/basic.php +++ b/tests/phpunit/tests/xmlrpc/basic.php @@ -19,10 +19,78 @@ class Tests_XMLRPC_Basic extends WP_XMLRPC_UnitTestCase { function test_login_pass_ok() { $user_id = $this->make_user_by_role( 'subscriber' ); - $this->assertFalse( $this->myxmlrpcserver->login_pass_ok( 'username', 'password' ) ); - $this->assertFalse( $this->myxmlrpcserver->login( 'username', 'password' ) ); - $this->assertTrue( $this->myxmlrpcserver->login_pass_ok( 'subscriber', 'subscriber' ) ); $this->assertInstanceOf( 'WP_User', $this->myxmlrpcserver->login( 'subscriber', 'subscriber' ) ); } + + function test_login_pass_bad() { + $user_id = $this->make_user_by_role( 'subscriber' ); + + $this->assertFalse( $this->myxmlrpcserver->login_pass_ok( 'username', 'password' ) ); + $this->assertFalse( $this->myxmlrpcserver->login( 'username', 'password' ) ); + + // The auth will still fail due to authentication blocking after the first failed attempt + $this->assertFalse( $this->myxmlrpcserver->login_pass_ok( 'subscriber', 'subscriber' ) ); + } + + /** + * @ticket 34336 + */ + function test_multicall_invalidates_all_calls_after_invalid_call() { + $editor_id = $this->make_user_by_role( 'editor' ); + $post_id = self::factory()->post->create( array( + 'post_author' => $editor_id, + ) ); + + $method_calls = array( + // Valid login + array( + 'methodName' => 'wp.editPost', + 'params' => array( + 0, + 'editor', + 'editor', + $post_id, + array( + 'title' => 'Title 1', + ), + ), + ), + // *Invalid* login + array( + 'methodName' => 'wp.editPost', + 'params' => array( + 0, + 'editor', + 'password', + $post_id, + array( + 'title' => 'Title 2', + ), + ), + ), + // Valid login + array( + 'methodName' => 'wp.editPost', + 'params' => array( + 0, + 'editor', + 'editor', + $post_id, + array( + 'title' => 'Title 3', + ), + ), + ), + ); + + $this->myxmlrpcserver->callbacks = $this->myxmlrpcserver->methods; + + $result = $this->myxmlrpcserver->multiCall( $method_calls ); + + $this->assertArrayNotHasKey( 'faultCode', $result[0] ); + $this->assertArrayHasKey( 'faultCode', $result[1] ); + $this->assertArrayHasKey( 'faultCode', $result[2] ); + + } } \ No newline at end of file