Newer
Older
/*
Plugin Name: WP Redis User Session Storage
Plugin URI: https://ethitter.com/plugins/wp-redis-user-session-storage/
Description: Store WordPress session tokens in Redis rather than the usermeta table. Requires the Redis PECL extension.
Version: 0.1
Author: Erick Hitter
Author URI: https://ethitter.com/
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
*/
* Redis-based user sessions token manager.
class WP_Redis_User_Session_Storage extends WP_Session_Tokens {
/**
* Holds the Redis client.
*
* @var
*/
private $redis;
/**
* Track if Redis is available
*
* @var bool
*/
private $redis_connected = false;
/**
* Prefix used to namespace keys
*
* @var string
*/
public $prefix = 'wpruss';
/**
* Create Redis connection using the Redis PECL extension
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
*/
public function __construct( $user_id ) {
// General Redis settings
$redis = array(
'host' => '127.0.0.1',
'port' => 6379,
);
if ( defined( 'WP_REDIS_BACKEND_HOST' ) && WP_REDIS_BACKEND_HOST ) {
$redis['host'] = WP_REDIS_BACKEND_HOST;
}
if ( defined( 'WP_REDIS_BACKEND_PORT' ) && WP_REDIS_BACKEND_PORT ) {
$redis['port'] = WP_REDIS_BACKEND_PORT;
}
if ( defined( 'WP_REDIS_BACKEND_AUTH' ) && WP_REDIS_BACKEND_AUTH ) {
$redis['auth'] = WP_REDIS_BACKEND_AUTH;
}
if ( defined( 'WP_REDIS_BACKEND_DB' ) && WP_REDIS_BACKEND_DB ) {
$redis['database'] = WP_REDIS_BACKEND_DB;
}
if ( ( defined( 'WP_REDIS_SERIALIZER' ) ) ) {
$redis['serializer'] = WP_REDIS_SERIALIZER;
} else {
$redis['serializer'] = Redis::SERIALIZER_PHP;
}
// Use Redis PECL library.
try {
$this->redis = new Redis();
$this->redis->connect( $redis['host'], $redis['port'] );
$this->redis->setOption( Redis::OPT_SERIALIZER, $redis['serializer'] );
if ( isset( $redis['auth'] ) ) {
$this->redis->auth( $redis['auth'] );
}
if ( isset( $redis['database'] ) ) {
$this->redis->select( $redis['database'] );
}
$this->redis_connected = true;
} catch ( RedisException $e ) {
$this->redis_connected = false;
}
// Ensure Core's session constructor fires
parent::__construct( $user_id );
}
/**
* Get all sessions of a user.
*
* @access protected
*
* @return array Sessions of a user.
*/
protected function get_sessions() {
if ( ! $this->redis_connected ) {
return array();
}
$key = $this->get_key();
if ( ! $this->redis->exists( $key ) ) {
return array();
}
$sessions = $this->redis->get( $key );
if ( ! is_array( $sessions ) ) {
return array();
}
$sessions = array_map( array( $this, 'prepare_session' ), $sessions );
return array_filter( $sessions, array( $this, 'is_still_valid' ) );
}
/**
* Converts an expiration to an array of session information.
*
* @param mixed $session Session or expiration.
* @return array Session.
*/
protected function prepare_session( $session ) {
if ( is_int( $session ) ) {
return array( 'expiration' => $session );
}
return $session;
}
/**
* Retrieve a session by its verifier (token hash).
*
* @access protected
*
* @param string $verifier Verifier of the session to retrieve.
* @return array|null The session, or null if it does not exist
*/
protected function get_session( $verifier ) {
$sessions = $this->get_sessions();
if ( isset( $sessions[ $verifier ] ) ) {
return $sessions[ $verifier ];
}
return null;
}
/**
* Update a session by its verifier.
*
* @access protected
*
* @param string $verifier Verifier of the session to update.
* @param array $session Optional. Session. Omitting this argument destroys the session.
*/
protected function update_session( $verifier, $session = null ) {
$sessions = $this->get_sessions();
if ( $session ) {
$sessions[ $verifier ] = $session;
} else {
unset( $sessions[ $verifier ] );
}
$this->update_sessions( $sessions );
}
/**
* @access protected
*
* @param array $sessions Sessions.
*/
protected function update_sessions( $sessions ) {
if ( ! $this->redis_connected ) {
return;
}
if ( ! has_filter( 'attach_session_information' ) ) {
$sessions = wp_list_pluck( $sessions, 'expiration' );
}
$key = $this->get_key();
if ( $sessions ) {
$this->redis->set( $key, $sessions );
} elseif ( $this->redis->exists( $key ) ) {
$this->redis->del( $key );
}
}
/**
* Destroy all session tokens for a user, except a single session passed.
*
* @access protected
*
* @param string $verifier Verifier of the session to keep.
*/
protected function destroy_other_sessions( $verifier ) {
$session = $this->get_session( $verifier );
$this->update_sessions( array( $verifier => $session ) );
}
/**
* Destroy all session tokens for a user.
*
* @access protected
*/
protected function destroy_all_sessions() {
$this->update_sessions( array() );
}
/**
* Destroy all session tokens for all users.
*
* @access public
* @static
*/
public static function drop_sessions() {
* Build key for current user
* @since 0.1
* @access protected
*
* @return string
*/
protected function get_key() {
return $this->prefix . ':' . $this->user_id;
}
}
* Override Core's default usermeta-based token storage
add_filter( 'session_token_manager', function( $manager ) {
return 'WP_Redis_User_Session_Storage';
} );