I use a modified simpleSAMLphp SSO authentication plugin for my blog network to allows a single sign-on using my main user database and then users are automatically authenticated/created in WordPress. The simpleSAMLphp plugin breaks XML-RPC authenticate, I know why and I'm happy enough with this BUT to provide a fix for users who want to use the WordPress mobile apps and other XML-RPC tools I have written a plug-in which allows users to set up a separate password for XML-RPC use which is stored in the user meta data table.
The problem is I can't figure our how to extend the login_pass_ok or login functions in class-wp-xmlrpc-server.php to allow my intended additional user password to be used. My plugin code looks like this at them moment.
<?php
/*
Plugin Name: XML RPC Separate Password
Plugin URI: http://dropdesign.co.uk/wordpress/xml_rpc_separate_password/
Description: Creates XML-RPC Password pages under Users menu to allow a separate password to be set for XML-RPC use. This is useful for WordPress setups where the main autheticate is external such as using SAML, LDAP or similar.
Version: 1.0
Author: Charlie Love
Author URI: http://charlielove.org
Text Domain: xml-rpc-separate-password
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
include_once(ABSPATH . WPINC . '/class-IXR.php');
include_once(ABSPATH . WPINC . '/class-wp-xmlrpc-server.php');
// for future use with localisation
//load_plugin_textdomain('xml-rpc-separate-password', false, basename(dirname(__FILE__)) . 'languages/');
//add the actions for the dashboard menus
add_action('admin_menu', 'xml_rpc_separate_password_init');
add_action('admin_init', 'xml_rpc_separate_password_check');
//Add a filter for XML RPC class - we'll replace the login check in the class with our one
add_filter( 'wp_xmlrpc_server_class', 'replace_xmlrpc_server_class' );
function xml_rpc_separate_password_init () {
add_submenu_page('users.php', __('Set Password for Mobile/XML-RPC App', 'xml-rpc-separate-password'), __('XML-RPC Password', 'xml-rpc-separate-password'), 0, 'change-password', 'xml_rpc_separate_password_pw');
}
function xml_rpc_separate_password_check () {
if (isset($_GET['page']) && ($_GET['page'] == 'change-password' )) {
wp_enqueue_script('user-profile');
wp_enqueue_script('password-strength-meter');
if (!empty($_POST)) {
global $wpdb, $user_ID, $user_email;
get_currentuserinfo();
$user = get_userdata($user_ID);
//write this password into the user meta data - DO NOT TOUCH THE CORE WORDPRESS USER PASSWORD
if (isset($_POST['pass1']) && isset($_POST['pass2']) && !empty($_POST['pass1']) && $_POST['pass1'] == $_POST['pass2']) {
//write the new xml-rpc authentication password into the user meta using the standard wp encryption for passwords
$update = update_user_meta( $user_ID, 'xml_rpc_password', array(wp_hash_password($_POST['pass1'])));
//if it hasn't screwed up say it's all super-duper!
if (!is_wp_error($update)) {
ob_start();
?>
<div id="message" class="updated fade">
<p><strong><?php _e('Password updated.') ?></strong></p>
</div>
<?php
$_POST['post_msg'] = ob_get_clean();
}
//eck, screwed up - fail!
if (is_wp_error($update)) {
ob_start();
?>
<div class="error">
<ul>
<?php
foreach ($update->get_error_messages() as $message) {
?>
<li><?php echo $message; ?></li>
<?php
}
?>
</ul>
</div>
<?php
$_POST['post_msg'] = ob_get_clean();
}
}
}
}
}
function xml_rpc_separate_password_pw () {
global $wpdb, $user_ID;
$title = __('Update Password for Mobile/XML-RPC Services');
$what = 'change-password';
$user = get_userdata($user_ID);
if (isset($_POST['post_msg']))
echo $_POST['post_msg'];
?>
<div class="wrap" id="profile-page">
<?php screen_icon(); ?>
<h2><?php echo esc_html($title); ?></h2>
<form id="your-profile" action="" method="post">
<table class="form-table">
<tr id="password">
<th><label for="pass1"><?php _e('New Password'); ?></label></th>
<td><input type="password" name="pass1" id="pass1" size="16" value="" autocomplete="off" />
<span class="description"><?php _e("If you would like to change the password type a new one. Otherwise leave this blank."); ?></span><br />
<input type="password" name="pass2" id="pass2" size="16" value="" autocomplete="off" />
<span class="description"><?php _e("Type your new password again."); ?></span><br />
<div id="pass-strength-result"><?php _e('Strength indicator'); ?></div>
<p class="description indicator-hint"><?php _e('Hint: The password should be at least seven characters long. To make it stronger, use upper and lower case letters, numbers and symbols like ! " ? $ % ^ & ).'); ?></p>
</td>
</tr>
</table>
<p class="submit">
<input type="hidden" name="user_login" id="user_login" value="<?php echo $user->user_login; ?>" />
<input type="submit" class="button-primary" value="<?php esc_attr_e('Update Password') ?>" name="submit" />
</p>
</form>
</div>
<?php
}
//now the serious stuff to authenticate XML-RPC calls using our saved user-meta password rather than the default WP user one!
/**
* Generate the Response
*
* @param methods Array - list of existing XMLRPC methods
* @return methods Array - list of updated XMLRPC methods
*/
function replace_xmlrpc_server_class( $class_name ) {
// only replace the default XML-RPC class if another plug-in hasn't already changed it
if ( $class_name === 'wp_xmlrpc_server' )
return 'wp_xmlrpc_server_ext';
else
return $class_name;
}
class wp_xmlrpc_server_ext extends wp_xmlrpc_server {
function __construct() {
// hook filter to add the new methods after the existing ones are added in the parent constructor
add_filter( 'xmlrpc_methods' , array( &$this, 'xmlrpc_methods' ) );
parent::__construct();
}
function xmlrpc_methods ( $methods ) {
$new_methods = array();
// array_merge will take the values defined in later arguments, so
// the plugin will not overwrite any methods defined by WP core
// (i.e., plugin will be forward-compatible with future releases of WordPress
// that include these methods built-in)
return array_merge( $new_methods, $methods );
}
//replace the login functions
/**
* Check user's credentials.
*
* @param string $user_login User's username.
* @param string $user_pass User's password.
* @return bool Whether authentication passed.
* @deprecated use wp_xmlrpc_server::login
* @see wp_xmlrpc_server::login
*/
function login_pass_ok($user_login, $user_pass) {
if ( !get_option( 'enable_xmlrpc' ) ) {
$this->error = new IXR_Error( 405, sprintf( __( 'XML-RPC services are disabled on this site. An admin user can enable them at %s'), admin_url('options-writing.php') ) );
return false;
}
// Let's run a check to see if credentials are okay
if (!function_exists('get_userdatabylogin'))
return new IXR_Error( 403, __( 'No user validation available.' ) );
//retrieve the user using the username
$user = get_userdatabylogin($user_login);
if(!$user)
return new IXR_Error( 403, __( 'User does not exist.' ) );
//get the user id from the user data
$user_id = $user->ID;
//retreive the stored hashed password array from the usermeta
$password_array = get_user_meta($user_id,'xml_rpc_password');
//stored the hashed password in an array so extract the hashed password from the array
$hashed_stored_password = $password_array[0];
//validate the hashed password with the user supplied one from XML-RPC
if ( !wp_check_password($user_pass,$hashed_stored_password) ) {
$this->error = new IXR_Error(403, __('Bad login/pass combination.'));
return false;
}
return true;
}
/**
* Log user in.
*
* @since 2.8
*
* @param string $username User's username.
* @param string $password User's password.
* @return mixed WP_User object if authentication passed, false otherwise
*/
function login($username, $password) {
if ( !get_option( 'enable_xmlrpc' ) ) {
$this->error = new IXR_Error( 405, sprintf( __( 'XML-RPC services are disabled on this site. An admin user can enable them at %s'), admin_url('options-writing.php') ) );
return false;
}
// Let's run a check to see if credentials are okay
if (!function_exists('get_userdatabylogin'))
return new IXR_Error( 403, __( 'No user validation available.' ) );
//retrieve the user using the username
$user = get_userdatabylogin($user_login);
if(!$user)
return new IXR_Error( 403, __( 'User does not exist.' ) );
//get the user id from the user data
$user_id = $user->ID;
//retreive the stored hashed password array from the usermeta
$password_array = get_user_meta($user_id,'xml_rpc_password');
//stored the hashed password in an array so extract the hashed password from the array
$hashed_stored_password = $password_array[0];
//validate the hashed password with the user supplied one from XML-RPC
if ( !wp_check_password($user_pass,$hashed_stored_password) ) {
$this->error = new IXR_Error(403, __('Bad login/pass combination.'));
return false;
}
wp_set_current_user( $user->ID );
return $user;
}
}
?>
I could just hack the class-wp-xmlrpc-server.php file and replace the two functions but I would much rather have something that works correctly as a plug. Any ideas how I can replace the functionality of these two functions?