WordPress.org

Make WordPress Core

Ticket #20276: 20276.6.diff

File 20276.6.diff, 11.7 KB (added by nacin, 5 years ago)
  • src/wp-includes/pluggable.php

     
    586586 * @since 2.5.0
    587587 */
    588588function wp_logout() {
     589        wp_destroy_current_session();
    589590        wp_clear_auth_cookie();
    590591
    591592        /**
     
    631632        $scheme = $cookie_elements['scheme'];
    632633        $username = $cookie_elements['username'];
    633634        $hmac = $cookie_elements['hmac'];
     635        $token = $cookie_elements['token'];
    634636        $expired = $expiration = $cookie_elements['expiration'];
    635637
    636638        // Allow a grace period for POST and AJAX requests
     
    666668
    667669        $pass_frag = substr($user->user_pass, 8, 4);
    668670
    669         $key = wp_hash($username . $pass_frag . '|' . $expiration, $scheme);
    670         $hash = hash_hmac('md5', $username . '|' . $expiration, $key);
     671        $key = wp_hash( $username . '|' . $pass_frag . '|' . $expiration . '|' . $token, $scheme );
     672        $hash = hash_hmac( 'md5', $username . '|' . $expiration . '|' . $token, $key );
    671673
    672674        if ( hash_hmac( 'md5', $hmac, $key ) !== hash_hmac( 'md5', $hash, $key ) ) {
    673675                /**
     
    681683                return false;
    682684        }
    683685
    684         if ( $expiration < time() ) {// AJAX/POST grace period set above
     686        $manager = WP_Session_Tokens::get_instance( $user->ID );
     687        if ( ! $manager->verify_token( $token ) ) {
     688                do_action( 'auth_cookie_bad_session_token', $cookie_elements );
     689                return false;
     690        }
     691
     692        if ( $expiration < time() ) { // AJAX/POST grace period set above
    685693                $GLOBALS['login_grace_period'] = 1;
    686694        }
    687695
     
    708716 * @param int $user_id User ID
    709717 * @param int $expiration Cookie expiration in seconds
    710718 * @param string $scheme Optional. The cookie scheme to use: auth, secure_auth, or logged_in
    711  * @return string Authentication cookie contents
     719 * @param string $token User's session token to use for this cookie
     720 * @return string Authentication cookie contents. Empty string if user does not exist.
    712721 */
    713 function wp_generate_auth_cookie($user_id, $expiration, $scheme = 'auth') {
     722function wp_generate_auth_cookie( $user_id, $expiration, $scheme = 'auth', $token = '' ) {
    714723        $user = get_userdata($user_id);
     724        if ( ! $user ) {
     725                return '';
     726        }
    715727
     728        if ( ! $token ) {
     729                $manager = WP_Session_Tokens::get_instance( $user_id );
     730                $token = $manager->create_token( $expiration );
     731        }
     732
    716733        $pass_frag = substr($user->user_pass, 8, 4);
    717734
    718         $key = wp_hash($user->user_login . $pass_frag . '|' . $expiration, $scheme);
    719         $hash = hash_hmac('md5', $user->user_login . '|' . $expiration, $key);
     735        $key = wp_hash( $user->user_login . '|' . $pass_frag . '|' . $expiration . '|' . $token, $scheme );
     736        $hash = hash_hmac( 'md5', $user->user_login . '|' . $expiration . '|' . $token, $key );
    720737
    721         $cookie = $user->user_login . '|' . $expiration . '|' . $hash;
     738        $cookie = $user->user_login . '|' . $expiration . '|' . $token . '|' . $hash;
    722739
    723740        /**
    724741         * Filter the authentication cookie.
     
    772789        }
    773790
    774791        $cookie_elements = explode('|', $cookie);
    775         if ( count($cookie_elements) != 3 )
     792        if ( count( $cookie_elements ) !== 4 ) {
    776793                return false;
     794        }
    777795
    778         list($username, $expiration, $hmac) = $cookie_elements;
     796        list( $username, $expiration, $token, $hmac ) = $cookie_elements;
    779797
    780         return compact('username', 'expiration', 'hmac', 'scheme');
     798        return compact( 'username', 'expiration', 'token', 'hmac', 'scheme' );
    781799}
    782800endif;
    783801
     
    793811 *
    794812 * @param int $user_id User ID
    795813 * @param bool $remember Whether to remember the user
     814 * @param mixed $secure  Whether the admin cookies should only be sent over HTTPS.
     815 *                       Default is_ssl().
    796816 */
    797817function wp_set_auth_cookie($user_id, $remember = false, $secure = '') {
    798818        if ( $remember ) {
     
    850870                $scheme = 'auth';
    851871        }
    852872
    853         $auth_cookie = wp_generate_auth_cookie($user_id, $expiration, $scheme);
    854         $logged_in_cookie = wp_generate_auth_cookie($user_id, $expiration, 'logged_in');
     873        $manager = WP_Session_Tokens::get_instance( $user_id );
     874        $token = $manager->create_token( $expiration );
    855875
     876        $auth_cookie = wp_generate_auth_cookie( $user_id, $expiration, $scheme, $token );
     877        $logged_in_cookie = wp_generate_auth_cookie( $user_id, $expiration, 'logged_in', $token );
     878
    856879        /**
    857880         * Fires immediately before the authentication cookie is set.
    858881         *
     
    16781701                $uid = apply_filters( 'nonce_user_logged_out', $uid, $action );
    16791702        }
    16801703
     1704        $token = wp_get_session_token();
    16811705        $i = wp_nonce_tick();
    16821706
    16831707        // Nonce generated 0-12 hours ago
    1684         if ( substr(wp_hash($i . $action . $uid, 'nonce'), -12, 10) === $nonce )
     1708        if ( $nonce === substr( wp_hash( $i . '|' . $action . '|' . $uid . '|' . $token, 'nonce'), -12, 10 ) ) {
    16851709                return 1;
     1710        }
     1711
    16861712        // Nonce generated 12-24 hours ago
    1687         if ( substr(wp_hash(($i - 1) . $action . $uid, 'nonce'), -12, 10) === $nonce )
     1713        if ( $nonce === substr( wp_hash( ( $i - 1 ) . '|' . $action . '|' . $uid . '|' . $token, 'nonce' ), -12, 10 ) ) {
    16881714                return 2;
     1715        }
     1716
    16891717        // Invalid nonce
    16901718        return false;
    16911719}
     
    17081736                $uid = apply_filters( 'nonce_user_logged_out', $uid, $action );
    17091737        }
    17101738
     1739        $token = wp_get_session_token();
    17111740        $i = wp_nonce_tick();
    17121741
    1713         return substr(wp_hash($i . $action . $uid, 'nonce'), -12, 10);
     1742        return substr( wp_hash( $i . '|' . $action . '|' . $uid . '|' . $token, 'nonce' ), -12, 10 );
    17141743}
    17151744endif;
    17161745
  • src/wp-includes/user.php

     
    21272127
    21282128        return $user_id;
    21292129}
     2130
     2131/**
     2132 * Class for managing a WordPress user's session tokens.
     2133 *
     2134 * @since 4.0.0
     2135 */
     2136class WP_Session_Tokens {
     2137
     2138        /**
     2139         * User ID.
     2140         *
     2141         * @since 4.0.0
     2142         *
     2143         * @var int User ID.
     2144         */
     2145        protected $user_id;
     2146
     2147        /**
     2148         * Protected constructor.
     2149         *
     2150         * @param int $user_id User whose session to manage.
     2151         */
     2152        protected function __construct( $user_id ) {
     2153                $this->user_id = $user_id;
     2154        }
     2155
     2156        /**
     2157         * Get a session token manager instance for a user.
     2158         *
     2159         * This method contains a filter that allows a plugin to swap out
     2160         * the session manager for a subclass of WP_Session_Tokens.
     2161         *
     2162         * @since 4.0.0
     2163         *
     2164         * @param int $user_id User whose session to manage.
     2165         */
     2166        final public static function get_instance( $user_id ) {
     2167                /**
     2168                 * Filter the session token manager used.
     2169                 *
     2170                 * @since 4.0.0
     2171                 *
     2172                 * @param string $session Name of class to use as the manager.
     2173                 *                        Default 'WP_Session_Tokens'.
     2174                 */
     2175                $manager = apply_filters( 'session_token_manager', 'WP_Session_Tokens' );
     2176                return new $manager( $user_id );
     2177        }
     2178
     2179        /**
     2180         * Validate a user's session token as authentic.
     2181         *
     2182         * Checks that the given token is present in the database and hasn't expired.
     2183         *
     2184         * @since 4.0.0
     2185         *
     2186         * @param string $token Token to verify.
     2187         * @return bool Whether the token is valid for the user.
     2188         */
     2189        final public function verify_token( $token ) {
     2190                $sessions = $this->get_sessions();
     2191                $verifier = hash( 'sha256', $token );
     2192                return isset( $sessions[ $verifier ] ) && $sessions[ $verifier ]['expiration'] >= time();
     2193        }
     2194
     2195        /**
     2196         * Generate a cookie session identification token.
     2197         *
     2198         * A session identification token is a long, random string. It is used to
     2199         * link a cookie to an expiration time and to ensure that cookies become
     2200         * invalidated upon logout. This function generates a token and stores it
     2201         * as user meta with the associated expiration time.
     2202         *
     2203         * Will also remove any expired tokens from the database.
     2204         *
     2205         * @since 4.0.0
     2206         *
     2207         * @param int $expiration Session expiration timestamp.
     2208         * @return string Session identification token.
     2209         */
     2210        final public function create_token( $expiration ) {
     2211                /**
     2212                 * Filter the information attached to the newly created session.
     2213                 *
     2214                 * Could be used in the future to attach information such as
     2215                 * IP address or user agent to a session.
     2216                 *
     2217                 * @since 4.0.0
     2218                 *
     2219                 * @param array $session Array of extra data.
     2220                 * @param int   $user_id User ID.
     2221                 */
     2222                $session = apply_filters( 'attach_session_information', array(), $this->user_id );
     2223                $session['expiration'] = $expiration;
     2224
     2225                $sessions = $this->get_sessions();
     2226                if ( ! $sessions ) {
     2227                        $sessions = array();
     2228                }
     2229
     2230                $token = wp_generate_password( 40, false, false );
     2231                $verifier = hash( 'sha256', $token );
     2232
     2233                $sessions[ $verifier ] = $session;
     2234
     2235                $this->update_sessions( $sessions );
     2236                return $token;
     2237        }
     2238
     2239        /**
     2240         * Remove a session token from the database.
     2241         *
     2242         * Will also remove any expired tokens from the database.
     2243         *
     2244         * @since 4.0.0
     2245         *
     2246         * @param string $token Token to destroy.
     2247         */
     2248        final public function destroy_session( $token ) {
     2249                $sessions = $this->get_sessions();
     2250                $verifier = hash( 'sha256', $token );
     2251                unset( $sessions[ $verifier ] );
     2252
     2253                $this->update_sessions( $sessions );
     2254        }
     2255
     2256        /**
     2257         * Remove all session tokens from the database for this user,
     2258         * except a single token, presumably the one in use.
     2259         *
     2260         * @since 4.0.0
     2261         *
     2262         * @param $token_to_keep Token to keep.
     2263         */
     2264        final public function destroy_other_sessions( $token_to_keep ) {
     2265                $sessions = $this->get_sessions();
     2266                $verifier = hash( 'sha256', $token_to_keep );
     2267                $sessions = array( $verifier => $sessions[ $verifier ] );
     2268
     2269                $this->update_sessions( $sessions );
     2270        }
     2271
     2272        /**
     2273         * Remove all session tokens from the database for this user.
     2274         *
     2275         * @since 4.0.0
     2276         */
     2277        final public function destroy_all_sessions() {
     2278                $this->update_sessions();
     2279        }
     2280
     2281        /**
     2282         * Invalidate all session tokens for all users.
     2283         *
     2284         * @since 4.0.0
     2285         */
     2286        public static function destroy_all_sessions_for_all_users() {
     2287                delete_metadata( 'user', false, 'session_tokens', false, true );
     2288        }
     2289
     2290        /**
     2291         * Get all sessions of a user.
     2292         *
     2293         * @since 4.0.0
     2294         *
     2295         * @return array
     2296         */
     2297        public function get_sessions() {
     2298                $sessions = get_user_meta( $this->user_id, 'session_tokens', true );
     2299
     2300                if ( ! is_array( $sessions ) ) {
     2301                        return array();
     2302                }
     2303
     2304                foreach ( $sessions as &$session ) {
     2305                        if ( is_int( $session ) ) {
     2306                                $session = array( 'expiration' => $session );
     2307                        }
     2308                }
     2309                return $sessions;
     2310        }
     2311
     2312        /**
     2313         * Update a user's sessions.
     2314         *
     2315         * @since 4.0.0
     2316         *
     2317         * @param array $sessions
     2318         */
     2319        protected function update_sessions( $sessions ) {
     2320                // Remove expired sessions.
     2321                $time = time();
     2322                $reduce = ! has_filter( 'attach_session_information' );
     2323                foreach ( $sessions as $verifier => $session ) {
     2324                        if ( $session['expiration'] < $time ) {
     2325                                unset( $sessions[ $verifier ] );
     2326                        }
     2327
     2328                        if ( $reduce ) {
     2329                                $sessions[ $verifier ] = $session['expiration'];
     2330                        }
     2331                }
     2332
     2333                if ( $sessions ) {
     2334                        update_user_meta( $this->user_id, 'session_tokens', $sessions );
     2335                } else {
     2336                        delete_user_meta( $this->user_id, 'session_tokens' );
     2337                }
     2338        }
     2339}
     2340
     2341/**
     2342 * Retrieve the current session token from the logged_in cookie.
     2343 *
     2344 * @since 4.0.0
     2345 *
     2346 * @return string
     2347 */
     2348function wp_get_session_token() {
     2349        $cookie = wp_parse_auth_cookie( '', 'logged_in' );
     2350        return ! empty( $cookie['token'] ) ? $cookie['token'] : '';
     2351}
     2352
     2353/**
     2354 * Remove the current session token from the database.
     2355 *
     2356 * @since 4.0.0
     2357 */
     2358function wp_destroy_current_session() {
     2359        $token = wp_get_session_token();
     2360        if ( $token ) {
     2361                $manager = WP_Session_Tokens::get_instance( get_current_user_id() );
     2362                $manager->destroy_session( $token );
     2363        }
     2364}
     2365
     2366/**
     2367 * Remove all but the current session token for the current user for the database.
     2368 *
     2369 * @since 4.0.0
     2370 */
     2371function wp_destroy_other_sessions() {
     2372        $token = wp_get_session_token();
     2373        if ( $token ) {
     2374                $manager = WP_Session_Tokens::get_instance( get_current_user_id() );
     2375                $manager->destroy_other_sessions( $token );
     2376        }
     2377}
     2378
     2379/**
     2380 * Remove all session tokens for the current user from the database.
     2381 *
     2382 * @since 4.0.0
     2383 */
     2384function wp_destroy_all_sessions() {
     2385        $manager = WP_Session_Tokens::get_instance( get_current_user_id() );
     2386        $manager->destroy_all_sessions();
     2387}