wp/wp-includes/session.php
changeset 5 5e2f62d02dcd
child 7 cf61fcea0001
equal deleted inserted replaced
4:346c88efed21 5:5e2f62d02dcd
       
     1 <?php
       
     2 /**
       
     3  * Abstract class for managing user session tokens.
       
     4  *
       
     5  * @since 4.0.0
       
     6  */
       
     7 abstract class WP_Session_Tokens {
       
     8 
       
     9 	/**
       
    10 	 * User ID.
       
    11 	 *
       
    12 	 * @since 4.0.0
       
    13 	 * @access protected
       
    14 	 * @var int User ID.
       
    15 	 */
       
    16 	protected $user_id;
       
    17 
       
    18 	/**
       
    19 	 * Protected constructor.
       
    20 	 *
       
    21 	 * @since 4.0.0
       
    22 	 *
       
    23 	 * @param int $user_id User whose session to manage.
       
    24 	 */
       
    25 	protected function __construct( $user_id ) {
       
    26 		$this->user_id = $user_id;
       
    27 	}
       
    28 
       
    29 	/**
       
    30 	 * Get a session token manager instance for a user.
       
    31 	 *
       
    32 	 * This method contains a filter that allows a plugin to swap out
       
    33 	 * the session manager for a subclass of WP_Session_Tokens.
       
    34 	 *
       
    35 	 * @since 4.0.0
       
    36 	 * @access public
       
    37 	 * @static
       
    38 	 *
       
    39 	 * @param int $user_id User whose session to manage.
       
    40 	 */
       
    41 	final public static function get_instance( $user_id ) {
       
    42 		/**
       
    43 		 * Filter the session token manager used.
       
    44 		 *
       
    45 		 * @since 4.0.0
       
    46 		 *
       
    47 		 * @param string $session Name of class to use as the manager.
       
    48 		 *                        Default 'WP_User_Meta_Session_Tokens'.
       
    49 		 */
       
    50 		$manager = apply_filters( 'session_token_manager', 'WP_User_Meta_Session_Tokens' );
       
    51 		return new $manager( $user_id );
       
    52 	}
       
    53 
       
    54 	/**
       
    55 	 * Hashes a session token for storage.
       
    56 	 *
       
    57 	 * @since 4.0.0
       
    58 	 * @access private
       
    59 	 *
       
    60 	 * @param string $token Session token to hash.
       
    61 	 * @return string A hash of the session token (a verifier).
       
    62 	 */
       
    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 );
       
    67 		} else {
       
    68 			return sha1( $token );
       
    69 		}
       
    70 	}
       
    71 
       
    72 	/**
       
    73 	 * Get a user's session.
       
    74 	 *
       
    75 	 * @since 4.0.0
       
    76 	 * @access public
       
    77 	 *
       
    78 	 * @param string $token Session token
       
    79 	 * @return array User session
       
    80 	 */
       
    81 	final public function get( $token ) {
       
    82 		$verifier = $this->hash_token( $token );
       
    83 		return $this->get_session( $verifier );
       
    84 	}
       
    85 
       
    86 	/**
       
    87 	 * Validate a user's session token as authentic.
       
    88 	 *
       
    89 	 * Checks that the given token is present and hasn't expired.
       
    90 	 *
       
    91 	 * @since 4.0.0
       
    92 	 * @access public
       
    93 	 *
       
    94 	 * @param string $token Token to verify.
       
    95 	 * @return bool Whether the token is valid for the user.
       
    96 	 */
       
    97 	final public function verify( $token ) {
       
    98 		$verifier = $this->hash_token( $token );
       
    99 		return (bool) $this->get_session( $verifier );
       
   100 	}
       
   101 
       
   102 	/**
       
   103 	 * Generate a session token and attach session information to it.
       
   104 	 *
       
   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.
       
   108 	 *
       
   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).
       
   112 	 *
       
   113 	 * @since 4.0.0
       
   114 	 * @access public
       
   115 	 *
       
   116 	 * @param int $expiration Session expiration timestamp.
       
   117 	 * @return string Session token.
       
   118 	 */
       
   119 	final public function create( $expiration ) {
       
   120 		/**
       
   121 		 * Filter the information attached to the newly created session.
       
   122 		 *
       
   123 		 * Could be used in the future to attach information such as
       
   124 		 * IP address or user agent to a session.
       
   125 		 *
       
   126 		 * @since 4.0.0
       
   127 		 *
       
   128 		 * @param array $session Array of extra data.
       
   129 		 * @param int   $user_id User ID.
       
   130 		 */
       
   131 		$session = apply_filters( 'attach_session_information', array(), $this->user_id );
       
   132 		$session['expiration'] = $expiration;
       
   133 
       
   134 		// IP address.
       
   135 		if ( !empty( $_SERVER['REMOTE_ADDR'] ) ) {
       
   136 			$session['ip'] = $_SERVER['REMOTE_ADDR'];
       
   137 		}
       
   138 
       
   139 		// User-agent.
       
   140 		if ( ! empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
       
   141 			$session['ua'] = wp_unslash( $_SERVER['HTTP_USER_AGENT'] );
       
   142 		}
       
   143 
       
   144 		// Timestamp
       
   145 		$session['login'] = time();
       
   146 
       
   147 		$token = wp_generate_password( 43, false, false );
       
   148 
       
   149 		$this->update( $token, $session );
       
   150 
       
   151 		return $token;
       
   152 	}
       
   153 
       
   154 	/**
       
   155 	 * Update a session token.
       
   156 	 *
       
   157 	 * @since 4.0.0
       
   158 	 * @access public
       
   159 	 *
       
   160 	 * @param string $token Session token to update.
       
   161 	 * @param array  $session Session information.
       
   162 	 */
       
   163 	final public function update( $token, $session ) {
       
   164 		$verifier = $this->hash_token( $token );
       
   165 		$this->update_session( $verifier, $session );
       
   166 	}
       
   167 
       
   168 	/**
       
   169 	 * Destroy a session token.
       
   170 	 *
       
   171 	 * @since 4.0.0
       
   172 	 * @access public
       
   173 	 *
       
   174 	 * @param string $token Session token to destroy.
       
   175 	 */
       
   176 	final public function destroy( $token ) {
       
   177 		$verifier = $this->hash_token( $token );
       
   178 		$this->update_session( $verifier, null );
       
   179 	}
       
   180 
       
   181 	/**
       
   182 	 * Destroy all session tokens for this user,
       
   183 	 * except a single token, presumably the one in use.
       
   184 	 *
       
   185 	 * @since 4.0.0
       
   186 	 * @access public
       
   187 	 *
       
   188 	 * @param string $token_to_keep Session token to keep.
       
   189 	 */
       
   190 	final public function destroy_others( $token_to_keep ) {
       
   191 		$verifier = $this->hash_token( $token_to_keep );
       
   192 		$session = $this->get_session( $verifier );
       
   193 		if ( $session ) {
       
   194 			$this->destroy_other_sessions( $verifier );
       
   195 		} else {
       
   196 			$this->destroy_all_sessions();
       
   197 		}
       
   198 	}
       
   199 
       
   200 	/**
       
   201 	 * Determine whether a session token is still valid,
       
   202 	 * based on expiration.
       
   203 	 *
       
   204 	 * @since 4.0.0
       
   205 	 * @access protected
       
   206 	 *
       
   207 	 * @param array $session Session to check.
       
   208 	 * @return bool Whether session is valid.
       
   209 	 */
       
   210 	final protected function is_still_valid( $session ) {
       
   211 		return $session['expiration'] >= time();
       
   212 	}
       
   213 
       
   214 	/**
       
   215 	 * Destroy all session tokens for a user.
       
   216 	 *
       
   217 	 * @since 4.0.0
       
   218 	 * @access public
       
   219 	 */
       
   220 	final public function destroy_all() {
       
   221 		$this->destroy_all_sessions();
       
   222 	}
       
   223 
       
   224 	/**
       
   225 	 * Destroy all session tokens for all users.
       
   226 	 *
       
   227 	 * @since 4.0.0
       
   228 	 * @access public
       
   229 	 * @static
       
   230 	 */
       
   231 	final public static function destroy_all_for_all_users() {
       
   232 		$manager = apply_filters( 'session_token_manager', 'WP_User_Meta_Session_Tokens' );
       
   233 		call_user_func( array( $manager, 'drop_sessions' ) );
       
   234 	}
       
   235 
       
   236 	/**
       
   237 	 * Retrieve all sessions of a user.
       
   238 	 *
       
   239 	 * @since 4.0.0
       
   240 	 * @access public
       
   241 	 *
       
   242 	 * @return array Sessions of a user.
       
   243 	 */
       
   244 	final public function get_all() {
       
   245 		return array_values( $this->get_sessions() );
       
   246 	}
       
   247 
       
   248 	/**
       
   249 	 * This method should retrieve all sessions of a user, keyed by verifier.
       
   250 	 *
       
   251 	 * @since 4.0.0
       
   252 	 * @access protected
       
   253 	 *
       
   254 	 * @return array Sessions of a user, keyed by verifier.
       
   255 	 */
       
   256 	abstract protected function get_sessions();
       
   257 
       
   258 	/**
       
   259 	 * This method should look up a session by its verifier (token hash).
       
   260 	 *
       
   261 	 * @since 4.0.0
       
   262 	 * @access protected
       
   263 	 *
       
   264 	 * @param string $verifier Verifier of the session to retrieve.
       
   265 	 * @return array|null The session, or null if it does not exist.
       
   266 	 */
       
   267 	abstract protected function get_session( $verifier );
       
   268 
       
   269 	/**
       
   270 	 * This method should update a session by its verifier.
       
   271 	 *
       
   272 	 * Omitting the second argument should destroy the session.
       
   273 	 *
       
   274 	 * @since 4.0.0
       
   275 	 * @access protected
       
   276 	 *
       
   277 	 * @param string $verifier Verifier of the session to update.
       
   278 	 */
       
   279 	abstract protected function update_session( $verifier, $session = null );
       
   280 
       
   281 	/**
       
   282 	 * This method should destroy all session tokens for this user,
       
   283 	 * except a single session passed.
       
   284 	 *
       
   285 	 * @since 4.0.0
       
   286 	 * @access protected
       
   287 	 *
       
   288 	 * @param string $verifier Verifier of the session to keep.
       
   289 	 */
       
   290 	abstract protected function destroy_other_sessions( $verifier );
       
   291 
       
   292 	/**
       
   293 	 * This method should destroy all sessions for a user.
       
   294 	 *
       
   295 	 * @since 4.0.0
       
   296 	 * @access protected
       
   297 	 */
       
   298 	abstract protected function destroy_all_sessions();
       
   299 
       
   300 	/**
       
   301 	 * This static method should destroy all session tokens for all users.
       
   302 	 *
       
   303 	 * @since 4.0.0
       
   304 	 * @access public
       
   305 	 * @static
       
   306 	 */
       
   307 	public static function drop_sessions() {}
       
   308 }
       
   309 
       
   310 /**
       
   311  * Meta-based user sessions token manager.
       
   312  *
       
   313  * @since 4.0.0
       
   314  */
       
   315 class WP_User_Meta_Session_Tokens extends WP_Session_Tokens {
       
   316 
       
   317 	/**
       
   318 	 * Get all sessions of a user.
       
   319 	 *
       
   320 	 * @since 4.0.0
       
   321 	 * @access protected
       
   322 	 *
       
   323 	 * @return array Sessions of a user.
       
   324 	 */
       
   325 	protected function get_sessions() {
       
   326 		$sessions = get_user_meta( $this->user_id, 'session_tokens', true );
       
   327 
       
   328 		if ( ! is_array( $sessions ) ) {
       
   329 			return array();
       
   330 		}
       
   331 
       
   332 		$sessions = array_map( array( $this, 'prepare_session' ), $sessions );
       
   333 		return array_filter( $sessions, array( $this, 'is_still_valid' ) );
       
   334 	}
       
   335 
       
   336 	/**
       
   337 	 * Converts an expiration to an array of session information.
       
   338 	 *
       
   339 	 * @param mixed $session Session or expiration.
       
   340 	 * @return array Session.
       
   341 	 */
       
   342 	protected function prepare_session( $session ) {
       
   343 		if ( is_int( $session ) ) {
       
   344 			return array( 'expiration' => $session );
       
   345 		}
       
   346 
       
   347 		return $session;
       
   348 	}
       
   349 
       
   350 	/**
       
   351 	 * Retrieve a session by its verifier (token hash).
       
   352 	 *
       
   353 	 * @since 4.0.0
       
   354 	 * @access protected
       
   355 	 *
       
   356 	 * @param string $verifier Verifier of the session to retrieve.
       
   357 	 * @return array|null The session, or null if it does not exist
       
   358 	 */
       
   359 	protected function get_session( $verifier ) {
       
   360 		$sessions = $this->get_sessions();
       
   361 
       
   362 		if ( isset( $sessions[ $verifier ] ) ) {
       
   363 			return $sessions[ $verifier ];
       
   364 		}
       
   365 
       
   366 		return null;
       
   367 	}
       
   368 
       
   369 	/**
       
   370 	 * Update a session by its verifier.
       
   371 	 *
       
   372 	 * @since 4.0.0
       
   373 	 * @access protected
       
   374 	 *
       
   375 	 * @param string $verifier Verifier of the session to update.
       
   376 	 * @param array  $session  Optional. Session. Omitting this argument destroys the session.
       
   377 	 */
       
   378 	protected function update_session( $verifier, $session = null ) {
       
   379 		$sessions = $this->get_sessions();
       
   380 
       
   381 		if ( $session ) {
       
   382 			$sessions[ $verifier ] = $session;
       
   383 		} else {
       
   384 			unset( $sessions[ $verifier ] );
       
   385 		}
       
   386 
       
   387 		$this->update_sessions( $sessions );
       
   388 	}
       
   389 
       
   390 	/**
       
   391 	 * Update a user's sessions in the usermeta table.
       
   392 	 *
       
   393 	 * @since 4.0.0
       
   394 	 * @access protected
       
   395 	 *
       
   396 	 * @param array $sessions Sessions.
       
   397 	 */
       
   398 	protected function update_sessions( $sessions ) {
       
   399 		if ( $sessions ) {
       
   400 			update_user_meta( $this->user_id, 'session_tokens', $sessions );
       
   401 		} else {
       
   402 			delete_user_meta( $this->user_id, 'session_tokens' );
       
   403 		}
       
   404 	}
       
   405 
       
   406 	/**
       
   407 	 * Destroy all session tokens for a user, except a single session passed.
       
   408 	 *
       
   409 	 * @since 4.0.0
       
   410 	 * @access protected
       
   411 	 *
       
   412 	 * @param string $verifier Verifier of the session to keep.
       
   413 	 */
       
   414 	protected function destroy_other_sessions( $verifier ) {
       
   415 		$session = $this->get_session( $verifier );
       
   416 		$this->update_sessions( array( $verifier => $session ) );
       
   417 	}
       
   418 
       
   419 	/**
       
   420 	 * Destroy all session tokens for a user.
       
   421 	 *
       
   422 	 * @since 4.0.0
       
   423 	 * @access protected
       
   424 	 */
       
   425 	protected function destroy_all_sessions() {
       
   426 		$this->update_sessions( array() );
       
   427 	}
       
   428 
       
   429 	/**
       
   430 	 * Destroy all session tokens for all users.
       
   431 	 *
       
   432 	 * @since 4.0.0
       
   433 	 * @access public
       
   434 	 * @static
       
   435 	 */
       
   436 	public static function drop_sessions() {
       
   437 		delete_metadata( 'user', 0, 'session_tokens', false, true );
       
   438 	}
       
   439 }