WordPress 4.5
[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          * @param array  $session  Optional. Session. Omitting this argument destroys the session.
279          */
280         abstract protected function update_session( $verifier, $session = null );
281
282         /**
283          * This method should destroy all session tokens for this user,
284          * except a single session passed.
285          *
286          * @since 4.0.0
287          * @access protected
288          *
289          * @param string $verifier Verifier of the session to keep.
290          */
291         abstract protected function destroy_other_sessions( $verifier );
292
293         /**
294          * This method should destroy all sessions for a user.
295          *
296          * @since 4.0.0
297          * @access protected
298          */
299         abstract protected function destroy_all_sessions();
300
301         /**
302          * This static method should destroy all session tokens for all users.
303          *
304          * @since 4.0.0
305          * @access public
306          * @static
307          */
308         public static function drop_sessions() {}
309 }
310
311 /**
312  * Meta-based user sessions token manager.
313  *
314  * @since 4.0.0
315  */
316 class WP_User_Meta_Session_Tokens extends WP_Session_Tokens {
317
318         /**
319          * Get all sessions of a user.
320          *
321          * @since 4.0.0
322          * @access protected
323          *
324          * @return array Sessions of a user.
325          */
326         protected function get_sessions() {
327                 $sessions = get_user_meta( $this->user_id, 'session_tokens', true );
328
329                 if ( ! is_array( $sessions ) ) {
330                         return array();
331                 }
332
333                 $sessions = array_map( array( $this, 'prepare_session' ), $sessions );
334                 return array_filter( $sessions, array( $this, 'is_still_valid' ) );
335         }
336
337         /**
338          * Converts an expiration to an array of session information.
339          *
340          * @param mixed $session Session or expiration.
341          * @return array Session.
342          */
343         protected function prepare_session( $session ) {
344                 if ( is_int( $session ) ) {
345                         return array( 'expiration' => $session );
346                 }
347
348                 return $session;
349         }
350
351         /**
352          * Retrieve a session by its verifier (token hash).
353          *
354          * @since 4.0.0
355          * @access protected
356          *
357          * @param string $verifier Verifier of the session to retrieve.
358          * @return array|null The session, or null if it does not exist
359          */
360         protected function get_session( $verifier ) {
361                 $sessions = $this->get_sessions();
362
363                 if ( isset( $sessions[ $verifier ] ) ) {
364                         return $sessions[ $verifier ];
365                 }
366
367                 return null;
368         }
369
370         /**
371          * Update a session by its verifier.
372          *
373          * @since 4.0.0
374          * @access protected
375          *
376          * @param string $verifier Verifier of the session to update.
377          * @param array  $session  Optional. Session. Omitting this argument destroys the session.
378          */
379         protected function update_session( $verifier, $session = null ) {
380                 $sessions = $this->get_sessions();
381
382                 if ( $session ) {
383                         $sessions[ $verifier ] = $session;
384                 } else {
385                         unset( $sessions[ $verifier ] );
386                 }
387
388                 $this->update_sessions( $sessions );
389         }
390
391         /**
392          * Update a user's sessions in the usermeta table.
393          *
394          * @since 4.0.0
395          * @access protected
396          *
397          * @param array $sessions Sessions.
398          */
399         protected function update_sessions( $sessions ) {
400                 if ( $sessions ) {
401                         update_user_meta( $this->user_id, 'session_tokens', $sessions );
402                 } else {
403                         delete_user_meta( $this->user_id, 'session_tokens' );
404                 }
405         }
406
407         /**
408          * Destroy all session tokens for a user, except a single session passed.
409          *
410          * @since 4.0.0
411          * @access protected
412          *
413          * @param string $verifier Verifier of the session to keep.
414          */
415         protected function destroy_other_sessions( $verifier ) {
416                 $session = $this->get_session( $verifier );
417                 $this->update_sessions( array( $verifier => $session ) );
418         }
419
420         /**
421          * Destroy all session tokens for a user.
422          *
423          * @since 4.0.0
424          * @access protected
425          */
426         protected function destroy_all_sessions() {
427                 $this->update_sessions( array() );
428         }
429
430         /**
431          * Destroy all session tokens for all users.
432          *
433          * @since 4.0.0
434          * @access public
435          * @static
436          */
437         public static function drop_sessions() {
438                 delete_metadata( 'user', 0, 'session_tokens', false, true );
439         }
440 }