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 );
216 return $this->backend->shouldForceHTTPS();
224 $this->backend->setForceHTTPS( $force );
232 return $this->backend->getLoggedOutTimestamp();
240 $this->backend->setLoggedOutTimestamp( $ts );
249 return $this->backend->getProviderMetadata();
256 $data = &$this->backend->getData();
259 $this->backend->dirty();
261 if ( $this->backend->canSetUser() ) {
262 $this->backend->setUser(
new User );
264 $this->backend->save();
274 $this->backend->renew();
287 $request->setSessionId( $this->backend->getSessionId() );
288 return $this->backend->getSession(
$request );
297 public function get( $key, $default = null ) {
298 $data = &$this->backend->getData();
299 return array_key_exists( $key, $data ) ? $data[$key] : $default;
309 $data = &$this->backend->getData();
310 return array_key_exists( $key, $data );
319 $data = &$this->backend->getData();
320 if ( !array_key_exists( $key, $data ) || $data[$key] !==
$value ) {
322 $this->backend->dirty();
330 public function remove( $key ) {
331 $data = &$this->backend->getData();
332 if ( array_key_exists( $key, $data ) ) {
333 unset( $data[$key] );
334 $this->backend->dirty();
348 public function getToken( $salt =
'', $key =
'default' ) {
350 $secrets = $this->
get(
'wsTokenSecrets' );
351 if ( !is_array( $secrets ) ) {
354 if ( isset( $secrets[$key] ) && is_string( $secrets[$key] ) ) {
355 $secret = $secrets[$key];
358 $secrets[$key] = $secret;
359 $this->
set(
'wsTokenSecrets', $secrets );
362 if ( is_array( $salt ) ) {
363 $salt = implode(
'|', $salt );
365 return new Token( $secret, (
string)$salt, $new );
376 $secrets = $this->
get(
'wsTokenSecrets' );
377 if ( is_array( $secrets ) && isset( $secrets[$key] ) ) {
378 unset( $secrets[$key] );
379 $this->
set(
'wsTokenSecrets', $secrets );
387 $this->
remove(
'wsTokenSecrets' );
398 $userSecret = $this->
get(
'wsSessionSecret', null );
399 if ( $userSecret ===
null ) {
401 $this->
set(
'wsSessionSecret', $userSecret );
403 $iterations = $this->
get(
'wsSessionPbkdf2Iterations', null );
404 if ( $iterations ===
null ) {
406 $this->
set(
'wsSessionPbkdf2Iterations', $iterations );
409 $keymats = hash_pbkdf2(
'sha256', $wikiSecret, $userSecret, $iterations, 64,
true );
411 substr( $keymats, 0, 32 ),
412 substr( $keymats, 32, 32 ),
423 if ( self::$encryptionAlgorithm ===
null ) {
424 if ( function_exists(
'openssl_encrypt' ) ) {
425 $methods = openssl_get_cipher_methods();
426 if ( in_array(
'aes-256-ctr', $methods,
true ) ) {
427 self::$encryptionAlgorithm = [
'openssl',
'aes-256-ctr' ];
430 if ( in_array(
'aes-256-cbc', $methods,
true ) ) {
431 self::$encryptionAlgorithm = [
'openssl',
'aes-256-cbc' ];
436 if ( function_exists(
'mcrypt_encrypt' )
437 && in_array(
'rijndael-128', mcrypt_list_algorithms(),
true )
439 $modes = mcrypt_list_modes();
440 if ( in_array(
'ctr', $modes,
true ) ) {
441 self::$encryptionAlgorithm = [
'mcrypt',
'rijndael-128',
'ctr' ];
444 if ( in_array(
'cbc', $modes,
true ) ) {
445 self::$encryptionAlgorithm = [
'mcrypt',
'rijndael-128',
'cbc' ];
452 self::$encryptionAlgorithm = [
'insecure' ];
456 throw new \BadMethodCallException(
457 'Encryption is not available. You really should install the PHP OpenSSL extension, ' .
458 'or failing that the mcrypt extension. But if you really can\'t and you\'re willing ' .
459 'to accept insecure storage of sensitive session data, set ' .
460 '$wgSessionInsecureSecrets = true in LocalSettings.php to make this exception go away.'
484 $iv = random_bytes( 16 );
486 switch ( $algorithm[0] ) {
488 $ciphertext = openssl_encrypt(
$serialized, $algorithm[1], $encKey, OPENSSL_RAW_DATA, $iv );
489 if ( $ciphertext ===
false ) {
490 throw new \UnexpectedValueException(
'Encryption failed: ' . openssl_error_string() );
495 $blocksize = mcrypt_get_block_size( $algorithm[1], $algorithm[2] );
496 $pad = $blocksize - ( strlen(
$serialized ) % $blocksize );
499 $ciphertext = mcrypt_encrypt( $algorithm[1], $encKey,
$serialized, $algorithm[2], $iv );
500 if ( $ciphertext ===
false ) {
501 throw new \UnexpectedValueException(
'Encryption failed' );
505 $ex = new \Exception(
'No encryption is available, storing data as plain text' );
506 $this->logger->warning( $ex->getMessage(), [
'exception' => $ex ] );
510 throw new \LogicException(
'invalid algorithm' );
514 $sealed = base64_encode( $iv ) .
'.' . base64_encode( $ciphertext );
515 $hmac = hash_hmac(
'sha256', $sealed, $hmacKey,
true );
516 $encrypted = base64_encode( $hmac ) .
'.' . $sealed;
519 $this->
set( $key, $encrypted );
530 $encrypted = $this->
get( $key, null );
531 if ( $encrypted ===
null ) {
540 $pieces = explode(
'.', $encrypted );
541 if (
count( $pieces ) !== 3 ) {
542 $ex = new \Exception(
'Invalid sealed-secret format' );
543 $this->logger->warning( $ex->getMessage(), [
'exception' => $ex ] );
546 list( $hmac, $iv, $ciphertext ) = $pieces;
548 $integCalc = hash_hmac(
'sha256', $iv .
'.' . $ciphertext, $hmacKey,
true );
549 if ( !hash_equals( $integCalc, base64_decode( $hmac ) ) ) {
550 $ex = new \Exception(
'Sealed secret has been tampered with, aborting.' );
551 $this->logger->warning( $ex->getMessage(), [
'exception' => $ex ] );
557 switch ( $algorithm[0] ) {
559 $serialized = openssl_decrypt( base64_decode( $ciphertext ), $algorithm[1], $encKey,
560 OPENSSL_RAW_DATA, base64_decode( $iv ) );
562 $ex = new \Exception(
'Decyption failed: ' . openssl_error_string() );
563 $this->logger->debug( $ex->getMessage(), [
'exception' => $ex ] );
568 $serialized = mcrypt_decrypt( $algorithm[1], $encKey, base64_decode( $ciphertext ),
569 $algorithm[2], base64_decode( $iv ) );
571 $ex = new \Exception(
'Decyption failed' );
572 $this->logger->debug( $ex->getMessage(), [
'exception' => $ex ] );
581 $ex = new \Exception(
582 'No encryption is available, retrieving data that was stored as plain text'
584 $this->logger->warning( $ex->getMessage(), [
'exception' => $ex ] );
588 throw new \LogicException(
'invalid algorithm' );
606 return $this->backend->delaySave();
616 $this->backend->save();
626 $data = &$this->backend->getData();
627 return count( $data );
632 $data = &$this->backend->getData();
638 $data = &$this->backend->getData();
644 $data = &$this->backend->getData();
650 $data = &$this->backend->getData();
656 $data = &$this->backend->getData();
657 return key( $data ) !==
null;
666 $data = &$this->backend->getData();
667 return isset( $data[$offset] );
679 $data = &$this->backend->getData();
680 if ( !array_key_exists( $offset, $data ) ) {
681 $ex = new \Exception(
"Undefined index (auto-adds to session with a null value): $offset" );
682 $this->logger->debug( $ex->getMessage(), [
'exception' => $ex ] );
684 return $data[$offset];
689 $this->
set( $offset,
$value );
694 $this->
remove( $offset );