MediaWiki  REL1_31
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 
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 }
$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:247
MediaWiki\Session\Session\key
key()
@inheritDoc
Definition: Session.php:643
MediaWiki\Session\Session\getSecret
getSecret( $key, $default=null)
Fetch a value from the session that was set with self::setSecret()
Definition: Session.php:534
MediaWiki\Session\Session\clear
clear()
Delete all session data and clear the user (if possible)
Definition: Session.php:261
use
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
Definition: APACHE-LICENSE-2.0.txt:10
MediaWiki\Session\Session\sessionWithRequest
sessionWithRequest(WebRequest $request)
Fetch a copy of this session attached to an alternative WebRequest.
Definition: Session.php:292
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()
Get the expected value of the forceHTTPS cookie.
Definition: Session.php:218
$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:8637
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:611
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
unserialize
unserialize( $serialized)
Definition: ApiMessage.php:192
MediaWiki\Session\Session\getToken
getToken( $salt='', $key='default')
Fetch a CSRF token from the session.
Definition: Session.php:354
MediaWiki\Session\Session\getLoggedOutTimestamp
getLoggedOutTimestamp()
Fetch the "logged out" timestamp.
Definition: Session.php:237
serialize
serialize()
Definition: ApiMessage.php:184
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:314
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:400
MediaWiki\Session\Session\getProviderMetadata
getProviderMetadata()
Fetch provider metadata.
Definition: Session.php:254
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:37
MediaWiki\Session\Session\resetAllTokens
resetAllTokens()
Remove all CSRF tokens from the session.
Definition: Session.php:392
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()
@inheritDoc
Definition: Session.php:649
MediaWiki\Session\Session\offsetExists
offsetExists( $offset)
Definition: Session.php:671
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:279
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:631
MediaWiki\Session\Session\getRequest
getRequest()
Returns the request associated with this session.
Definition: Session.php:164
MediaWiki\Session\Session\current
current()
@inheritDoc
Definition: Session.php:637
global
when a variable name is used in a it is silently declared as a new masking the global
Definition: design.txt:95
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:245
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:2469
MediaWiki\Session\Session\offsetGet
& offsetGet( $offset)
Definition: Session.php:684
$value
$value
Definition: styleTest.css.php:45
MediaWiki\Session\Session\$backend
SessionBackend $backend
Session backend.
Definition: Session.php:53
MediaWiki\Session\Session\valid
valid()
@inheritDoc
Definition: Session.php:661
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:699
MediaWiki\Session\Session\resetToken
resetToken( $key='default')
Remove a CSRF token from the session.
Definition: Session.php:381
MediaWiki\Session\Session\__destruct
__destruct()
Definition: Session.php:72
MediaWiki\Session\Session\offsetSet
offsetSet( $offset, $value)
@inheritDoc
Definition: Session.php:694
MediaWiki\Session\Session\rewind
rewind()
@inheritDoc
Definition: Session.php:655
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:481
$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:2806
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:5966
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:53
MediaWiki\Session\Session\getId
getId()
Returns the session ID.
Definition: Session.php:80
$wgSessionSecret
$wgSessionSecret
Secret for session storage.
Definition: DefaultSettings.php:8628
MediaWiki\Session\Session\save
save()
Save the session.
Definition: Session.php:621
MediaWiki\Session\Session\setForceHTTPS
setForceHTTPS( $force)
Set the value of the forceHTTPS cookie.
Definition: Session.php:229
MediaWiki\Session\Session\getEncryptionAlgorithm
static getEncryptionAlgorithm()
Decide what type of encryption to use, based on system capabilities.
Definition: Session.php:426
MediaWiki\Session\SessionBackend
This is the actual workhorse for Session.
Definition: SessionBackend.php:49