Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/wp-includes/class-wp-xmlrpc-server.php
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@ public function addTwoNumbers( $args ) {
* Logs user in.
*
* @since 2.8.0
* @since 7.2.0 Non-scalar credentials are treated as a failed login.
*
* @param string $username User's username.
* @param string $password User's password.
Expand All @@ -304,6 +305,9 @@ public function login(

if ( $this->auth_failed ) {
$user = new WP_Error( 'login_prevented' );
} elseif ( ! is_scalar( $username ) || ! is_scalar( $password ) ) {
// A request can supply an array or object, which wp_authenticate() cannot handle.
$user = new WP_Error( 'invalid_credentials' );
} else {
$user = wp_authenticate( $username, $password );
}
Expand Down
56 changes: 56 additions & 0 deletions tests/phpunit/tests/xmlrpc/basic.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,62 @@ public function test_login_pass_bad() {
$this->assertFalse( $this->myxmlrpcserver->login_pass_ok( 'subscriber', 'subscriber' ) );
}

/**
* Tests that non-scalar credentials are rejected before reaching wp_authenticate().
*
* A non-scalar password triggers a fatal error there, and a non-scalar username
* an E_USER_WARNING.
*
* @ticket 65600
*
* @dataProvider data_non_scalar_credentials
*
* @covers wp_xmlrpc_server::login
*
* @param mixed $username Username argument.
* @param mixed $password Password argument.
*/
public function test_login_with_non_scalar_credentials( $username, $password ): void {
$this->assertFalse( $this->myxmlrpcserver->login( $username, $password ), 'The login did not fail.' );
$this->assertIXRError( $this->myxmlrpcserver->error );
$this->assertSame( 403, $this->myxmlrpcserver->error->code, 'The error code was not 403.' );
$this->assertSame( 'Incorrect username or password.', $this->myxmlrpcserver->error->message, 'The error message did not match.' );
}

/**
* Data provider for test_login_with_non_scalar_credentials.
*
* @return array[]
*/
public static function data_non_scalar_credentials(): array {
return array(
'an array as password' => array( 'subscriber', array() ),
'an array as username' => array( array(), 'subscriber' ),
'an object as password' => array( 'subscriber', new IXR_Date( '20260806T00:00:00' ) ),
'null as password' => array( 'subscriber', null ),
);
}

/**
* Tests that numeric credentials, which XML-RPC sends as `<int>` values,
* continue to authenticate.
*
* @ticket 65600
*
* @covers wp_xmlrpc_server::login
*/
public function test_login_with_numeric_credentials(): void {
self::factory()->user->create(
array(
'user_login' => '12345',
'user_pass' => '67890',
'role' => 'subscriber',
)
);

$this->assertInstanceOf( 'WP_User', $this->myxmlrpcserver->login( 12345, 67890 ) );
}

/**
* @ticket 34336
*/
Expand Down
93 changes: 93 additions & 0 deletions tests/phpunit/tests/xmlrpc/wp/getUsersBlogs.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<?php

/**
* Tests for the XML-RPC wp.getUsersBlogs method.
*
* @group xmlrpc
* @group user
*
* @covers wp_xmlrpc_server::wp_getUsersBlogs
*/
class Tests_XMLRPC_wp_getUsersBlogs extends WP_XMLRPC_UnitTestCase {

/**
* Tests that non-scalar credentials return an error rather than reaching
* wp_authenticate(), where a non-scalar password triggers a fatal error and
* a non-scalar username an E_USER_WARNING.
*
* @ticket 65600
*
* @dataProvider data_non_scalar_credentials
*
* @param mixed $username Username argument.
* @param mixed $password Password argument.
*/
public function test_non_scalar_credentials_should_return_error( $username, $password ): void {
$result = $this->myxmlrpcserver->wp_getUsersBlogs( array( $username, $password ) );

$this->assertIXRError( $result );
$this->assertSame( 403, $result->code );
$this->assertSame( 'Incorrect username or password.', $result->message );
}

/**
* Data provider for test_non_scalar_credentials_should_return_error.
*
* @return array[]
*/
public static function data_non_scalar_credentials(): array {
return array(
'an array as password' => array( 'subscriber', array() ),
'an array as username' => array( array(), 'subscriber' ),
'an object as password' => array( 'subscriber', new IXR_Date( '20260806T00:00:00' ) ),
'an object as username' => array( new IXR_Date( '20260806T00:00:00' ), 'subscriber' ),
'null as password' => array( 'subscriber', null ),
'null as username' => array( null, 'subscriber' ),
);
}

/**
* Tests that valid string credentials are still delegated to blogger_getUsersBlogs().
*
* @ticket 65600
* @group ms-excluded
*/
public function test_valid_credentials_should_return_blogs(): void {
$this->make_user_by_role( 'subscriber' );

$result = $this->myxmlrpcserver->wp_getUsersBlogs( array( 'subscriber', 'subscriber' ) );

$this->assertNotIXRError( $result, 'The result should not be an instance of IXR_Error.' );
$this->assertIsArray( $result, 'The result should be an array.' );
$this->assertCount( 1, $result, 'The result should contain a single blog.' );

$blog = $result[0];
$this->assertSame( '1', $blog['blogid'], 'The blogid should be that of the only blog.' );
$this->assertSame( get_option( 'blogname' ), $blog['blogName'], 'The blogName should match the site name.' );
$this->assertFalse( $blog['isAdmin'], 'A subscriber should not be flagged as an administrator.' );
}

/**
* Tests that valid string credentials still return blogs on multisite, where
* wp_getUsersBlogs() handles the request itself rather than delegating.
*
* @ticket 65600
* @group ms-required
* @group multisite
*/
public function test_valid_credentials_should_return_blogs_on_multisite(): void {
$this->make_user_by_role( 'subscriber' );

$result = $this->myxmlrpcserver->wp_getUsersBlogs( array( 'subscriber', 'subscriber' ) );

$this->assertNotIXRError( $result, 'The result should not be an instance of IXR_Error.' );
$this->assertIsArray( $result, 'The result should be an array.' );
$this->assertNotEmpty( $result, 'The result should not be empty.' );

$blog = $result[0];
$this->assertArrayHasKey( 'url', $blog, 'The result should include the url field.' );
$this->assertArrayHasKey( 'blogid', $blog, 'The result should include the blogid field.' );
$this->assertArrayHasKey( 'blogName', $blog, 'The result should include the blogName field.' );
$this->assertArrayHasKey( 'isPrimary', $blog, 'The result should include the isPrimary field.' );
}
}