MediaWiki REL1_31
Session.php
Go to the documentation of this file.
1<?php
24namespace MediaWiki\Session;
25
26use Psr\Log\LoggerInterface;
27use User;
28use WebRequest;
29
48final class Session implements \Countable, \Iterator, \ArrayAccess {
50 private static $encryptionAlgorithm = null;
51
53 private $backend;
54
56 private $index;
57
59 private $logger;
60
66 public function __construct( SessionBackend $backend, $index, LoggerInterface $logger ) {
67 $this->backend = $backend;
68 $this->index = $index;
69 $this->logger = $logger;
70 }
71
72 public function __destruct() {
73 $this->backend->deregisterSession( $this->index );
74 }
75
80 public function getId() {
81 return $this->backend->getId();
82 }
83
89 public function getSessionId() {
90 return $this->backend->getSessionId();
91 }
92
97 public function resetId() {
98 return $this->backend->resetId();
99 }
100
105 public function getProvider() {
106 return $this->backend->getProvider();
107 }
108
116 public function isPersistent() {
117 return $this->backend->isPersistent();
118 }
119
126 public function persist() {
127 $this->backend->persist();
128 }
129
138 public function unpersist() {
139 $this->backend->unpersist();
140 }
141
147 public function shouldRememberUser() {
148 return $this->backend->shouldRememberUser();
149 }
150
156 public function setRememberUser( $remember ) {
157 $this->backend->setRememberUser( $remember );
158 }
159
164 public function getRequest() {
165 return $this->backend->getRequest( $this->index );
166 }
167
172 public function getUser() {
173 return $this->backend->getUser();
174 }
175
180 public function getAllowedUserRights() {
181 return $this->backend->getAllowedUserRights();
182 }
183
188 public function canSetUser() {
189 return $this->backend->canSetUser();
190 }
191
199 public function setUser( $user ) {
200 $this->backend->setUser( $user );
201 }
202
207 public function suggestLoginUsername() {
208 return $this->backend->suggestLoginUsername( $this->index );
209 }
210
218 public function shouldForceHTTPS() {
219 return $this->backend->shouldForceHTTPS();
220 }
221
229 public function setForceHTTPS( $force ) {
230 $this->backend->setForceHTTPS( $force );
231 }
232
237 public function getLoggedOutTimestamp() {
238 return $this->backend->getLoggedOutTimestamp();
239 }
240
245 public function setLoggedOutTimestamp( $ts ) {
246 $this->backend->setLoggedOutTimestamp( $ts );
247 }
248
254 public function getProviderMetadata() {
255 return $this->backend->getProviderMetadata();
256 }
257
261 public function clear() {
262 $data = &$this->backend->getData();
263 if ( $data ) {
264 $data = [];
265 $this->backend->dirty();
266 }
267 if ( $this->backend->canSetUser() ) {
268 $this->backend->setUser( new User );
269 }
270 $this->backend->save();
271 }
272
279 public function renew() {
280 $this->backend->renew();
281 }
282
293 $request->setSessionId( $this->backend->getSessionId() );
294 return $this->backend->getSession( $request );
295 }
296
303 public function get( $key, $default = null ) {
304 $data = &$this->backend->getData();
305 return array_key_exists( $key, $data ) ? $data[$key] : $default;
306 }
307
314 public function exists( $key ) {
315 $data = &$this->backend->getData();
316 return array_key_exists( $key, $data );
317 }
318
324 public function set( $key, $value ) {
325 $data = &$this->backend->getData();
326 if ( !array_key_exists( $key, $data ) || $data[$key] !== $value ) {
327 $data[$key] = $value;
328 $this->backend->dirty();
329 }
330 }
331
336 public function remove( $key ) {
337 $data = &$this->backend->getData();
338 if ( array_key_exists( $key, $data ) ) {
339 unset( $data[$key] );
340 $this->backend->dirty();
341 }
342 }
343
354 public function getToken( $salt = '', $key = 'default' ) {
355 $new = false;
356 $secrets = $this->get( 'wsTokenSecrets' );
357 if ( !is_array( $secrets ) ) {
358 $secrets = [];
359 }
360 if ( isset( $secrets[$key] ) && is_string( $secrets[$key] ) ) {
361 $secret = $secrets[$key];
362 } else {
363 $secret = \MWCryptRand::generateHex( 32 );
364 $secrets[$key] = $secret;
365 $this->set( 'wsTokenSecrets', $secrets );
366 $new = true;
367 }
368 if ( is_array( $salt ) ) {
369 $salt = implode( '|', $salt );
370 }
371 return new Token( $secret, (string)$salt, $new );
372 }
373
381 public function resetToken( $key = 'default' ) {
382 $secrets = $this->get( 'wsTokenSecrets' );
383 if ( is_array( $secrets ) && isset( $secrets[$key] ) ) {
384 unset( $secrets[$key] );
385 $this->set( 'wsTokenSecrets', $secrets );
386 }
387 }
388
392 public function resetAllTokens() {
393 $this->remove( 'wsTokenSecrets' );
394 }
395
400 private function getSecretKeys() {
402
403 $wikiSecret = $wgSessionSecret ?: $wgSecretKey;
404 $userSecret = $this->get( 'wsSessionSecret', null );
405 if ( $userSecret === null ) {
406 $userSecret = \MWCryptRand::generateHex( 32 );
407 $this->set( 'wsSessionSecret', $userSecret );
408 }
409 $iterations = $this->get( 'wsSessionPbkdf2Iterations', null );
410 if ( $iterations === null ) {
411 $iterations = $wgSessionPbkdf2Iterations;
412 $this->set( 'wsSessionPbkdf2Iterations', $iterations );
413 }
414
415 $keymats = hash_pbkdf2( 'sha256', $wikiSecret, $userSecret, $iterations, 64, true );
416 return [
417 substr( $keymats, 0, 32 ),
418 substr( $keymats, 32, 32 ),
419 ];
420 }
421
426 private static function getEncryptionAlgorithm() {
428
429 if ( self::$encryptionAlgorithm === null ) {
430 if ( function_exists( 'openssl_encrypt' ) ) {
431 $methods = openssl_get_cipher_methods();
432 if ( in_array( 'aes-256-ctr', $methods, true ) ) {
433 self::$encryptionAlgorithm = [ 'openssl', 'aes-256-ctr' ];
435 }
436 if ( in_array( 'aes-256-cbc', $methods, true ) ) {
437 self::$encryptionAlgorithm = [ 'openssl', 'aes-256-cbc' ];
439 }
440 }
441
442 if ( function_exists( 'mcrypt_encrypt' )
443 && in_array( 'rijndael-128', mcrypt_list_algorithms(), true )
444 ) {
445 $modes = mcrypt_list_modes();
446 if ( in_array( 'ctr', $modes, true ) ) {
447 self::$encryptionAlgorithm = [ 'mcrypt', 'rijndael-128', 'ctr' ];
449 }
450 if ( in_array( 'cbc', $modes, true ) ) {
451 self::$encryptionAlgorithm = [ 'mcrypt', 'rijndael-128', 'cbc' ];
453 }
454 }
455
457 // @todo: import a pure-PHP library for AES instead of this
458 self::$encryptionAlgorithm = [ 'insecure' ];
460 }
461
462 throw new \BadMethodCallException(
463 'Encryption is not available. You really should install the PHP OpenSSL extension, ' .
464 'or failing that the mcrypt extension. But if you really can\'t and you\'re willing ' .
465 'to accept insecure storage of sensitive session data, set ' .
466 '$wgSessionInsecureSecrets = true in LocalSettings.php to make this exception go away.'
467 );
468 }
469
471 }
472
481 public function setSecret( $key, $value ) {
482 list( $encKey, $hmacKey ) = $this->getSecretKeys();
484
485 // The code for encryption (with OpenSSL) and sealing is taken from
486 // Chris Steipp's OATHAuthUtils class in Extension::OATHAuth.
487
488 // Encrypt
489 // @todo: import a pure-PHP library for AES instead of doing $wgSessionInsecureSecrets
490 $iv = \MWCryptRand::generate( 16, true );
491 $algorithm = self::getEncryptionAlgorithm();
492 switch ( $algorithm[0] ) {
493 case 'openssl':
494 $ciphertext = openssl_encrypt( $serialized, $algorithm[1], $encKey, OPENSSL_RAW_DATA, $iv );
495 if ( $ciphertext === false ) {
496 throw new \UnexpectedValueException( 'Encryption failed: ' . openssl_error_string() );
497 }
498 break;
499 case 'mcrypt':
500 // PKCS7 padding
501 $blocksize = mcrypt_get_block_size( $algorithm[1], $algorithm[2] );
502 $pad = $blocksize - ( strlen( $serialized ) % $blocksize );
503 $serialized .= str_repeat( chr( $pad ), $pad );
504
505 $ciphertext = mcrypt_encrypt( $algorithm[1], $encKey, $serialized, $algorithm[2], $iv );
506 if ( $ciphertext === false ) {
507 throw new \UnexpectedValueException( 'Encryption failed' );
508 }
509 break;
510 case 'insecure':
511 $ex = new \Exception( 'No encryption is available, storing data as plain text' );
512 $this->logger->warning( $ex->getMessage(), [ 'exception' => $ex ] );
513 $ciphertext = $serialized;
514 break;
515 default:
516 throw new \LogicException( 'invalid algorithm' );
517 }
518
519 // Seal
520 $sealed = base64_encode( $iv ) . '.' . base64_encode( $ciphertext );
521 $hmac = hash_hmac( 'sha256', $sealed, $hmacKey, true );
522 $encrypted = base64_encode( $hmac ) . '.' . $sealed;
523
524 // Store
525 $this->set( $key, $encrypted );
526 }
527
534 public function getSecret( $key, $default = null ) {
535 // Fetch
536 $encrypted = $this->get( $key, null );
537 if ( $encrypted === null ) {
538 return $default;
539 }
540
541 // The code for unsealing, checking, and decrypting (with OpenSSL) is
542 // taken from Chris Steipp's OATHAuthUtils class in
543 // Extension::OATHAuth.
544
545 // Unseal and check
546 $pieces = explode( '.', $encrypted );
547 if ( count( $pieces ) !== 3 ) {
548 $ex = new \Exception( 'Invalid sealed-secret format' );
549 $this->logger->warning( $ex->getMessage(), [ 'exception' => $ex ] );
550 return $default;
551 }
552 list( $hmac, $iv, $ciphertext ) = $pieces;
553 list( $encKey, $hmacKey ) = $this->getSecretKeys();
554 $integCalc = hash_hmac( 'sha256', $iv . '.' . $ciphertext, $hmacKey, true );
555 if ( !hash_equals( $integCalc, base64_decode( $hmac ) ) ) {
556 $ex = new \Exception( 'Sealed secret has been tampered with, aborting.' );
557 $this->logger->warning( $ex->getMessage(), [ 'exception' => $ex ] );
558 return $default;
559 }
560
561 // Decrypt
562 $algorithm = self::getEncryptionAlgorithm();
563 switch ( $algorithm[0] ) {
564 case 'openssl':
565 $serialized = openssl_decrypt( base64_decode( $ciphertext ), $algorithm[1], $encKey,
566 OPENSSL_RAW_DATA, base64_decode( $iv ) );
567 if ( $serialized === false ) {
568 $ex = new \Exception( 'Decyption failed: ' . openssl_error_string() );
569 $this->logger->debug( $ex->getMessage(), [ 'exception' => $ex ] );
570 return $default;
571 }
572 break;
573 case 'mcrypt':
574 $serialized = mcrypt_decrypt( $algorithm[1], $encKey, base64_decode( $ciphertext ),
575 $algorithm[2], base64_decode( $iv ) );
576 if ( $serialized === false ) {
577 $ex = new \Exception( 'Decyption failed' );
578 $this->logger->debug( $ex->getMessage(), [ 'exception' => $ex ] );
579 return $default;
580 }
581
582 // Remove PKCS7 padding
583 $pad = ord( substr( $serialized, -1 ) );
584 $serialized = substr( $serialized, 0, -$pad );
585 break;
586 case 'insecure':
587 $ex = new \Exception(
588 'No encryption is available, retrieving data that was stored as plain text'
589 );
590 $this->logger->warning( $ex->getMessage(), [ 'exception' => $ex ] );
591 $serialized = base64_decode( $ciphertext );
592 break;
593 default:
594 throw new \LogicException( 'invalid algorithm' );
595 }
596
598 if ( $value === false && $serialized !== serialize( false ) ) {
599 $value = $default;
600 }
601 return $value;
602 }
603
611 public function delaySave() {
612 return $this->backend->delaySave();
613 }
614
621 public function save() {
622 $this->backend->save();
623 }
624
631 public function count() {
632 $data = &$this->backend->getData();
633 return count( $data );
634 }
635
637 public function current() {
638 $data = &$this->backend->getData();
639 return current( $data );
640 }
641
643 public function key() {
644 $data = &$this->backend->getData();
645 return key( $data );
646 }
647
649 public function next() {
650 $data = &$this->backend->getData();
651 next( $data );
652 }
653
655 public function rewind() {
656 $data = &$this->backend->getData();
657 reset( $data );
658 }
659
661 public function valid() {
662 $data = &$this->backend->getData();
663 return key( $data ) !== null;
664 }
665
671 public function offsetExists( $offset ) {
672 $data = &$this->backend->getData();
673 return isset( $data[$offset] );
674 }
675
684 public function &offsetGet( $offset ) {
685 $data = &$this->backend->getData();
686 if ( !array_key_exists( $offset, $data ) ) {
687 $ex = new \Exception( "Undefined index (auto-adds to session with a null value): $offset" );
688 $this->logger->debug( $ex->getMessage(), [ 'exception' => $ex ] );
689 }
690 return $data[$offset];
691 }
692
694 public function offsetSet( $offset, $value ) {
695 $this->set( $offset, $value );
696 }
697
699 public function offsetUnset( $offset ) {
700 $this->remove( $offset );
701 }
702
705}
serialize()
unserialize( $serialized)
$wgSecretKey
This should always be customised in LocalSettings.php.
$wgSessionPbkdf2Iterations
Number of internal PBKDF2 iterations to use when deriving session secrets.
$wgSessionSecret
Secret for session storage.
$wgSessionInsecureSecrets
If for some reason you can't install the PHP OpenSSL or mcrypt extensions, you can set this to true t...
This is the actual workhorse for Session.
Manages data for an an authenticated session.
Definition Session.php:48
offsetSet( $offset, $value)
@inheritDoc
Definition Session.php:694
delaySave()
Delay automatic saving while multiple updates are being made.
Definition Session.php:611
int $index
Session index.
Definition Session.php:56
offsetUnset( $offset)
@inheritDoc
Definition Session.php:699
suggestLoginUsername()
Get a suggested username for the login form.
Definition Session.php:207
static getEncryptionAlgorithm()
Decide what type of encryption to use, based on system capabilities.
Definition Session.php:426
setForceHTTPS( $force)
Set the value of the forceHTTPS cookie.
Definition Session.php:229
getToken( $salt='', $key='default')
Fetch a CSRF token from the session.
Definition Session.php:354
setSecret( $key, $value)
Set a value in the session, encrypted.
Definition Session.php:481
persist()
Make this session persisted across requests.
Definition Session.php:126
shouldForceHTTPS()
Get the expected value of the forceHTTPS cookie.
Definition Session.php:218
renew()
Renew the session.
Definition Session.php:279
getSecret( $key, $default=null)
Fetch a value from the session that was set with self::setSecret()
Definition Session.php:534
resetId()
Changes the session ID.
Definition Session.php:97
getProvider()
Fetch the SessionProvider for this session.
Definition Session.php:105
isPersistent()
Indicate whether this session is persisted across requests.
Definition Session.php:116
getRequest()
Returns the request associated with this session.
Definition Session.php:164
getProviderMetadata()
Fetch provider metadata.
Definition Session.php:254
resetToken( $key='default')
Remove a CSRF token from the session.
Definition Session.php:381
sessionWithRequest(WebRequest $request)
Fetch a copy of this session attached to an alternative WebRequest.
Definition Session.php:292
save()
Save the session.
Definition Session.php:621
static null string[] $encryptionAlgorithm
Encryption algorithm to use.
Definition Session.php:50
unpersist()
Make this session not be persisted across requests.
Definition Session.php:138
canSetUser()
Indicate whether the session user info can be changed.
Definition Session.php:188
exists( $key)
Test if a value exists in the session.
Definition Session.php:314
getLoggedOutTimestamp()
Fetch the "logged out" timestamp.
Definition Session.php:237
getUser()
Returns the authenticated user for this session.
Definition Session.php:172
getId()
Returns the session ID.
Definition Session.php:80
getSecretKeys()
Fetch the secret keys for self::setSecret() and self::getSecret().
Definition Session.php:400
setRememberUser( $remember)
Set whether the user should be remembered independently of the session ID.
Definition Session.php:156
resetAllTokens()
Remove all CSRF tokens from the session.
Definition Session.php:392
clear()
Delete all session data and clear the user (if possible)
Definition Session.php:261
shouldRememberUser()
Indicate whether the user should be remembered independently of the session ID.
Definition Session.php:147
SessionBackend $backend
Session backend.
Definition Session.php:53
setLoggedOutTimestamp( $ts)
Set the "logged out" timestamp.
Definition Session.php:245
LoggerInterface $logger
Definition Session.php:59
setUser( $user)
Set a new user for this session.
Definition Session.php:199
getSessionId()
Returns the SessionId object.
Definition Session.php:89
getAllowedUserRights()
Fetch the rights allowed the user when this session is active.
Definition Session.php:180
__construct(SessionBackend $backend, $index, LoggerInterface $logger)
Definition Session.php:66
Value object representing a CSRF token.
Definition Token.php:32
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition User.php:53
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form stripping il...
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition deferred.txt:11
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g. with the RejectParserCacheValue hook) because MediaWiki won 't do it for you. & $defaults also a ContextSource after deleting those rows but within the same transaction you ll probably need to make sure the header is varied on $request
Definition hooks.txt:2806
foreach( $res as $row) $serialized