6

I'm working on a plugin derived from this tutorial. So far, the plugin adds a checkbox on a user's profile edit page where admins can ban the user.

checkbox

If checked, the user will receive a message upon logging in:

banned

It works fine. The problem is, if the user is already logged in (has an active session) and gets banned, the user can continue to interact with the site until either the session ends or the user logs out.

How do I end the user's session upon banning so the user is forced out?

Here is the code:

/**
 * Admin init
 *
 * @access      public
 * @since       1.0 
 * @return      void
*/
function rc_admin_init(){

    // Edit user profile
    add_action( 'edit_user_profile', 'rc_edit_user_profile' );
    add_action( 'edit_user_profile_update', 'rc_edit_user_profile_update' );

}
add_action('admin_init', 'rc_admin_init' );


/**
 * Adds custom checkbox to user edition page
 *
 * @access      public
 * @since       1.0 
 * @return      void
*/
function rc_edit_user_profile() {
    if ( !current_user_can( 'edit_users' ) ) {
        return;
    }

    global $user_id;

    // User cannot disable itself
    $current_user = wp_get_current_user();
    $current_user_id = $current_user->ID;
    if ( $current_user_id == $user_id ) {
        return;
    }
    ?>
    <h3>Ban user</h3>
    <table class="form-table">
    <tr>
        <th scope="row"></th>
        <td><label for="rc_ban"><input name="rc_ban" type="checkbox" id="rc_ban"  /> Check to ban user.</label></td>
    </tr>
    </table>
    <?php
}


/**
 * Save custom checkbox
 *
 * @access      public
 * @since       1.0 
 * @return      void
*/
function rc_edit_user_profile_update() {

    if ( !current_user_can( 'edit_users' ) ) {
        return;
    }

    global $user_id;

    // User cannot disable itself
    $current_user    = wp_get_current_user();
    $current_user_id = $current_user->ID;
    if ( $current_user_id == $user_id ) {
        return;
    }

    // Lock
    if( isset( $_POST['rc_ban'] ) && $_POST['rc_ban'] = 'on' ) {
        rc_ban_user( $user_id );
    } else { // Unlock
        rc_unban_user( $user_id );
    }

}


/**
 * Ban user
 *
 * @access      public
 * @since       1.0 
 * @return      void
*/
function rc_ban_user( $user_id ) {

    $old_status = rc_is_user_banned( $user_id );

    // Update status
    if ( !$old_status ) {
        update_user_option( $user_id, 'rc_banned', true, false );
    }
}


/**
 * Un-ban user
 *
 * @access      public
 * @since       1.0 
 * @return      void
*/
function rc_unban_user( $user_id ) {

    $old_status = rc_is_user_banned( $user_id );

    // Update status
    if ( $old_status ) {
        update_user_option( $user_id, 'rc_banned', false, false );
    }
}


/**
 * Checks if a user is already banned
 *
 * @access      public
 * @since       1.0 
 * @return      void
*/
function rc_is_user_banned( $user_id ) {
    return get_user_option( 'rc_banned', $user_id, false );
}


/**
 * Check if user is locked while login process
 *
 * @access      public
 * @since       1.0 
 * @return      void
*/
function rc_authenticate_user( $user ) {

    if ( is_wp_error( $user ) ) {
        return $user;
    }

    // Return error if user account is banned
    $banned = get_user_option( 'rc_banned', $user->ID, false );
    if ( $banned ) {
        return new WP_Error( 'rc_banned', __('<strong>ERROR</strong>: This user account is disabled.', 'rc') );
    }

    return $user;
}

add_filter( 'wp_authenticate_user', 'rc_authenticate_user', 1 );

So the ending of the session should go in the rc_ban_user() function.

EDIT: Full plugin posted below.

5

Use wp_logout(). It calls wp_clear_auth_cookie() and invalidates the current log-in information immediately.

Sample code, not tested:

add_action( 'init', 'log_out_banned_user' );

