MediaWiki  master
Session.php
Go to the documentation of this file.
1 <?php
24 namespace MediaWiki\Session;
25 
26 use Psr\Log\LoggerInterface;
27 use User;
28 use WebRequest;
29 
48 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 
244  public function setLoggedOutTimestamp( $ts ) {
245  $this->backend->setLoggedOutTimestamp( $ts );
246  }
247 
253  public function getProviderMetadata() {
254  return $this->backend->getProviderMetadata();
255  }
256 
260  public function clear() {
261  $data = &$this->backend->getData();
262  if ( $data ) {
263  $data = [];
264  $this->backend->dirty();
265  }
266  if ( $this->backend->canSetUser() ) {
267  $this->backend->setUser( new User );
268  }
269  $this->backend->save();
270  }
271 
276  public function renew() {
277  $this->backend->renew();
278  }
279 
289  public function sessionWithRequest( WebRequest $request ) {
290  $request->setSessionId( $this->backend->getSessionId() );
291  return $this->backend->getSession( $request );
292  }
293 
300  public function get( $key, $default = null ) {
301  $data = &$this->backend->getData();
302  return array_key_exists( $key, $data ) ? $data[$key] : $default;
303  }
304 
311  public function exists( $key ) {
312  $data = &$this->backend->getData();
313  return array_key_exists( $key, $data );
314  }
315 
321  public function set( $key, $value ) {
322  $data = &$this->backend->getData();
323  if ( !array_key_exists( $key, $data ) || $data[$key] !== $value ) {
324  $data[$key] = $value;
325  $this->backend->dirty();
326  }
327  }
328 
333  public function remove( $key ) {
334  $data = &$this->backend->getData();
335  if ( array_key_exists( $key, $data ) ) {
336  unset( $data[$key] );
337  $this->backend->dirty();
338  }
339  }
340 
351  public function getToken( $salt = '', $key = 'default' ) {
352  $new = false;
353  $secrets = $this->get( 'wsTokenSecrets' );
354  if ( !is_array( $secrets ) ) {
355  $secrets = [];
356  }
357  if ( isset( $secrets[$key] ) && is_string( $secrets[$key] ) ) {
358  $secret = $secrets[$key];
359  } else {
360  $secret = \MWCryptRand::generateHex( 32 );
361  $secrets[$key] = $secret;
362  $this->set( 'wsTokenSecrets', $secrets );
363  $new = true;
364  }
365  if ( is_array( $salt ) ) {
366  $salt = implode( '|', $salt );
367  }
368  return new Token( $secret, (string)$salt, $new );
369  }
370 
378  public function resetToken( $key = 'default' ) {
379  $secrets = $this->get( 'wsTokenSecrets' );
380  if ( is_array( $secrets ) && isset( $secrets[$key] ) ) {
381  unset( $secrets[$key] );
382  $this->set( 'wsTokenSecrets', $secrets );
383  }
384  }
385 
389  public function resetAllTokens() {
390  $this->remove( 'wsTokenSecrets' );
391  }
392 
397  private function getSecretKeys() {
399 
400  $wikiSecret = $wgSessionSecret ?: $wgSecretKey;
401  $userSecret = $this->get( 'wsSessionSecret', null );
402  if ( $userSecret === null ) {
403  $userSecret = \MWCryptRand::generateHex( 32 );
404  $this->set( 'wsSessionSecret', $userSecret );
405  }
406  $iterations = $this->get( 'wsSessionPbkdf2Iterations', null );
407  if ( $iterations === null ) {
408  $iterations = $wgSessionPbkdf2Iterations;
409  $this->set( 'wsSessionPbkdf2Iterations', $iterations );
410  }
411 
412  $keymats = hash_pbkdf2( 'sha256', $wikiSecret, $userSecret, $iterations, 64, true );
413  return [
414  substr( $keymats, 0, 32 ),
415  substr( $keymats, 32, 32 ),
416  ];
417  }
418 
423  private static function getEncryptionAlgorithm() {
425 
426  if ( self::$encryptionAlgorithm === null ) {
427  if ( function_exists( 'openssl_encrypt' ) ) {
428  $methods = openssl_get_cipher_methods();
429  if ( in_array( 'aes-256-ctr', $methods, true ) ) {
430  self::$encryptionAlgorithm = [ 'openssl', 'aes-256-ctr' ];
432  }
433  if ( in_array( 'aes-256-cbc', $methods, true ) ) {
434  self::$encryptionAlgorithm = [ 'openssl', 'aes-256-cbc' ];
436  }
437  }
438 
440  // @todo: import a pure-PHP library for AES instead of this
441  self::$encryptionAlgorithm = [ 'insecure' ];
443  }
444 
445  throw new \BadMethodCallException(
446  'Encryption is not available. You really should install the PHP OpenSSL extension. ' .
447  'But if you really can\'t and you\'re willing ' .
448  'to accept insecure storage of sensitive session data, set ' .
449  '$wgSessionInsecureSecrets = true in LocalSettings.php to make this exception go away.'
450  );
451  }
452 
454  }
455 
464  public function setSecret( $key, $value ) {
465  list( $encKey, $hmacKey ) = $this->getSecretKeys();
466  $serialized = serialize( $value );
467 
468  // The code for encryption (with OpenSSL) and sealing is taken from
469  // Chris Steipp's OATHAuthUtils class in Extension::OATHAuth.
470 
471  // Encrypt
472  // @todo: import a pure-PHP library for AES instead of doing $wgSessionInsecureSecrets
473  $iv = random_bytes( 16 );
474  $algorithm = self::getEncryptionAlgorithm();
475  switch ( $algorithm[0] ) {
476  case 'openssl':
477  $ciphertext = openssl_encrypt( $serialized, $algorithm[1], $encKey, OPENSSL_RAW_DATA, $iv );
478  if ( $ciphertext === false ) {
479  throw new \UnexpectedValueException( 'Encryption failed: ' . openssl_error_string() );
480  }
481  break;
482  case 'insecure':
483  $ex = new \Exception( 'No encryption is available, storing data as plain text' );
484  $this->logger->warning( $ex->getMessage(), [ 'exception' => $ex ] );
485  $ciphertext = $serialized;
486  break;
487  default:
488  throw new \LogicException( 'invalid algorithm' );
489  }
490 
491  // Seal
492  $sealed = base64_encode( $iv ) . '.' . base64_encode( $ciphertext );
493  $hmac = hash_hmac( 'sha256', $sealed, $hmacKey, true );
494  $encrypted = base64_encode( $hmac ) . '.' . $sealed;
495 
496  // Store
497  $this->set( $key, $encrypted );
498  }
499 
506  public function getSecret( $key, $default = null ) {
507  // Fetch
508  $encrypted = $this->get( $key, null );
509  if ( $encrypted === null ) {
510  return $default;
511  }
512 
513  // The code for unsealing, checking, and decrypting (with OpenSSL) is
514  // taken from Chris Steipp's OATHAuthUtils class in
515  // Extension::OATHAuth.
516 
517  // Unseal and check
518  $pieces = explode( '.', $encrypted, 4 );
519  if ( count( $pieces ) !== 3 ) {
520  $ex = new \Exception( 'Invalid sealed-secret format' );
521  $this->logger->warning( $ex->getMessage(), [ 'exception' => $ex ] );
522  return $default;
523  }
524  list( $hmac, $iv, $ciphertext ) = $pieces;
525  list( $encKey, $hmacKey ) = $this->getSecretKeys();
526  $integCalc = hash_hmac( 'sha256', $iv . '.' . $ciphertext, $hmacKey, true );
527  if ( !hash_equals( $integCalc, base64_decode( $hmac ) ) ) {
528  $ex = new \Exception( 'Sealed secret has been tampered with, aborting.' );
529  $this->logger->warning( $ex->getMessage(), [ 'exception' => $ex ] );
530  return $default;
531  }
532 
533  // Decrypt
534  $algorithm = self::getEncryptionAlgorithm();
535  switch ( $algorithm[0] ) {
536  case 'openssl':
537  $serialized = openssl_decrypt( base64_decode( $ciphertext ), $algorithm[1], $encKey,
538  OPENSSL_RAW_DATA, base64_decode( $iv ) );
539  if ( $serialized === false ) {
540  $ex = new \Exception( 'Decyption failed: ' . openssl_error_string() );
541  $this->logger->debug( $ex->getMessage(), [ 'exception' => $ex ] );
542  return $default;
543  }
544  break;
545  case 'insecure':
546  $ex = new \Exception(
547  'No encryption is available, retrieving data that was stored as plain text'
548  );
549  $this->logger->warning( $ex->getMessage(), [ 'exception' => $ex ] );
550  $serialized = base64_decode( $ciphertext );
551  break;
552  default:
553  throw new \LogicException( 'invalid algorithm' );
554  }
555 
556  $value = unserialize( $serialized );
557  if ( $value === false && $serialized !== serialize( false ) ) {
558  $value = $default;
559  }
560  return $value;
561  }
562 
570  public function delaySave() {
571  return $this->backend->delaySave();
572  }
573 
578  public function save() {
579  $this->backend->save();
580  }
581 
588  public function count() {
589  $data = &$this->backend->getData();
590  return count( $data );
591  }
592 
594  public function current() {
595  $data = &$this->backend->getData();
596  return current( $data );
597  }
598 
600  public function key() {
601  $data = &$this->backend->getData();
602  return key( $data );
603  }
604 
606  public function next() {
607  $data = &$this->backend->getData();
608  next( $data );
609  }
610 
612  public function rewind() {
613  $data = &$this->backend->getData();
614  reset( $data );
615  }
616 
618  public function valid() {
619  $data = &$this->backend->getData();
620  return key( $data ) !== null;
621  }
622 
628  public function offsetExists( $offset ) {
629  $data = &$this->backend->getData();
630  return isset( $data[$offset] );
631  }
632 
641  public function &offsetGet( $offset ) {
642  $data = &$this->backend->getData();
643  if ( !array_key_exists( $offset, $data ) ) {
644  $ex = new \Exception( "Undefined index (auto-adds to session with a null value): $offset" );
645  $this->logger->debug( $ex->getMessage(), [ 'exception' => $ex ] );
646  }
647  return $data[$offset];
648  }
649 
651  public function offsetSet( $offset, $value ) {
652  $this->set( $offset, $value );
653  }
654 
656  public function offsetUnset( $offset ) {
657  $this->remove( $offset );
658  }
659 
662 }
MediaWiki\Session\Session\key
key()
Definition: Session.php:600
MediaWiki\Session\Session\getSecret
getSecret( $key, $default=null)
Fetch a value from the session that was set with self::setSecret()
Definition: Session.php:506
MediaWiki\Session\Session\clear
clear()
Delete all session data and clear the user (if possible)
Definition: Session.php:260
MediaWiki\Session\Session\sessionWithRequest
sessionWithRequest(WebRequest $request)
Fetch a copy of this session attached to an alternative WebRequest.
Definition: Session.php:289
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
WebRequest\setSessionId
setSessionId(SessionId $sessionId)
Set the session for this request.
Definition: WebRequest.php:837
$wgSessionInsecureSecrets
$wgSessionInsecureSecrets
If for some reason you can't install the PHP OpenSSL extension, you can set this to true to make Medi...
Definition: DefaultSettings.php:9168
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:570
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:88
MediaWiki\Session\Session\getToken
getToken( $salt='', $key='default')
Fetch a CSRF token from the session.
Definition: Session.php:351
MediaWiki\Session\Session\getLoggedOutTimestamp
getLoggedOutTimestamp()
Fetch the "logged out" timestamp.
Definition: Session.php:237
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:311
MediaWiki\Session\Session\getSecretKeys
getSecretKeys()
Fetch the secret keys for self::setSecret() and self::getSecret().
Definition: Session.php:397
serialize
serialize()
Definition: ApiMessageTrait.php:138
MediaWiki\Session\Session\getProviderMetadata
getProviderMetadata()
Fetch provider metadata.
Definition: Session.php:253
MediaWiki\Session\Session\resetAllTokens
resetAllTokens()
Remove all CSRF tokens from the session.
Definition: Session.php:389
MediaWiki\Session\Session\$logger
LoggerInterface $logger
Definition: Session.php:59
MediaWiki\Session\Session\next
next()
Definition: Session.php:606
MediaWiki\Session\Session\offsetExists
offsetExists( $offset)
Definition: Session.php:628
MediaWiki\Session\Session
Manages data for an authenticated session.
Definition: Session.php:48
MediaWiki\Session\Session\renew
renew()
Resets the TTL in the backend store if the session is near expiring, and re-persists the session to a...
Definition: Session.php:276
MediaWiki\Session\Session\getUser
getUser()
Returns the authenticated user for this session.
Definition: Session.php:172
MediaWiki\Session\Session\count
count()
Definition: Session.php:588
MediaWiki\Session\Session\getRequest
getRequest()
Returns the request associated with this session.
Definition: Session.php:164
MediaWiki\Session\Session\current
current()
Definition: Session.php:594
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)
Definition: Session.php:244
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:2698
MediaWiki\Session\Session\offsetGet
& offsetGet( $offset)
Definition: Session.php:641
MediaWiki\Session\Session\$backend
SessionBackend $backend
Session backend.
Definition: Session.php:53
MediaWiki\Session\Session\valid
valid()
Definition: Session.php:618
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:656
MediaWiki\Session\Session\resetToken
resetToken( $key='default')
Remove a CSRF token from the session.
Definition: Session.php:378
MWCryptRand\generateHex
static generateHex( $chars)
Generate a run of cryptographically random data and return it in hexadecimal string format.
Definition: MWCryptRand.php:36
MediaWiki\Session\Session\__destruct
__destruct()
Definition: Session.php:72
MediaWiki\Session\Session\offsetSet
offsetSet( $offset, $value)
Definition: Session.php:651
MediaWiki\Session\Session\rewind
rewind()
Definition: Session.php:612
WebRequest
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form stripping il...
Definition: WebRequest.php:42
unserialize
unserialize( $serialized)
Definition: ApiMessageTrait.php:146
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:464
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:6436
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:56
MediaWiki\Session\Session\getId
getId()
Returns the session ID.
Definition: Session.php:80
$wgSessionSecret
$wgSessionSecret
Secret for session storage.
Definition: DefaultSettings.php:9159
MediaWiki\Session\Session\save
save()
This will update the backend data and might re-persist the session if needed.
Definition: Session.php:578
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:423
MediaWiki\Session\SessionBackend
This is the actual workhorse for Session.
Definition: SessionBackend.php:52