26 use Psr\Log\LoggerInterface;
48 final class Session implements \Countable, \Iterator, \ArrayAccess {
73 $this->backend->deregisterSession( $this->index );
81 return $this->backend->getId();
90 return $this->backend->getSessionId();
98 return $this->backend->resetId();
106 return $this->backend->getProvider();
117 return $this->backend->isPersistent();
127 $this->backend->persist();
139 $this->backend->unpersist();
148 return $this->backend->shouldRememberUser();
157 $this->backend->setRememberUser( $remember );
165 return $this->backend->getRequest( $this->index );
173 return $this->backend->getUser();
181 return $this->backend->getAllowedUserRights();
189 return $this->backend->canSetUser();
200 $this->backend->setUser(
$user );
208 return $this->backend->suggestLoginUsername( $this->index );
219 return $this->backend->shouldForceHTTPS();
230 $this->backend->setForceHTTPS( $force );
238 return $this->backend->getLoggedOutTimestamp();
246 $this->backend->setLoggedOutTimestamp( $ts );
255 return $this->backend->getProviderMetadata();
262 $data = &$this->backend->getData();
265 $this->backend->dirty();
267 if ( $this->backend->canSetUser() ) {
268 $this->backend->setUser(
new User );
270 $this->backend->save();
280 $this->backend->renew();
293 $request->setSessionId( $this->backend->getSessionId() );
294 return $this->backend->getSession(
$request );
303 public function get( $key, $default = null ) {
304 $data = &$this->backend->getData();
305 return array_key_exists( $key, $data ) ? $data[$key] : $default;
315 $data = &$this->backend->getData();
316 return array_key_exists( $key, $data );
325 $data = &$this->backend->getData();
326 if ( !array_key_exists( $key, $data ) || $data[$key] !==
$value ) {
328 $this->backend->dirty();
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();
354 public function getToken( $salt =
'', $key =
'default' ) {
356 $secrets = $this->
get(
'wsTokenSecrets' );
357 if ( !is_array( $secrets ) ) {
360 if ( isset( $secrets[$key] ) && is_string( $secrets[$key] ) ) {
361 $secret = $secrets[$key];
364 $secrets[$key] = $secret;
365 $this->
set(
'wsTokenSecrets', $secrets );
368 if ( is_array( $salt ) ) {
369 $salt = implode(
'|', $salt );
371 return new Token( $secret, (
string)$salt, $new );
382 $secrets = $this->
get(
'wsTokenSecrets' );
383 if ( is_array( $secrets ) && isset( $secrets[$key] ) ) {
384 unset( $secrets[$key] );
385 $this->
set(
'wsTokenSecrets', $secrets );
393 $this->
remove(
'wsTokenSecrets' );
404 $userSecret = $this->
get(
'wsSessionSecret', null );
405 if ( $userSecret ===
null ) {
407 $this->
set(
'wsSessionSecret', $userSecret );
409 $iterations = $this->
get(
'wsSessionPbkdf2Iterations', null );
410 if ( $iterations ===
null ) {
412 $this->
set(
'wsSessionPbkdf2Iterations', $iterations );
415 $keymats = hash_pbkdf2(
'sha256', $wikiSecret, $userSecret, $iterations, 64,
true );
417 substr( $keymats, 0, 32 ),
418 substr( $keymats, 32, 32 ),
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' ];
436 if ( in_array(
'aes-256-cbc', $methods,
true ) ) {
437 self::$encryptionAlgorithm = [
'openssl',
'aes-256-cbc' ];
442 if ( function_exists(
'mcrypt_encrypt' )
443 && in_array(
'rijndael-128', mcrypt_list_algorithms(),
true )
445 $modes = mcrypt_list_modes();
446 if ( in_array(
'ctr', $modes,
true ) ) {
447 self::$encryptionAlgorithm = [
'mcrypt',
'rijndael-128',
'ctr' ];
450 if ( in_array(
'cbc', $modes,
true ) ) {
451 self::$encryptionAlgorithm = [
'mcrypt',
'rijndael-128',
'cbc' ];
458 self::$encryptionAlgorithm = [
'insecure' ];
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.'
492 switch ( $algorithm[0] ) {
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() );
501 $blocksize = mcrypt_get_block_size( $algorithm[1], $algorithm[2] );
502 $pad = $blocksize - ( strlen(
$serialized ) % $blocksize );
505 $ciphertext = mcrypt_encrypt( $algorithm[1], $encKey,
$serialized, $algorithm[2], $iv );
506 if ( $ciphertext ===
false ) {
507 throw new \UnexpectedValueException(
'Encryption failed' );
511 $ex = new \Exception(
'No encryption is available, storing data as plain text' );
512 $this->logger->warning( $ex->getMessage(), [
'exception' => $ex ] );
516 throw new \LogicException(
'invalid algorithm' );
520 $sealed = base64_encode( $iv ) .
'.' . base64_encode( $ciphertext );
521 $hmac = hash_hmac(
'sha256', $sealed, $hmacKey,
true );
522 $encrypted = base64_encode( $hmac ) .
'.' . $sealed;
525 $this->
set( $key, $encrypted );
536 $encrypted = $this->
get( $key, null );
537 if ( $encrypted ===
null ) {
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 ] );
552 list( $hmac, $iv, $ciphertext ) = $pieces;
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 ] );
563 switch ( $algorithm[0] ) {
565 $serialized = openssl_decrypt( base64_decode( $ciphertext ), $algorithm[1], $encKey,
566 OPENSSL_RAW_DATA, base64_decode( $iv ) );
568 $ex = new \Exception(
'Decyption failed: ' . openssl_error_string() );
569 $this->logger->debug( $ex->getMessage(), [
'exception' => $ex ] );
574 $serialized = mcrypt_decrypt( $algorithm[1], $encKey, base64_decode( $ciphertext ),
575 $algorithm[2], base64_decode( $iv ) );
577 $ex = new \Exception(
'Decyption failed' );
578 $this->logger->debug( $ex->getMessage(), [
'exception' => $ex ] );
587 $ex = new \Exception(
588 'No encryption is available, retrieving data that was stored as plain text'
590 $this->logger->warning( $ex->getMessage(), [
'exception' => $ex ] );
594 throw new \LogicException(
'invalid algorithm' );
612 return $this->backend->delaySave();
622 $this->backend->save();
632 $data = &$this->backend->getData();
633 return count( $data );
638 $data = &$this->backend->getData();
644 $data = &$this->backend->getData();
650 $data = &$this->backend->getData();
656 $data = &$this->backend->getData();
662 $data = &$this->backend->getData();
663 return key( $data ) !==
null;
672 $data = &$this->backend->getData();
673 return isset( $data[$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 ] );
690 return $data[$offset];
695 $this->
set( $offset,
$value );
700 $this->
remove( $offset );