WordPress.org

Make WordPress Core

Ticket #20276: 20276.5.diff

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