WordPress 4.2
[autoinstalls/wordpress.git] / wp-includes / session.php
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 }