3 * Abstract class for managing user session tokens.
7 abstract class WP_Session_Tokens {
19 * Protected constructor.
23 * @param int $user_id User whose session to manage.
25 protected function __construct( $user_id ) {
26 $this->user_id = $user_id;
30 * Get a session token manager instance for a user.
32 * This method contains a filter that allows a plugin to swap out
33 * the session manager for a subclass of WP_Session_Tokens.
39 * @param int $user_id User whose session to manage.
41 final public static function get_instance( $user_id ) {
43 * Filter the session token manager used.
47 * @param string $session Name of class to use as the manager.
48 * Default 'WP_User_Meta_Session_Tokens'.
50 $manager = apply_filters( 'session_token_manager', 'WP_User_Meta_Session_Tokens' );
51 return new $manager( $user_id );
55 * Hashes a session token for storage.
60 * @param string $token Session token to hash.
61 * @return string A hash of the session token (a verifier).
63 final private function hash_token( $token ) {
64 // If ext/hash is not present, use sha1() instead.
65 if ( function_exists( 'hash' ) ) {
66 return hash( 'sha256', $token );
68 return sha1( $token );
73 * Get a user's session.
78 * @param string $token Session token
79 * @return array User session
81 final public function get( $token ) {
82 $verifier = $this->hash_token( $token );
83 return $this->get_session( $verifier );
87 * Validate a user's session token as authentic.
89 * Checks that the given token is present and hasn't expired.
94 * @param string $token Token to verify.
95 * @return bool Whether the token is valid for the user.
97 final public function verify( $token ) {
98 $verifier = $this->hash_token( $token );
99 return (bool) $this->get_session( $verifier );
103 * Generate a session token and attach session information to it.
105 * A session token is a long, random string. It is used in a cookie
106 * link that cookie to an expiration time and to ensure the cookie
107 * becomes invalidated upon logout.
109 * This function generates a token and stores it with the associated
110 * expiration time (and potentially other session information via the
111 * `attach_session_information` filter).
116 * @param int $expiration Session expiration timestamp.
117 * @return string Session token.
119 final public function create( $expiration ) {
121 * Filter the information attached to the newly created session.
123 * Could be used in the future to attach information such as
124 * IP address or user agent to a session.
128 * @param array $session Array of extra data.
129 * @param int $user_id User ID.
131 $session = apply_filters( 'attach_session_information', array(), $this->user_id );
132 $session['expiration'] = $expiration;
134 $token = wp_generate_password( 43, false, false );
136 $this->update( $token, $session );
142 * Update a session token.
147 * @param string $token Session token to update.
148 * @param array $session Session information.
150 final public function update( $token, $session ) {
151 $verifier = $this->hash_token( $token );
152 $this->update_session( $verifier, $session );
156 * Destroy a session token.
161 * @param string $token Session token to destroy.
163 final public function destroy( $token ) {
164 $verifier = $this->hash_token( $token );
165 $this->update_session( $verifier, null );
169 * Destroy all session tokens for this user,
170 * except a single token, presumably the one in use.
175 * @param string $token_to_keep Session token to keep.
177 final public function destroy_others( $token_to_keep ) {
178 $verifier = $this->hash_token( $token_to_keep );
179 $session = $this->get_session( $verifier );
181 $this->destroy_other_sessions( $verifier );
183 $this->destroy_all_sessions();
188 * Determine whether a session token is still valid,
189 * based on expiration.
194 * @param array $session Session to check.
195 * @return bool Whether session is valid.
197 final protected function is_still_valid( $session ) {
198 return $session['expiration'] >= time();
202 * Destroy all session tokens for a user.
207 final public function destroy_all() {
208 $this->destroy_all_sessions();
212 * Destroy all session tokens for all users.
218 final public static function destroy_all_for_all_users() {
219 $manager = apply_filters( 'session_token_manager', 'WP_User_Meta_Session_Tokens' );
220 call_user_func( array( $manager, 'drop_sessions' ) );
224 * Retrieve all sessions of a user.
229 * @return array Sessions of a user.
231 final public function get_all() {
232 return array_values( $this->get_sessions() );
236 * This method should retrieve all sessions of a user, keyed by verifier.
241 * @return array Sessions of a user, keyed by verifier.
243 abstract protected function get_sessions();
246 * This method should look up a session by its verifier (token hash).
251 * @param string $verifier Verifier of the session to retrieve.
252 * @return array|null The session, or null if it does not exist.
254 abstract protected function get_session( $verifier );
257 * This method should update a session by its verifier.
259 * Omitting the second argument should destroy the session.
264 * @param string $verifier Verifier of the session to update.
266 abstract protected function update_session( $verifier, $session = null );
269 * This method should destroy all session tokens for this user,
270 * except a single session passed.
275 * @param string $verifier Verifier of the session to keep.
277 abstract protected function destroy_other_sessions( $verifier );
280 * This method should destroy all sessions for a user.
285 abstract protected function destroy_all_sessions();
288 * This static method should destroy all session tokens for all users.
294 public static function drop_sessions() {}
298 * Meta-based user sessions token manager.
302 class WP_User_Meta_Session_Tokens extends WP_Session_Tokens {
305 * Get all sessions of a user.
310 * @return array Sessions of a user.
312 protected function get_sessions() {
313 $sessions = get_user_meta( $this->user_id, 'session_tokens', true );
315 if ( ! is_array( $sessions ) ) {
319 $sessions = array_map( array( $this, 'prepare_session' ), $sessions );
320 return array_filter( $sessions, array( $this, 'is_still_valid' ) );
324 * Converts an expiration to an array of session information.
326 * @param mixed $session Session or expiration.
327 * @return array Session.
329 protected function prepare_session( $session ) {
330 if ( is_int( $session ) ) {
331 return array( 'expiration' => $session );
338 * Retrieve a session by its verifier (token hash).
343 * @param string $verifier Verifier of the session to retrieve.
344 * @return array|null The session, or null if it does not exist
346 protected function get_session( $verifier ) {
347 $sessions = $this->get_sessions();
349 if ( isset( $sessions[ $verifier ] ) ) {
350 return $sessions[ $verifier ];
357 * Update a session by its verifier.
362 * @param string $verifier Verifier of the session to update.
363 * @param array $session Optional. Session. Omitting this argument destroys the session.
365 protected function update_session( $verifier, $session = null ) {
366 $sessions = $this->get_sessions();
369 $sessions[ $verifier ] = $session;
371 unset( $sessions[ $verifier ] );
374 $this->update_sessions( $sessions );
378 * Update a user's sessions in the usermeta table.
383 * @param array $sessions Sessions.
385 protected function update_sessions( $sessions ) {
386 if ( ! has_filter( 'attach_session_information' ) ) {
387 $sessions = wp_list_pluck( $sessions, 'expiration' );
391 update_user_meta( $this->user_id, 'session_tokens', $sessions );
393 delete_user_meta( $this->user_id, 'session_tokens' );
398 * Destroy all session tokens for a user, except a single session passed.
403 * @param string $verifier Verifier of the session to keep.
405 protected function destroy_other_sessions( $verifier ) {
406 $session = $this->get_session( $verifier );
407 $this->update_sessions( array( $verifier => $session ) );
411 * Destroy all session tokens for a user.
416 protected function destroy_all_sessions() {
417 $this->update_sessions( array() );
421 * Destroy all session tokens for all users.
427 public static function drop_sessions() {
428 delete_metadata( 'user', false, 'session_tokens', false, true );