function log_out_banned_user() {
    if ( ! is_user_logged_in() )
        return;

    $user = wp_get_current_user();

    if ( ! get_user_option( 'rc_banned', $user->ID, false ) )
        return;

    wp_logout();
    wp_redirect( home_url( '/' ) );
    exit;
}
  • 1
    Just a small remark to your (absolutely correct) answer: The user will still be logged in for that pageview as init hooks after the user authentification. To be totally safe you could add a redirect to the end of your function. – s1lv3r Nov 20 '13 at 16:27
  • @s1lv3r Good point. I have extended my example. – fuxia Nov 20 '13 at 20:19
  • 1
    @toscho Confirming that this code works great! I will post the full plugin once it is ready. One question, will this solution affect performance much? – Christine Cooper Nov 23 '13 at 17:31
  • @ChristineCooper Depends on the amount of your users and on the database indexes. Meta queries are slow sometimes. – fuxia Nov 23 '13 at 18:16
4

While toscho's method works, a simpler approach might be to use the authenticate hook to prevent them from authenticating via cookie, or any other means, in a more direct fashion.

Totally untested code. Should work though.

// the priority of 999 is to ensure it's last in the auth chain
add_filter('authenticate', 'force_fail_banned_users', 999, 3); 

function force_fail_banned_users($user, $username, $password) {
    if ( ! is_a($user, 'WP_User') ) { 
        // we only care about actual users who already auth'd okay via some means
        return $user; 
    }

    if ( rc_is_user_banned( $user->ID ) ) {
        // user is banned, so return a failure case
        return new WP_Error('banned_user', 'Banned message goes here.');
    }

    // user not banned, so return the user normally
    return $user;
}

The authenticate filter chain lets you decide whether or not a user is authenticated at every possible opportunity for them to authenticate. Returning a value WP_User logs them in. Returning a WP_Error of any sort fails their authentication attempt, no matter how it was done, whether via username/password or via cookie.

  • 1
    +1 but be wary on the filter's priority. The internal WP functions don't check if $user already is a WP_Error, meaning you want to check after they're all done — priority 31 or higher. – Denis de Bernardy Nov 20 '13 at 23:01
  • Yeah, that's why I set it to 999. – Otto Nov 21 '13 at 2:24
  • @Otto I tested this code without success, user can continue to browse the site. Do you by any chance know where the issue lies to make it function? Also, is this a better solution compared to toscho's working code when considering server performance etc? – Christine Cooper Nov 21 '13 at 16:17
  • This won't block them from "browsing the site", it will block them from authenticating or logging in. If you want to block viewing of any kind, that's a whole different thing. – Otto Dec 5 '13 at 19:28
2

I also write similar plugin and already published it on WordPress.org. I think the best solution drop user session immediately then administrator click "ban" (block) button (link). This possible with WP_Session_Tokens class:

$manager = \WP_Session_Tokens::get_instance( $user_id );
$manager->destroy_all();

And even if user currently authorised and some pages from /wp-admin/ opened they will be force log out because we already drop sessions (immediately).

Source code: https://wordpress.org/plugins/mark-user-as-spammer/

1

The plugin is now ready to be used and functions elegantly. Thanks to Remi for the original tutorial and toscho (the bot) for the working solution to end users session upon banning.

Future Updates

I plan the following for future updates:

  • Option to delete a user's comments or/and posts upon banning.
  • Add a custom ban message for specific user.
  • Allow admins to ban multiple users on users listing page in wp-admin.

Leave a comment if you have any update suggestions. I will edit this answer whenever there are any updates to the plugin.

Ban Users Plugin

Create a folder named ban-users in your plugin folder and create a file named ban-users.php with the following code:

<?php
/*
Plugin Name: Ban Users
Plugin URI: http://wordpress.stackexchange.com/questions/123902/ban-a-user-and-end-their-session
Description: Allows you to ban users
Author: Christine Cooper, Remi, toscho
Version: 1.1
Author URI: http://wordpress.stackexchange.com/questions/123902/ban-a-user-and-end-their-session
*/


