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 
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 
292  public function sessionWithRequest( WebRequest $request ) {
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 
443  // @todo: import a pure-PHP library for AES instead of this
444  self::$encryptionAlgorithm = [ 'insecure' ];
446  }
447 
448  throw new \BadMethodCallException(
449  'Encryption is not available. You really should install the PHP OpenSSL extension. ' .
450  'But if you really can\'t and you\'re willing ' .
451  'to accept insecure storage of sensitive session data, set ' .
452  '$wgSessionInsecureSecrets = true in LocalSettings.php to make this exception go away.'
453  );
454  }
455 
457  }
458 
467  public function setSecret( $key, $value ) {
468  list( $encKey, $hmacKey ) = $this->getSecretKeys();
469  $serialized = serialize( $value );
470 
471  // The code for encryption (with OpenSSL) and sealing is taken from
472  // Chris Steipp's OATHAuthUtils class in Extension::OATHAuth.
473 
474  // Encrypt
475  // @todo: import a pure-PHP library for AES instead of doing $wgSessionInsecureSecrets
476  $iv = random_bytes( 16 );
477  $algorithm = self::getEncryptionAlgorithm();
478  switch ( $algorithm[0] ) {
479  case 'openssl':
480  $ciphertext = openssl_encrypt( $serialized, $algorithm[1], $encKey, OPENSSL_RAW_DATA, $iv );
481  if ( $ciphertext === false ) {
482  throw new \UnexpectedValueException( 'Encryption failed: ' . openssl_error_string() );
483  }
484  break;
485  case 'insecure':
486  $ex = new \Exception( 'No encryption is available, storing data as plain text' );
487  $this->logger->warning( $ex->getMessage(), [ 'exception' => $ex ] );
488  $ciphertext = $serialized;
489  break;
490  default:
491  throw new \LogicException( 'invalid algorithm' );
492  }
493 
494  // Seal
495  $sealed = base64_encode( $iv ) . '.' . base64_encode( $ciphertext );
496  $hmac = hash_hmac( 'sha256', $sealed, $hmacKey, true );
497  $encrypted = base64_encode( $hmac ) . '.' . $sealed;
498 
499  // Store
500  $this->set( $key, $encrypted );
501  }
502 
509  public function getSecret( $key, $default = null ) {
510  // Fetch
511  $encrypted = $this->get( $key, null );
512  if ( $encrypted === null ) {
513  return $default;
514  }
515 
516  // The code for unsealing, checking, and decrypting (with OpenSSL) is
517  // taken from Chris Steipp's OATHAuthUtils class in
518  // Extension::OATHAuth.
519 
520  // Unseal and check
521  $pieces = explode( '.', $encrypted, 4 );
522  if ( count( $pieces ) !== 3 ) {
523  $ex = new \Exception( 'Invalid sealed-secret format' );
524  $this->logger->warning( $ex->getMessage(), [ 'exception' => $ex ] );
525  return $default;
526  }
527  list( $hmac, $iv, $ciphertext ) = $pieces;
528  list( $encKey, $hmacKey ) = $this->getSecretKeys();
529  $integCalc = hash_hmac( 'sha256', $iv . '.' . $ciphertext, $hmacKey, true );
530  if ( !hash_equals( $integCalc, base64_decode( $hmac ) ) ) {
531  $ex = new \Exception( 'Sealed secret has been tampered with, aborting.' );
532  $this->logger->warning( $ex->getMessage(), [ 'exception' => $ex ] );
533  return $default;
534  }
535 
536  // Decrypt
537  $algorithm = self::getEncryptionAlgorithm();
538  switch ( $algorithm[0] ) {
539  case 'openssl':
540  $serialized = openssl_decrypt( base64_decode( $ciphertext ), $algorithm[1], $encKey,
541  OPENSSL_RAW_DATA, base64_decode( $iv ) );
542  if ( $serialized === false ) {
543  $ex = new \Exception( 'Decyption failed: ' . openssl_error_string() );
544  $this->logger->debug( $ex->getMessage(), [ 'exception' => $ex ] );
545  return $default;
546  }
547  break;
548  case 'insecure':
549  $ex = new \Exception(
550  'No encryption is available, retrieving data that was stored as plain text'
551  );
552  $this->logger->warning( $ex->getMessage(), [ 'exception' => $ex ] );
553  $serialized = base64_decode( $ciphertext );
554  break;
555  default:
556  throw new \LogicException( 'invalid algorithm' );
557  }
558 
559  $value = unserialize( $serialized );
560  if ( $value === false && $serialized !== serialize( false ) ) {
561  $value = $default;
562  }
563  return $value;
564  }
565 
573  public function delaySave() {
574  return $this->backend->delaySave();
575  }
576 
583  public function save() {
584  $this->backend->save();
585  }
586 
593  public function count() {
594  $data = &$this->backend->getData();
595  return count( $data );
596  }
597 
599  public function current() {
600  $data = &$this->backend->getData();
601  return current( $data );
602  }
603 
605  public function key() {
606  $data = &$this->backend->getData();
607  return key( $data );
608  }
609 
611  public function next() {
612  $data = &$this->backend->getData();
613  next( $data );
614  }
615 
617  public function rewind() {
618  $data = &$this->backend->getData();
619  reset( $data );
620  }
621 
623  public function valid() {
624  $data = &$this->backend->getData();
625  return key( $data ) !== null;
626  }
627 
633  public function offsetExists( $offset ) {
634  $data = &$this->backend->getData();
635  return isset( $data[$offset] );
636  }
637 
646  public function &offsetGet( $offset ) {
647  $data = &$this->backend->getData();
648  if ( !array_key_exists( $offset, $data ) ) {
649  $ex = new \Exception( "Undefined index (auto-adds to session with a null value): $offset" );
650  $this->logger->debug( $ex->getMessage(), [ 'exception' => $ex ] );
651  }
652  return $data[$offset];
653  }
654 
656  public function offsetSet( $offset, $value ) {
657  $this->set( $offset, $value );
658  }
659 
661  public function offsetUnset( $offset ) {
662  $this->remove( $offset );
663  }
664 
667 }
MediaWiki\Session\Session\key
key()
Definition: Session.php:605
MediaWiki\Session\Session\getSecret
getSecret( $key, $default=null)
Fetch a value from the session that was set with self::setSecret()
Definition: Session.php:509
MediaWiki\Session\Session\clear
clear()
Delete all session data and clear the user (if possible)
Definition: Session.php:261
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
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:9151
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:573
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:354
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:314
MediaWiki\Session\Session\getSecretKeys
getSecretKeys()
Fetch the secret keys for self::setSecret() and self::getSecret().
Definition: Session.php:400
serialize
serialize()
Definition: ApiMessageTrait.php:138
MediaWiki\Session\Session\getProviderMetadata
getProviderMetadata()
Fetch provider metadata.
Definition: Session.php:254
MediaWiki\Session\Session\resetAllTokens
resetAllTokens()
Remove all CSRF tokens from the session.
Definition: Session.php:392
MediaWiki\Session\Session\$logger
LoggerInterface $logger
Definition: Session.php:59
MediaWiki\Session\Session\next
next()
Definition: Session.php:611
MediaWiki\Session\Session\offsetExists
offsetExists( $offset)
Definition: Session.php:633
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()
Definition: Session.php:593
MediaWiki\Session\Session\getRequest
getRequest()
Returns the request associated with this session.
Definition: Session.php:164
MediaWiki\Session\Session\current
current()
Definition: Session.php:599
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
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:2684
MediaWiki\Session\Session\offsetGet
& offsetGet( $offset)
Definition: Session.php:646
MediaWiki\Session\Session\$backend
SessionBackend $backend
Session backend.
Definition: Session.php:53
MediaWiki\Session\Session\valid
valid()
Definition: Session.php:623
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:661
MediaWiki\Session\Session\resetToken
resetToken( $key='default')
Remove a CSRF token from the session.
Definition: Session.php:381
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:656
MediaWiki\Session\Session\rewind
rewind()
Definition: Session.php:617
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:467
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:6414
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:9142
MediaWiki\Session\Session\save
save()
Save the session.
Definition: Session.php:583
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:52