MediaWiki  1.32.5
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() {
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() {
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 
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 = random_bytes( 16 );
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 
625  public function count() {
626  $data = &$this->backend->getData();
627  return count( $data );
628  }
629 
631  public function current() {
632  $data = &$this->backend->getData();
633  return current( $data );
634  }
635 
637  public function key() {
638  $data = &$this->backend->getData();
639  return key( $data );
640  }
641 
643  public function next() {
644  $data = &$this->backend->getData();
645  next( $data );
646  }
647 
649  public function rewind() {
650  $data = &$this->backend->getData();
651  reset( $data );
652  }
653 
655  public function valid() {
656  $data = &$this->backend->getData();
657  return key( $data ) !== null;
658  }
659 
665  public function offsetExists( $offset ) {
666  $data = &$this->backend->getData();
667  return isset( $data[$offset] );
668  }
669 
678  public function &offsetGet( $offset ) {
679  $data = &$this->backend->getData();
680  if ( !array_key_exists( $offset, $data ) ) {
681  $ex = new \Exception( "Undefined index (auto-adds to session with a null value): $offset" );
682  $this->logger->debug( $ex->getMessage(), [ 'exception' => $ex ] );
683  }
684  return $data[$offset];
685  }
686 
688  public function offsetSet( $offset, $value ) {
689  $this->set( $offset, $value );
690  }
691 
693  public function offsetUnset( $offset ) {
694  $this->remove( $offset );
695  }
696 
699 }
MediaWiki\Session\Session\key
key()
@inheritDoc
Definition: Session.php:637
MediaWiki\Session\Session\getSecret
getSecret( $key, $default=null)
Fetch a value from the session that was set with self::setSecret()
Definition: Session.php:528
$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:244
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
$wgSessionInsecureSecrets
$wgSessionInsecureSecrets
If for some reason you can't install the PHP OpenSSL or mcrypt extensions, you can set this to true t...
Definition: DefaultSettings.php:8691
MediaWiki\Session\Session\persist
persist()
Make this session persisted across requests.
Definition: Session.php:126
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
$serialized
foreach( $res as $row) $serialized
Definition: testCompression.php:81
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
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
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
serialize
serialize()
Definition: ApiMessageTrait.php:131
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
MediaWiki\Session\Session\$logger
LoggerInterface $logger
Definition: Session.php:59
MediaWiki\Session\Session\next
next()
@inheritDoc
Definition: Session.php:643
MediaWiki\Session\Session\offsetExists
offsetExists( $offset)
Definition: Session.php:665
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()
@inheritDoc
Definition: Session.php:625
MediaWiki\Session\Session\getRequest
getRequest()
Returns the request associated with this session.
Definition: Session.php:164
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\current
current()
@inheritDoc
Definition: Session.php:631
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
$request
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:2683
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:2534
MediaWiki\Session\Session\offsetGet
& offsetGet( $offset)
Definition: Session.php:678
$value
$value
Definition: styleTest.css.php:49
MediaWiki\Session\Session\$backend
SessionBackend $backend
Session backend.
Definition: Session.php:53
MediaWiki\Session\Session\valid
valid()
@inheritDoc
Definition: Session.php:655
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)
@inheritDoc
Definition: Session.php:693
MediaWiki\Session\Session\resetToken
resetToken( $key='default')
Remove a CSRF token from the session.
Definition: Session.php:375
MWCryptRand\generateHex
static generateHex( $chars)
Generate a run of cryptographically random data and return it in hexadecimal string format.
Definition: MWCryptRand.php:71
MediaWiki\Session\Session\__destruct
__destruct()
Definition: Session.php:72
MediaWiki\Session\Session\offsetSet
offsetSet( $offset, $value)
@inheritDoc
Definition: Session.php:688
MediaWiki\Session\Session\rewind
rewind()
@inheritDoc
Definition: Session.php:649
WebRequest
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form stripping il...
Definition: WebRequest.php:41
unserialize
unserialize( $serialized)
Definition: ApiMessageTrait.php:139
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
$wgSecretKey
$wgSecretKey
This should always be customised in LocalSettings.php.
Definition: DefaultSettings.php:5986
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:47
MediaWiki\Session\Session\getId
getId()
Returns the session ID.
Definition: Session.php:80
$wgSessionSecret
$wgSessionSecret
Secret for session storage.
Definition: DefaultSettings.php:8682
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