/**
 * Admin init
 *
 * @access      public
 * @since       1.0 
 * @return      void
*/
function rc_admin_init(){

    // Edit user profile
    add_action( 'edit_user_profile', 'rc_edit_user_profile' );
    add_action( 'edit_user_profile_update', 'rc_edit_user_profile_update' );

}
add_action('admin_init', 'rc_admin_init' );


/**
 * Adds custom checkbox to user edition page
 *
 * @access      public
 * @since       1.0 
 * @return      void
*/
function rc_edit_user_profile() {
    if ( !current_user_can( 'edit_users' ) ) {
        return;
    }

    global $user_id;

    // User cannot disable itself
    $current_user = wp_get_current_user();
    $current_user_id = $current_user->ID;
    if ( $current_user_id == $user_id ) {
        return;
    }
    ?>
    <h3>Ban User</h3>
    <table class="form-table">
    <tr>
        <th scope="row"></th>
        <td><label for="rc_ban"><input name="rc_ban" type="checkbox" id="rc_ban" <?php if ( get_user_option( 'rc_banned', $user_id, false ) ) { echo 'checked'; } ?> /> Check to ban user </label></td>
    </tr>
    </table>
    <?php
}


/**
 * Save custom checkbox
 *
 * @access      public
 * @since       1.0 
 * @return      void
*/
function rc_edit_user_profile_update() {

    if ( !current_user_can( 'edit_users' ) ) {
        return;
    }

    global $user_id;

    // User cannot disable itself
    $current_user    = wp_get_current_user();
    $current_user_id = $current_user->ID;
    if ( $current_user_id == $user_id ) {
        return;
    }

    // Lock
    if( isset( $_POST['rc_ban'] ) && $_POST['rc_ban'] = 'on' ) {
        rc_ban_user( $user_id );
    } else { // Unlock
        rc_unban_user( $user_id );
    }

}


/**
 * Ban user
 *
 * @access      public
 * @since       1.0 
 * @return      void
*/
function rc_ban_user( $user_id ) {

    $old_status = rc_is_user_banned( $user_id );

    // Update status
    if ( !$old_status ) {
        update_user_option( $user_id, 'rc_banned', true, false );
    }
}


/**
 * Un-ban user
 *
 * @access      public
 * @since       1.0 
 * @return      void
*/
function rc_unban_user( $user_id ) {

    $old_status = rc_is_user_banned( $user_id );

    // Update status
    if ( $old_status ) {
        update_user_option( $user_id, 'rc_banned', false, false );
    }
}


/**
 * Checks if a user is already banned
 *
 * @access      public
 * @since       1.0 
 * @return      void
*/
function rc_is_user_banned( $user_id ) {
    return get_user_option( 'rc_banned', $user_id, false );
}



/**
 * End a users active session if they are banned
 * By toscho http://wordpress.stackexchange.com/a/123903/24875
 *
 * @access      public
 * @since       1.1 
 * @return      void
*/
add_action( 'init', 'log_out_banned_user' );

function log_out_banned_user() {
    if ( ! is_user_logged_in() )
        return;

    $user = wp_get_current_user();

    if ( ! get_user_option( 'rc_banned', $user->ID, false ) )
        return;

    wp_logout();
    wp_redirect( home_url( '/' ) );
    exit;
}



/**
 * Check if user is locked while login process
 *
 * @access      public
 * @since       1.0 
 * @return      void
*/
function rc_authenticate_user( $user ) {

    if ( is_wp_error( $user ) ) {
        return $user;
    }

    // Return error if user account is banned
    $banned = get_user_option( 'rc_banned', $user->ID, false );
    if ( $banned ) {
        return new WP_Error( 'rc_banned', __('<strong>ERROR</strong>: Your user account has been disabled.', 'rc') );
    }

    return $user;
}

add_filter( 'wp_authenticate_user', 'rc_authenticate_user', 1 );

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.