• 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: https://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: https://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?

Viewing 2 replies - 1 through 2 (of 2 total)
  • can you write how to hack class-wp-xmlrpc-server.php?

    This is a rather old post, but I’ll add my two cents in case anyone were to find this post trying to provide similar functionality.

    Most authentication in my experience provide authentication support by use of the ‘wp_authenticate’ hook that is used called in wp-login.php. The xmlrpc server login does not call the authenticate hook, but instead calls the wp_authenticate function. Additionally, the login_pass_ok calls user_pass_ok, which then also calls wp_authenticate.

    The wp_authenticate function is defined in pluggable.php, so you can write a plugin that overrides the wp_authenticate function and calls the wp_authenticate action and should provide authentication through xmlrpc and any other scripted method that calls wp_authenticate.

Viewing 2 replies - 1 through 2 (of 2 total)
  • The topic ‘Custom XML-RPC authentication’ is closed to new replies.