MediaWiki  1.27.0
Session.php
Go to the documentation of this file.
1 <?php
24 namespace MediaWiki\Session;
25 
27 use User;
29 
48 final class Session implements \Countable, \Iterator, \ArrayAccess {
50  private $backend;
51 
53  private $index;
54 
56  private $logger;
57 
63  public function __construct( SessionBackend $backend, $index, LoggerInterface $logger ) {
64  $this->backend = $backend;
65  $this->index = $index;
66  $this->logger = $logger;
67  }
68 
69  public function __destruct() {
70  $this->backend->deregisterSession( $this->index );
71  }
72 
77  public function getId() {
78  return $this->backend->getId();
79  }
80 
86  public function getSessionId() {
87  return $this->backend->getSessionId();
88  }
89 
94  public function resetId() {
95  return $this->backend->resetId();
96  }
97 
102  public function getProvider() {
103  return $this->backend->getProvider();
104  }
105 
113  public function isPersistent() {
114  return $this->backend->isPersistent();
115  }
116 
123  public function persist() {
124  $this->backend->persist();
125  }
126 
130  public function unpersist() {
131  $this->backend->unpersist();
132  }
133 
139  public function shouldRememberUser() {
140  return $this->backend->shouldRememberUser();
141  }
142 
148  public function setRememberUser( $remember ) {
149  $this->backend->setRememberUser( $remember );
150  }
151 
156  public function getRequest() {
157  return $this->backend->getRequest( $this->index );
158  }
159 
164  public function getUser() {
165  return $this->backend->getUser();
166  }
167 
172  public function getAllowedUserRights() {
173  return $this->backend->getAllowedUserRights();
174  }
175 
180  public function canSetUser() {
181  return $this->backend->canSetUser();
182  }
183 
191  public function setUser( $user ) {
192  $this->backend->setUser( $user );
193  }
194 
199  public function suggestLoginUsername() {
200  return $this->backend->suggestLoginUsername( $this->index );
201  }
202 
207  public function shouldForceHTTPS() {
208  return $this->backend->shouldForceHTTPS();
209  }
210 
215  public function setForceHTTPS( $force ) {
216  $this->backend->setForceHTTPS( $force );
217  }
218 
223  public function getLoggedOutTimestamp() {
224  return $this->backend->getLoggedOutTimestamp();
225  }
226 
231  public function setLoggedOutTimestamp( $ts ) {
232  $this->backend->setLoggedOutTimestamp( $ts );
233  }
234 
240  public function getProviderMetadata() {
241  return $this->backend->getProviderMetadata();
242  }
243 
247  public function clear() {
248  $data = &$this->backend->getData();
249  if ( $data ) {
250  $data = [];
251  $this->backend->dirty();
252  }
253  if ( $this->backend->canSetUser() ) {
254  $this->backend->setUser( new User );
255  }
256  $this->backend->save();
257  }
258 
265  public function renew() {
266  $this->backend->renew();
267  }
268 
279  $request->setSessionId( $this->backend->getSessionId() );
280  return $this->backend->getSession( $request );
281  }
282 
289  public function get( $key, $default = null ) {
290  $data = &$this->backend->getData();
291  return array_key_exists( $key, $data ) ? $data[$key] : $default;
292  }
293 
300  public function exists( $key ) {
301  $data = &$this->backend->getData();
302  return array_key_exists( $key, $data );
303  }
304 
310  public function set( $key, $value ) {
311  $data = &$this->backend->getData();
312  if ( !array_key_exists( $key, $data ) || $data[$key] !== $value ) {
313  $data[$key] = $value;
314  $this->backend->dirty();
315  }
316  }
317 
322  public function remove( $key ) {
323  $data = &$this->backend->getData();
324  if ( array_key_exists( $key, $data ) ) {
325  unset( $data[$key] );
326  $this->backend->dirty();
327  }
328  }
329 
340  public function getToken( $salt = '', $key = 'default' ) {
341  $new = false;
342  $secrets = $this->get( 'wsTokenSecrets' );
343  if ( !is_array( $secrets ) ) {
344  $secrets = [];
345  }
346  if ( isset( $secrets[$key] ) && is_string( $secrets[$key] ) ) {
347  $secret = $secrets[$key];
348  } else {
349  $secret = \MWCryptRand::generateHex( 32 );
350  $secrets[$key] = $secret;
351  $this->set( 'wsTokenSecrets', $secrets );
352  $new = true;
353  }
354  if ( is_array( $salt ) ) {
355  $salt = implode( '|', $salt );
356  }
357  return new Token( $secret, (string)$salt, $new );
358  }
359 
367  public function resetToken( $key = 'default' ) {
368  $secrets = $this->get( 'wsTokenSecrets' );
369  if ( is_array( $secrets ) && isset( $secrets[$key] ) ) {
370  unset( $secrets[$key] );
371  $this->set( 'wsTokenSecrets', $secrets );
372  }
373  }
374 
378  public function resetAllTokens() {
379  $this->remove( 'wsTokenSecrets' );
380  }
381 
386  private function getSecretKeys() {
387  global $wgSessionSecret, $wgSecretKey;
388 
389  $wikiSecret = $wgSessionSecret ?: $wgSecretKey;
390  $userSecret = $this->get( 'wsSessionSecret', null );
391  if ( $userSecret === null ) {
392  $userSecret = \MWCryptRand::generateHex( 32 );
393  $this->set( 'wsSessionSecret', $userSecret );
394  }
395 
396  $keymats = hash_pbkdf2( 'sha256', $wikiSecret, $userSecret, 10001, 64, true );
397  return [
398  substr( $keymats, 0, 32 ),
399  substr( $keymats, 32, 32 ),
400  ];
401  }
402 
411  public function setSecret( $key, $value ) {
412  global $wgSessionInsecureSecrets;
413 
414  list( $encKey, $hmacKey ) = $this->getSecretKeys();
416 
417  // The code for encryption (with OpenSSL) and sealing is taken from
418  // Chris Steipp's OATHAuthUtils class in Extension::OATHAuth.
419 
420  // Encrypt
421  // @todo: import a pure-PHP library for AES instead of doing $wgSessionInsecureSecrets
422  $iv = \MWCryptRand::generate( 16, true );
423  if ( function_exists( 'openssl_encrypt' ) ) {
424  $ciphertext = openssl_encrypt( $serialized, 'aes-256-ctr', $encKey, OPENSSL_RAW_DATA, $iv );
425  if ( $ciphertext === false ) {
426  throw new UnexpectedValueException( 'Encryption failed: ' . openssl_error_string() );
427  }
428  } elseif ( function_exists( 'mcrypt_encrypt' ) ) {
429  $ciphertext = mcrypt_encrypt( 'rijndael-128', $encKey, $serialized, 'ctr', $iv );
430  if ( $ciphertext === false ) {
431  throw new UnexpectedValueException( 'Encryption failed' );
432  }
433  } elseif ( $wgSessionInsecureSecrets ) {
434  $ex = new \Exception( 'No encryption is available, storing data as plain text' );
435  $this->logger->warning( $ex->getMessage(), [ 'exception' => $ex ] );
436  $ciphertext = $serialized;
437  } else {
438  throw new \BadMethodCallException(
439  'Encryption is not available. You really should install the PHP OpenSSL extension, ' .
440  'or failing that the mcrypt extension. But if you really can\'t and you\'re willing ' .
441  'to accept insecure storage of sensitive session data, set ' .
442  '$wgSessionInsecureSecrets = true in LocalSettings.php to make this exception go away.'
443  );
444  }
445 
446  // Seal
447  $sealed = base64_encode( $iv ) . '.' . base64_encode( $ciphertext );
448  $hmac = hash_hmac( 'sha256', $sealed, $hmacKey, true );
449  $encrypted = base64_encode( $hmac ) . '.' . $sealed;
450 
451  // Store
452  $this->set( $key, $encrypted );
453  }
454 
461  public function getSecret( $key, $default = null ) {
462  global $wgSessionInsecureSecrets;
463 
464  // Fetch
465  $encrypted = $this->get( $key, null );
466  if ( $encrypted === null ) {
467  return $default;
468  }
469 
470  // The code for unsealing, checking, and decrypting (with OpenSSL) is
471  // taken from Chris Steipp's OATHAuthUtils class in
472  // Extension::OATHAuth.
473 
474  // Unseal and check
475  $pieces = explode( '.', $encrypted );
476  if ( count( $pieces ) !== 3 ) {
477  $ex = new \Exception( 'Invalid sealed-secret format' );
478  $this->logger->warning( $ex->getMessage(), [ 'exception' => $ex ] );
479  return $default;
480  }
481  list( $hmac, $iv, $ciphertext ) = $pieces;
482  list( $encKey, $hmacKey ) = $this->getSecretKeys();
483  $integCalc = hash_hmac( 'sha256', $iv . '.' . $ciphertext, $hmacKey, true );
484  if ( !hash_equals( $integCalc, base64_decode( $hmac ) ) ) {
485  $ex = new \Exception( 'Sealed secret has been tampered with, aborting.' );
486  $this->logger->warning( $ex->getMessage(), [ 'exception' => $ex ] );
487  return $default;
488  }
489 
490  // Decrypt
491  // @todo: import a pure-PHP library for AES instead of doing $wgSessionInsecureSecrets
492  if ( function_exists( 'openssl_decrypt' ) ) {
493  $serialized = openssl_decrypt(
494  base64_decode( $ciphertext ), 'aes-256-ctr', $encKey, OPENSSL_RAW_DATA, base64_decode( $iv )
495  );
496  if ( $serialized === false ) {
497  $ex = new \Exception( 'Decyption failed: ' . openssl_error_string() );
498  $this->logger->debug( $ex->getMessage(), [ 'exception' => $ex ] );
499  return $default;
500  }
501  } elseif ( function_exists( 'mcrypt_decrypt' ) ) {
502  $serialized = mcrypt_decrypt(
503  'rijndael-128', $encKey, base64_decode( $ciphertext ), 'ctr', base64_decode( $iv )
504  );
505  if ( $serialized === false ) {
506  $ex = new \Exception( 'Decyption failed' );
507  $this->logger->debug( $ex->getMessage(), [ 'exception' => $ex ] );
508  return $default;
509  }
510  } elseif ( $wgSessionInsecureSecrets ) {
511  $ex = new \Exception(
512  'No encryption is available, retrieving data that was stored as plain text'
513  );
514  $this->logger->warning( $ex->getMessage(), [ 'exception' => $ex ] );
515  $serialized = base64_decode( $ciphertext );
516  } else {
517  throw new \BadMethodCallException(
518  'Encryption is not available. You really should install the PHP OpenSSL extension, ' .
519  'or failing that the mcrypt extension. But if you really can\'t and you\'re willing ' .
520  'to accept insecure storage of sensitive session data, set ' .
521  '$wgSessionInsecureSecrets = true in LocalSettings.php to make this exception go away.'
522  );
523  }
524 
526  if ( $value === false && $serialized !== serialize( false ) ) {
527  $value = $default;
528  }
529  return $value;
530  }
531 
539  public function delaySave() {
540  return $this->backend->delaySave();
541  }
542 
546  public function save() {
547  $this->backend->save();
548  }
549 
555  public function count() {
556  $data = &$this->backend->getData();
557  return count( $data );
558  }
559 
560  public function current() {
561  $data = &$this->backend->getData();
562  return current( $data );
563  }
564 
565  public function key() {
566  $data = &$this->backend->getData();
567  return key( $data );
568  }
569 
570  public function next() {
571  $data = &$this->backend->getData();
572  next( $data );
573  }
574 
575  public function rewind() {
576  $data = &$this->backend->getData();
577  reset( $data );
578  }
579 
580  public function valid() {
581  $data = &$this->backend->getData();
582  return key( $data ) !== null;
583  }
584 
589  public function offsetExists( $offset ) {
590  $data = &$this->backend->getData();
591  return isset( $data[$offset] );
592  }
593 
601  public function &offsetGet( $offset ) {
602  $data = &$this->backend->getData();
603  if ( !array_key_exists( $offset, $data ) ) {
604  $ex = new \Exception( "Undefined index (auto-adds to session with a null value): $offset" );
605  $this->logger->debug( $ex->getMessage(), [ 'exception' => $ex ] );
606  }
607  return $data[$offset];
608  }
609 
610  public function offsetSet( $offset, $value ) {
611  $this->set( $offset, $value );
612  }
613 
614  public function offsetUnset( $offset ) {
615  $this->remove( $offset );
616  }
617 
620 }
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
This is the actual workhorse for Session.
magic word the default is to use $key to get the and $key value or $key value text $key value html to format the value $key
Definition: hooks.txt:2321
setForceHTTPS($force)
Set whether HTTPS should be forced.
Definition: Session.php:215
setUser($user)
Set a new user for this session.
Definition: Session.php:191
canSetUser()
Indicate whether the session user info can be changed.
Definition: Session.php:180
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
getLoggedOutTimestamp()
Fetch the "logged out" timestamp.
Definition: Session.php:223
resetAllTokens()
Remove all CSRF tokens from the session.
Definition: Session.php:378
$value
set($key, $value)
Set a value in the session.
Definition: Session.php:310
exists($key)
Test if a value exists in the session.
Definition: Session.php:300
when a variable name is used in a it is silently declared as a new local masking the global
Definition: design.txt:93
setLoggedOutTimestamp($ts)
Set the "logged out" timestamp.
Definition: Session.php:231
getRequest()
Returns the request associated with this session.
Definition: Session.php:156
getAllowedUserRights()
Fetch the rights allowed the user when this session is active.
Definition: Session.php:172
renew()
Renew the session.
Definition: Session.php:265
__construct(SessionBackend $backend, $index, LoggerInterface $logger)
Definition: Session.php:63
getSessionId()
Returns the SessionId object.
Definition: Session.php:86
Value object representing a CSRF token.
Definition: Token.php:32
unserialize($serialized)
Definition: ApiMessage.php:102
delaySave()
Delay automatic saving while multiple updates are being made.
Definition: Session.php:539
shouldForceHTTPS()
Whether HTTPS should be forced.
Definition: Session.php:207
sessionWithRequest(WebRequest $request)
Fetch a copy of this session attached to an alternative WebRequest.
Definition: Session.php:278
getProviderMetadata()
Fetch provider metadata.
Definition: Session.php:240
getProvider()
Fetch the SessionProvider for this session.
Definition: Session.php:102
shouldRememberUser()
Indicate whether the user should be remembered independently of the session ID.
Definition: Session.php:139
Manages data for an an authenticated session.
Definition: Session.php:48
LoggerInterface $logger
Definition: Session.php:56
getSecret($key, $default=null)
Fetch a value from the session that was set with self::setSecret()
Definition: Session.php:461
getId()
Returns the session ID.
Definition: Session.php:77
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 local account $user
Definition: hooks.txt:242
setSessionId(SessionId $sessionId)
Set the session for this request.
Definition: WebRequest.php:711
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
getSecretKeys()
Fetch the secret keys for self::setSecret() and self::getSecret().
Definition: Session.php:386
isPersistent()
Indicate whether this session is persisted across requests.
Definition: Session.php:113
error also a ContextSource you ll probably need to make sure the header is varied on $request
Definition: hooks.txt:2418
resetId()
Changes the session ID.
Definition: Session.php:94
int $index
Session index.
Definition: Session.php:53
resetToken($key= 'default')
Remove a CSRF token from the session.
Definition: Session.php:367
offsetSet($offset, $value)
Definition: Session.php:610
getToken($salt= '', $key= 'default')
Fetch a CSRF token from the session.
Definition: Session.php:340
static generateHex($chars, $forceStrong=false)
Generate a run of (ideally) cryptographically random data and return it in hexadecimal string format...
setRememberUser($remember)
Set whether the user should be remembered independently of the session ID.
Definition: Session.php:148
suggestLoginUsername()
Get a suggested username for the login form.
Definition: Session.php:199
save()
Save the session.
Definition: Session.php:546
clear()
Delete all session data and clear the user (if possible)
Definition: Session.php:247
SessionBackend $backend
Session backend.
Definition: Session.php:50
setSecret($key, $value)
Set a value in the session, encrypted.
Definition: Session.php:411
getUser()
Returns the authenticated user for this session.
Definition: Session.php:164
serialize()
Definition: ApiMessage.php:94
static generate($bytes, $forceStrong=false)
Generate a run of (ideally) cryptographically random data and return it in raw binary form...
foreach($res as $row) $serialized
unpersist()
Make this session not be persisted across requests.
Definition: Session.php:130
persist()
Make this session persisted across requests.
Definition: Session.php:123