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 );
318 public function set( $key, $value ) {
319 $data = &$this->backend->getData();
320 if ( !array_key_exists( $key, $data ) || $data[$key] !== $value ) {
321 $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' ];
438 self::$encryptionAlgorithm = [
'insecure' ];
442 throw new \BadMethodCallException(
443 'Encryption is not available. You really should install the PHP OpenSSL extension. ' .
444 'But if you really can\'t and you\'re willing ' .
445 'to accept insecure storage of sensitive session data, set ' .
446 '$wgSessionInsecureSecrets = true in LocalSettings.php to make this exception go away.'
470 $iv = random_bytes( 16 );
472 switch ( $algorithm[0] ) {
474 $ciphertext = openssl_encrypt(
$serialized, $algorithm[1], $encKey, OPENSSL_RAW_DATA, $iv );
475 if ( $ciphertext ===
false ) {
476 throw new \UnexpectedValueException(
'Encryption failed: ' . openssl_error_string() );
480 $ex = new \Exception(
'No encryption is available, storing data as plain text' );
481 $this->logger->warning( $ex->getMessage(), [
'exception' => $ex ] );
485 throw new \LogicException(
'invalid algorithm' );
489 $sealed = base64_encode( $iv ) .
'.' . base64_encode( $ciphertext );
490 $hmac = hash_hmac(
'sha256', $sealed, $hmacKey,
true );
491 $encrypted = base64_encode( $hmac ) .
'.' . $sealed;
494 $this->
set( $key, $encrypted );
505 $encrypted = $this->
get( $key, null );
506 if ( $encrypted ===
null ) {
515 $pieces = explode(
'.', $encrypted, 4 );
516 if (
count( $pieces ) !== 3 ) {
517 $ex = new \Exception(
'Invalid sealed-secret format' );
518 $this->logger->warning( $ex->getMessage(), [
'exception' => $ex ] );
521 list( $hmac, $iv, $ciphertext ) = $pieces;
523 $integCalc = hash_hmac(
'sha256', $iv .
'.' . $ciphertext, $hmacKey,
true );
524 if ( !hash_equals( $integCalc, base64_decode( $hmac ) ) ) {
525 $ex = new \Exception(
'Sealed secret has been tampered with, aborting.' );
526 $this->logger->warning( $ex->getMessage(), [
'exception' => $ex ] );
532 switch ( $algorithm[0] ) {
534 $serialized = openssl_decrypt( base64_decode( $ciphertext ), $algorithm[1], $encKey,
535 OPENSSL_RAW_DATA, base64_decode( $iv ) );
537 $ex = new \Exception(
'Decyption failed: ' . openssl_error_string() );
538 $this->logger->debug( $ex->getMessage(), [
'exception' => $ex ] );
543 $ex = new \Exception(
544 'No encryption is available, retrieving data that was stored as plain text'
546 $this->logger->warning( $ex->getMessage(), [
'exception' => $ex ] );
550 throw new \LogicException(
'invalid algorithm' );
568 return $this->backend->delaySave();
578 $this->backend->save();
588 $data = &$this->backend->getData();
589 return count( $data );
594 $data = &$this->backend->getData();
600 $data = &$this->backend->getData();
606 $data = &$this->backend->getData();
612 $data = &$this->backend->getData();
618 $data = &$this->backend->getData();
619 return key( $data ) !==
null;
628 $data = &$this->backend->getData();
629 return isset( $data[$offset] );
641 $data = &$this->backend->getData();
642 if ( !array_key_exists( $offset, $data ) ) {
643 $ex = new \Exception(
"Undefined index (auto-adds to session with a null value): $offset" );
644 $this->logger->debug( $ex->getMessage(), [
'exception' => $ex ] );
646 return $data[$offset];
651 $this->
set( $offset, $value );
656 $this->
remove( $offset );