| 2130 | |
| 2131 | /** |
| 2132 | * Class for managing a WordPress user's session tokens. |
| 2133 | * |
| 2134 | * @since 4.0.0 |
| 2135 | */ |
| 2136 | class 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 | */ |
| 2348 | function 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 | */ |
| 2358 | function 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 | */ |
| 2371 | function 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 | */ |
| 2384 | function wp_destroy_all_sessions() { |
| 2385 | $manager = WP_Session_Tokens::get_instance( get_current_user_id() ); |
| 2386 | $manager->destroy_all_sessions(); |
| 2387 | } |