MediaWiki  1.29.1
Session.php
Go to the documentation of this file.
1 <?php
24 namespace MediaWiki\Session;
25 
26 use Psr\Log\LoggerInterface;
27 use User;
29 
48 final 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 
215  public function shouldForceHTTPS() {
216  return $this->backend->shouldForceHTTPS();
217  }
218 
223  public function setForceHTTPS( $force ) {
224  $this->backend->setForceHTTPS( $force );
225  }
226 
231  public function getLoggedOutTimestamp() {
232  return $this->backend->getLoggedOutTimestamp();
233  }
234 
239  public function setLoggedOutTimestamp( $ts ) {
240  $this->backend->setLoggedOutTimestamp( $ts );
241  }
242 
248  public function getProviderMetadata() {
249  return $this->backend->getProviderMetadata();
250  }
251 
255  public function clear() {
256  $data = &$this->backend->getData();
257  if ( $data ) {
258  $data = [];
259  $this->backend->dirty();
260  }
261  if ( $this->backend->canSetUser() ) {
262  $this->backend->setUser( new User );
263  }
264  $this->backend->save();
265  }
266 
273  public function renew() {
274  $this->backend->renew();
275  }
276 
287  $request->setSessionId( $this->backend->getSessionId() );
288  return $this->backend->getSession( $request );
289  }
290 
297  public function get( $key, $default = null ) {
298  $data = &$this->backend->getData();
299  return array_key_exists( $key, $data ) ? $data[$key] : $default;
300  }
301 
308  public function exists( $key ) {
309  $data = &$this->backend->getData();
310  return array_key_exists( $key, $data );
311  }
312 
318  public function set( $key, $value ) {
319  $data = &$this->backend->getData();
320  if ( !array_key_exists( $key, $data ) || $data[$key] !== $value ) {
321  $data[$key] = $value;
322  $this->backend->dirty();
323  }
324  }
325 
330  public function remove( $key ) {
331  $data = &$this->backend->getData();
332  if ( array_key_exists( $key, $data ) ) {
333  unset( $data[$key] );
334  $this->backend->dirty();
335  }
336  }
337 
348  public function getToken( $salt = '', $key = 'default' ) {
349  $new = false;
350  $secrets = $this->get( 'wsTokenSecrets' );
351  if ( !is_array( $secrets ) ) {
352  $secrets = [];
353  }
354  if ( isset( $secrets[$key] ) && is_string( $secrets[$key] ) ) {
355  $secret = $secrets[$key];
356  } else {
357  $secret = \MWCryptRand::generateHex( 32 );
358  $secrets[$key] = $secret;
359  $this->set( 'wsTokenSecrets', $secrets );
360  $new = true;
361  }
362  if ( is_array( $salt ) ) {
363  $salt = implode( '|', $salt );
364  }
365  return new Token( $secret, (string)$salt, $new );
366  }
367 
375  public function resetToken( $key = 'default' ) {
376  $secrets = $this->get( 'wsTokenSecrets' );
377  if ( is_array( $secrets ) && isset( $secrets[$key] ) ) {
378  unset( $secrets[$key] );
379  $this->set( 'wsTokenSecrets', $secrets );
380  }
381  }
382 
386  public function resetAllTokens() {
387  $this->remove( 'wsTokenSecrets' );
388  }
389 
394  private function getSecretKeys() {
395  global $wgSessionSecret, $wgSecretKey, $wgSessionPbkdf2Iterations;
396 
397  $wikiSecret = $wgSessionSecret ?: $wgSecretKey;
398  $userSecret = $this->get( 'wsSessionSecret', null );
399  if ( $userSecret === null ) {
400  $userSecret = \MWCryptRand::generateHex( 32 );
401  $this->set( 'wsSessionSecret', $userSecret );
402  }
403  $iterations = $this->get( 'wsSessionPbkdf2Iterations', null );
404  if ( $iterations === null ) {
405  $iterations = $wgSessionPbkdf2Iterations;
406  $this->set( 'wsSessionPbkdf2Iterations', $iterations );
407  }
408 
409  $keymats = hash_pbkdf2( 'sha256', $wikiSecret, $userSecret, $iterations, 64, true );
410  return [
411  substr( $keymats, 0, 32 ),
412  substr( $keymats, 32, 32 ),
413  ];
414  }
415 
420  private static function getEncryptionAlgorithm() {
421  global $wgSessionInsecureSecrets;
422 
423  if ( self::$encryptionAlgorithm === null ) {
424  if ( function_exists( 'openssl_encrypt' ) ) {
425  $methods = openssl_get_cipher_methods();
426  if ( in_array( 'aes-256-ctr', $methods, true ) ) {
427  self::$encryptionAlgorithm = [ 'openssl', 'aes-256-ctr' ];
429  }
430  if ( in_array( 'aes-256-cbc', $methods, true ) ) {
431  self::$encryptionAlgorithm = [ 'openssl', 'aes-256-cbc' ];
433  }
434  }
435 
436  if ( function_exists( 'mcrypt_encrypt' )
437  && in_array( 'rijndael-128', mcrypt_list_algorithms(), true )
438  ) {
439  $modes = mcrypt_list_modes();
440  if ( in_array( 'ctr', $modes, true ) ) {
441  self::$encryptionAlgorithm = [ 'mcrypt', 'rijndael-128', 'ctr' ];
443  }
444  if ( in_array( 'cbc', $modes, true ) ) {
445  self::$encryptionAlgorithm = [ 'mcrypt', 'rijndael-128', 'cbc' ];
447  }
448  }
449 
450  if ( $wgSessionInsecureSecrets ) {
451  // @todo: import a pure-PHP library for AES instead of this
452  self::$encryptionAlgorithm = [ 'insecure' ];
454  }
455 
456  throw new \BadMethodCallException(
457  'Encryption is not available. You really should install the PHP OpenSSL extension, ' .
458  'or failing that the mcrypt extension. But if you really can\'t and you\'re willing ' .
459  'to accept insecure storage of sensitive session data, set ' .
460  '$wgSessionInsecureSecrets = true in LocalSettings.php to make this exception go away.'
461  );
462  }
463 
465  }
466 
475  public function setSecret( $key, $value ) {
476  list( $encKey, $hmacKey ) = $this->getSecretKeys();
478 
479  // The code for encryption (with OpenSSL) and sealing is taken from
480  // Chris Steipp's OATHAuthUtils class in Extension::OATHAuth.
481 
482  // Encrypt
483  // @todo: import a pure-PHP library for AES instead of doing $wgSessionInsecureSecrets
484  $iv = \MWCryptRand::generate( 16, true );
485  $algorithm = self::getEncryptionAlgorithm();
486  switch ( $algorithm[0] ) {
487  case 'openssl':
488  $ciphertext = openssl_encrypt( $serialized, $algorithm[1], $encKey, OPENSSL_RAW_DATA, $iv );
489  if ( $ciphertext === false ) {
490  throw new \UnexpectedValueException( 'Encryption failed: ' . openssl_error_string() );
491  }
492  break;
493  case 'mcrypt':
494  // PKCS7 padding
495  $blocksize = mcrypt_get_block_size( $algorithm[1], $algorithm[2] );
496  $pad = $blocksize - ( strlen( $serialized ) % $blocksize );
497  $serialized .= str_repeat( chr( $pad ), $pad );
498 
499  $ciphertext = mcrypt_encrypt( $algorithm[1], $encKey, $serialized, $algorithm[2], $iv );
500  if ( $ciphertext === false ) {
501  throw new \UnexpectedValueException( 'Encryption failed' );
502  }
503  break;
504  case 'insecure':
505  $ex = new \Exception( 'No encryption is available, storing data as plain text' );
506  $this->logger->warning( $ex->getMessage(), [ 'exception' => $ex ] );
507  $ciphertext = $serialized;
508  break;
509  default:
510  throw new \LogicException( 'invalid algorithm' );
511  }
512 
513  // Seal
514  $sealed = base64_encode( $iv ) . '.' . base64_encode( $ciphertext );
515  $hmac = hash_hmac( 'sha256', $sealed, $hmacKey, true );
516  $encrypted = base64_encode( $hmac ) . '.' . $sealed;
517 
518  // Store
519  $this->set( $key, $encrypted );
520  }
521 
528  public function getSecret( $key, $default = null ) {
529  // Fetch
530  $encrypted = $this->get( $key, null );
531  if ( $encrypted === null ) {
532  return $default;
533  }
534 
535  // The code for unsealing, checking, and decrypting (with OpenSSL) is
536  // taken from Chris Steipp's OATHAuthUtils class in
537  // Extension::OATHAuth.
538 
539  // Unseal and check
540  $pieces = explode( '.', $encrypted );
541  if ( count( $pieces ) !== 3 ) {
542  $ex = new \Exception( 'Invalid sealed-secret format' );
543  $this->logger->warning( $ex->getMessage(), [ 'exception' => $ex ] );
544  return $default;
545  }
546  list( $hmac, $iv, $ciphertext ) = $pieces;
547  list( $encKey, $hmacKey ) = $this->getSecretKeys();
548  $integCalc = hash_hmac( 'sha256', $iv . '.' . $ciphertext, $hmacKey, true );
549  if ( !hash_equals( $integCalc, base64_decode( $hmac ) ) ) {
550  $ex = new \Exception( 'Sealed secret has been tampered with, aborting.' );
551  $this->logger->warning( $ex->getMessage(), [ 'exception' => $ex ] );
552  return $default;
553  }
554 
555  // Decrypt
556  $algorithm = self::getEncryptionAlgorithm();
557  switch ( $algorithm[0] ) {
558  case 'openssl':
559  $serialized = openssl_decrypt( base64_decode( $ciphertext ), $algorithm[1], $encKey,
560  OPENSSL_RAW_DATA, base64_decode( $iv ) );
561  if ( $serialized === false ) {
562  $ex = new \Exception( 'Decyption failed: ' . openssl_error_string() );
563  $this->logger->debug( $ex->getMessage(), [ 'exception' => $ex ] );
564  return $default;
565  }
566  break;
567  case 'mcrypt':
568  $serialized = mcrypt_decrypt( $algorithm[1], $encKey, base64_decode( $ciphertext ),
569  $algorithm[2], base64_decode( $iv ) );
570  if ( $serialized === false ) {
571  $ex = new \Exception( 'Decyption failed' );
572  $this->logger->debug( $ex->getMessage(), [ 'exception' => $ex ] );
573  return $default;
574  }
575 
576  // Remove PKCS7 padding
577  $pad = ord( substr( $serialized, -1 ) );
578  $serialized = substr( $serialized, 0, -$pad );
579  break;
580  case 'insecure':
581  $ex = new \Exception(
582  'No encryption is available, retrieving data that was stored as plain text'
583  );
584  $this->logger->warning( $ex->getMessage(), [ 'exception' => $ex ] );
585  $serialized = base64_decode( $ciphertext );
586  break;
587  default:
588  throw new \LogicException( 'invalid algorithm' );
589  }
590 
592  if ( $value === false && $serialized !== serialize( false ) ) {
593  $value = $default;
594  }
595  return $value;
596  }
597 
605  public function delaySave() {
606  return $this->backend->delaySave();
607  }
608 
615  public function save() {
616  $this->backend->save();
617  }
618 
624  public function count() {
625  $data = &$this->backend->getData();
626  return count( $data );
627  }
628 
629  public function current() {
630  $data = &$this->backend->getData();
631  return current( $data );
632  }
633 
634  public function key() {
635  $data = &$this->backend->getData();
636  return key( $data );
637  }
638 
639  public function next() {
640  $data = &$this->backend->getData();
641  next( $data );
642  }
643 
644  public function rewind() {
645  $data = &$this->backend->getData();
646  reset( $data );
647  }
648 
649  public function valid() {
650  $data = &$this->backend->getData();
651  return key( $data ) !== null;
652  }
653 
658  public function offsetExists( $offset ) {
659  $data = &$this->backend->getData();
660  return isset( $data[$offset] );
661  }
662 
670  public function &offsetGet( $offset ) {
671  $data = &$this->backend->getData();
672  if ( !array_key_exists( $offset, $data ) ) {
673  $ex = new \Exception( "Undefined index (auto-adds to session with a null value): $offset" );
674  $this->logger->debug( $ex->getMessage(), [ 'exception' => $ex ] );
675  }
676  return $data[$offset];
677  }
678 
679  public function offsetSet( $offset, $value ) {
680  $this->set( $offset, $value );
681  }
682 
683  public function offsetUnset( $offset ) {
684  $this->remove( $offset );
685  }
686 
689 }
MediaWiki\Session\Session\key
key()
Definition: Session.php:634
MediaWiki\Session\Session\getSecret
getSecret( $key, $default=null)
Fetch a value from the session that was set with self::setSecret()
Definition: Session.php:528
$request
error also a ContextSource you ll probably need to make sure the header is varied on $request
Definition: hooks.txt:2612
MediaWiki\Session\Session\clear
clear()
Delete all session data and clear the user (if possible)
Definition: Session.php:255
MediaWiki\Session\Session\sessionWithRequest
sessionWithRequest(WebRequest $request)
Fetch a copy of this session attached to an alternative WebRequest.
Definition: Session.php:286
MediaWiki\Session\Session\getProvider
getProvider()
Fetch the SessionProvider for this session.
Definition: Session.php:105
MediaWiki\Session\Session\__construct
__construct(SessionBackend $backend, $index, LoggerInterface $logger)
Definition: Session.php:66
MediaWiki\Session\Session\shouldForceHTTPS
shouldForceHTTPS()
Whether HTTPS should be forced.
Definition: Session.php:215
MediaWiki\Session\Session\persist
persist()
Make this session persisted across requests.
Definition: Session.php:126
use
as see the revision history and available at free of to any person obtaining a copy of this software and associated documentation to deal in the Software without including without limitation the rights to use
Definition: MIT-LICENSE.txt:10
MediaWiki\Session\Session\delaySave
delaySave()
Delay automatic saving while multiple updates are being made.
Definition: Session.php:605
MediaWiki\Session\Session\isPersistent
isPersistent()
Indicate whether this session is persisted across requests.
Definition: Session.php:116
$user
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a account $user
Definition: hooks.txt:246
$serialized
foreach( $res as $row) $serialized
Definition: testCompression.php:79
unserialize
unserialize( $serialized)
Definition: ApiMessage.php:185
MediaWiki\Session\Session\getToken
getToken( $salt='', $key='default')
Fetch a CSRF token from the session.
Definition: Session.php:348
MediaWiki\Session\Session\getLoggedOutTimestamp
getLoggedOutTimestamp()
Fetch the "logged out" timestamp.
Definition: Session.php:231
serialize
serialize()
Definition: ApiMessage.php:177
MediaWiki\Session\Session\getAllowedUserRights
getAllowedUserRights()
Fetch the rights allowed the user when this session is active.
Definition: Session.php:180
MediaWiki\Session\Session\shouldRememberUser
shouldRememberUser()
Indicate whether the user should be remembered independently of the session ID.
Definition: Session.php:147
MediaWiki\Session\Session\exists
exists( $key)
Test if a value exists in the session.
Definition: Session.php:308
MWCryptRand\generateHex
static generateHex( $chars, $forceStrong=false)
Generate a run of (ideally) cryptographically random data and return it in hexadecimal string format.
Definition: MWCryptRand.php:76
User
User
Definition: All_system_messages.txt:425
MediaWiki\Session\Session\getSecretKeys
getSecretKeys()
Fetch the secret keys for self::setSecret() and self::getSecret().
Definition: Session.php:394
MediaWiki\Session\Session\getProviderMetadata
getProviderMetadata()
Fetch provider metadata.
Definition: Session.php:248
php
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
MediaWiki\Session\Session\resetAllTokens
resetAllTokens()
Remove all CSRF tokens from the session.
Definition: Session.php:386
MWCryptRand\generate
static generate( $bytes, $forceStrong=false)
Generate a run of (ideally) cryptographically random data and return it in raw binary form.
Definition: MWCryptRand.php:60
MediaWiki\Session\Session\$logger
LoggerInterface $logger
Definition: Session.php:59
MediaWiki\Session\Session\next
next()
Definition: Session.php:639
MediaWiki\Session\Session\offsetExists
offsetExists( $offset)
Definition: Session.php:658
MediaWiki\Session\Session
Manages data for an an authenticated session.
Definition: Session.php:48
MediaWiki\Session\Session\renew
renew()
Renew the session.
Definition: Session.php:273
MediaWiki\Session\Session\getUser
getUser()
Returns the authenticated user for this session.
Definition: Session.php:172
MediaWiki\Session\Session\count
count()
Definition: Session.php:624
MediaWiki\Session\Session\getRequest
getRequest()
Returns the request associated with this session.
Definition: Session.php:164
MediaWiki\Session\Session\current
current()
Definition: Session.php:629
global
when a variable name is used in a it is silently declared as a new masking the global
Definition: design.txt:93
MediaWiki\Session
Definition: BotPasswordSessionProvider.php:24
MediaWiki\Session\Session\canSetUser
canSetUser()
Indicate whether the session user info can be changed.
Definition: Session.php:188
MediaWiki\Session\Session\getSessionId
getSessionId()
Returns the SessionId object.
Definition: Session.php:89
MediaWiki\Session\Session\unpersist
unpersist()
Make this session not be persisted across requests.
Definition: Session.php:138
MediaWiki\Session\Session\setLoggedOutTimestamp
setLoggedOutTimestamp( $ts)
Set the "logged out" timestamp.
Definition: Session.php:239
list
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
MediaWiki\Session\Session\resetId
resetId()
Changes the session ID.
Definition: Session.php:97
$wgSessionPbkdf2Iterations
$wgSessionPbkdf2Iterations
Number of internal PBKDF2 iterations to use when deriving session secrets.
Definition: DefaultSettings.php:2423
MediaWiki\Session\Session\offsetGet
& offsetGet( $offset)
Definition: Session.php:670
$value
$value
Definition: styleTest.css.php:45
MediaWiki\Session\Session\$backend
SessionBackend $backend
Session backend.
Definition: Session.php:53
MediaWiki\Session\Session\valid
valid()
Definition: Session.php:649
MediaWiki\Session\Session\setRememberUser
setRememberUser( $remember)
Set whether the user should be remembered independently of the session ID.
Definition: Session.php:156
MediaWiki\Session\Session\$index
int $index
Session index.
Definition: Session.php:56
MediaWiki\Session\Session\offsetUnset
offsetUnset( $offset)
Definition: Session.php:683
MediaWiki\Session\Session\resetToken
resetToken( $key='default')
Remove a CSRF token from the session.
Definition: Session.php:375
MediaWiki\Session\Session\__destruct
__destruct()
Definition: Session.php:72
MediaWiki\Session\Session\offsetSet
offsetSet( $offset, $value)
Definition: Session.php:679
MediaWiki\Session\Session\rewind
rewind()
Definition: Session.php:644
WebRequest
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form stripping il...
Definition: WebRequest.php:38
MediaWiki\Session\Session\setUser
setUser( $user)
Set a new user for this session.
Definition: Session.php:199
MediaWiki\Session\Session\$encryptionAlgorithm
static null string[] $encryptionAlgorithm
Encryption algorithm to use.
Definition: Session.php:50
MediaWiki\Session\Session\setSecret
setSecret( $key, $value)
Set a value in the session, encrypted.
Definition: Session.php:475
MediaWiki\Session\Token
Value object representing a CSRF token.
Definition: Token.php:32
MediaWiki\Session\Session\suggestLoginUsername
suggestLoginUsername()
Get a suggested username for the login form.
Definition: Session.php:207
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:50
MediaWiki\Session\Session\getId
getId()
Returns the session ID.
Definition: Session.php:80
MediaWiki\Session\Session\save
save()
Save the session.
Definition: Session.php:615
MediaWiki\Session\Session\setForceHTTPS
setForceHTTPS( $force)
Set whether HTTPS should be forced.
Definition: Session.php:223
MediaWiki\Session\Session\getEncryptionAlgorithm
static getEncryptionAlgorithm()
Decide what type of encryption to use, based on system capabilities.
Definition: Session.php:420
MediaWiki\Session\SessionBackend
This is the actual workhorse for Session.
Definition: SessionBackend.php:49