28use Psr\Log\LoggerInterface;
50class Session implements \Countable, \Iterator, \ArrayAccess {
52 private static $encryptionAlgorithm =
null;
69 $this->backend = $backend;
70 $this->index = $index;
71 $this->logger = $logger;
75 $this->backend->deregisterSession( $this->index );
83 return $this->backend->getId();
92 return $this->backend->getSessionId();
100 return $this->backend->resetId();
108 return $this->backend->getProvider();
119 return $this->backend->isPersistent();
129 $this->backend->persist();
141 $this->backend->unpersist();
150 return $this->backend->shouldRememberUser();
159 $this->backend->setRememberUser( $remember );
167 return $this->backend->getRequest( $this->index );
175 return $this->backend->getUser();
183 return $this->backend->getAllowedUserRights();
191 return $this->backend->canSetUser();
202 $this->backend->setUser( $user );
210 return $this->backend->suggestLoginUsername( $this->index );
221 return $this->backend->shouldForceHTTPS();
232 $this->backend->setForceHTTPS( $force );
240 return $this->backend->getLoggedOutTimestamp();
247 $this->backend->setLoggedOutTimestamp( $ts );
256 return $this->backend->getProviderMetadata();
263 $data = &$this->backend->getData();
266 $this->backend->dirty();
268 if ( $this->backend->canSetUser() ) {
269 $this->backend->setUser(
new User );
271 $this->backend->save();
279 $this->backend->renew();
292 $request->
setSessionId( $this->backend->getSessionId() );
293 return $this->backend->getSession( $request );
302 public function get( $key, $default = null ) {
303 $data = &$this->backend->getData();
304 return array_key_exists( $key, $data ) ? $data[$key] : $default;
314 $data = &$this->backend->getData();
315 return array_key_exists( $key, $data );
323 public function set( $key, $value ) {
324 $data = &$this->backend->getData();
325 if ( !array_key_exists( $key, $data ) || $data[$key] !== $value ) {
326 $data[$key] = $value;
327 $this->backend->dirty();
335 public function remove( $key ) {
336 $data = &$this->backend->getData();
337 if ( array_key_exists( $key, $data ) ) {
338 unset( $data[$key] );
339 $this->backend->dirty();
350 public function hasToken(
string $key =
'default' ): bool {
351 $secrets = $this->
get(
'wsTokenSecrets' );
352 if ( !is_array( $secrets ) ) {
355 return isset( $secrets[$key] ) && is_string( $secrets[$key] );
368 public function getToken( $salt =
'', $key =
'default' ) {
370 $secrets = $this->
get(
'wsTokenSecrets' );
371 if ( !is_array( $secrets ) ) {
374 if ( isset( $secrets[$key] ) && is_string( $secrets[$key] ) ) {
375 $secret = $secrets[$key];
377 $secret = \MWCryptRand::generateHex( 32 );
378 $secrets[$key] = $secret;
379 $this->
set(
'wsTokenSecrets', $secrets );
382 if ( is_array( $salt ) ) {
383 $salt = implode(
'|', $salt );
385 return new Token( $secret, (
string)$salt, $new );
396 $secrets = $this->
get(
'wsTokenSecrets' );
397 if ( is_array( $secrets ) && isset( $secrets[$key] ) ) {
398 unset( $secrets[$key] );
399 $this->
set(
'wsTokenSecrets', $secrets );
407 $this->
remove(
'wsTokenSecrets' );
414 private function getSecretKeys() {
419 $wikiSecret = $sessionSecret ?: $secretKey;
420 $userSecret = $this->
get(
'wsSessionSecret', null );
421 if ( $userSecret ===
null ) {
422 $userSecret = \MWCryptRand::generateHex( 32 );
423 $this->
set(
'wsSessionSecret', $userSecret );
425 $iterations = $this->
get(
'wsSessionPbkdf2Iterations', null );
426 if ( $iterations ===
null ) {
427 $iterations = $sessionPbkdf2Iterations;
428 $this->
set(
'wsSessionPbkdf2Iterations', $iterations );
431 $keymats = hash_pbkdf2(
'sha256', $wikiSecret, $userSecret, $iterations, 64,
true );
433 substr( $keymats, 0, 32 ),
434 substr( $keymats, 32, 32 ),
442 private static function getEncryptionAlgorithm() {
446 if ( self::$encryptionAlgorithm ===
null ) {
447 if ( function_exists(
'openssl_encrypt' ) ) {
448 $methods = openssl_get_cipher_methods();
449 if ( in_array(
'aes-256-ctr', $methods,
true ) ) {
450 self::$encryptionAlgorithm = [
'openssl',
'aes-256-ctr' ];
451 return self::$encryptionAlgorithm;
453 if ( in_array(
'aes-256-cbc', $methods,
true ) ) {
454 self::$encryptionAlgorithm = [
'openssl',
'aes-256-cbc' ];
455 return self::$encryptionAlgorithm;
459 if ( $sessionInsecureSecrets ) {
461 self::$encryptionAlgorithm = [
'insecure' ];
462 return self::$encryptionAlgorithm;
465 throw new \BadMethodCallException(
466 'Encryption is not available. You really should install the PHP OpenSSL extension. ' .
467 'But if you really can\'t and you\'re willing ' .
468 'to accept insecure storage of sensitive session data, set ' .
469 '$wgSessionInsecureSecrets = true in LocalSettings.php to make this exception go away.'
473 return self::$encryptionAlgorithm;
485 list( $encKey, $hmacKey ) = $this->getSecretKeys();
493 $iv = random_bytes( 16 );
494 $algorithm = self::getEncryptionAlgorithm();
495 switch ( $algorithm[0] ) {
497 $ciphertext = openssl_encrypt(
$serialized, $algorithm[1], $encKey, OPENSSL_RAW_DATA, $iv );
498 if ( $ciphertext ===
false ) {
499 throw new \UnexpectedValueException(
'Encryption failed: ' . openssl_error_string() );
503 $ex = new \Exception(
'No encryption is available, storing data as plain text' );
504 $this->logger->warning( $ex->getMessage(), [
'exception' => $ex ] );
508 throw new \LogicException(
'invalid algorithm' );
512 $sealed = base64_encode( $iv ) .
'.' . base64_encode( $ciphertext );
513 $hmac = hash_hmac(
'sha256', $sealed, $hmacKey,
true );
514 $encrypted = base64_encode( $hmac ) .
'.' . $sealed;
517 $this->
set( $key, $encrypted );
528 $encrypted = $this->
get( $key, null );
529 if ( $encrypted ===
null ) {
538 $pieces = explode(
'.', $encrypted, 4 );
539 if ( count( $pieces ) !== 3 ) {
540 $ex = new \Exception(
'Invalid sealed-secret format' );
541 $this->logger->warning( $ex->getMessage(), [
'exception' => $ex ] );
544 list( $hmac, $iv, $ciphertext ) = $pieces;
545 list( $encKey, $hmacKey ) = $this->getSecretKeys();
546 $integCalc = hash_hmac(
'sha256', $iv .
'.' . $ciphertext, $hmacKey,
true );
547 if ( !hash_equals( $integCalc, base64_decode( $hmac ) ) ) {
548 $ex = new \Exception(
'Sealed secret has been tampered with, aborting.' );
549 $this->logger->warning( $ex->getMessage(), [
'exception' => $ex ] );
554 $algorithm = self::getEncryptionAlgorithm();
555 switch ( $algorithm[0] ) {
557 $serialized = openssl_decrypt( base64_decode( $ciphertext ), $algorithm[1], $encKey,
558 OPENSSL_RAW_DATA, base64_decode( $iv ) );
560 $ex = new \Exception(
'Decyption failed: ' . openssl_error_string() );
561 $this->logger->debug( $ex->getMessage(), [
'exception' => $ex ] );
566 $ex = new \Exception(
567 'No encryption is available, retrieving data that was stored as plain text'
569 $this->logger->warning( $ex->getMessage(), [
'exception' => $ex ] );
573 throw new \LogicException(
'invalid algorithm' );
591 return $this->backend->delaySave();
599 $this->backend->save();
609 $data = &$this->backend->getData();
610 return count( $data );
614 #[\ReturnTypeWillChange]
616 $data = &$this->backend->getData();
617 return current( $data );
621 #[\ReturnTypeWillChange]
623 $data = &$this->backend->getData();
629 $data = &$this->backend->getData();
635 $data = &$this->backend->getData();
641 $data = &$this->backend->getData();
642 return key( $data ) !==
null;
651 $data = &$this->backend->getData();
652 return isset( $data[$offset] );
663 #[\ReturnTypeWillChange]
665 $data = &$this->backend->getData();
666 if ( !array_key_exists( $offset, $data ) ) {
667 $ex = new \Exception(
"Undefined index (auto-adds to session with a null value): $offset" );
668 $this->logger->debug( $ex->getMessage(), [
'exception' => $ex ] );
670 return $data[$offset];
675 $this->set( $offset, $value );
680 $this->remove( $offset );
unserialize( $serialized)
A class containing constants representing the names of configuration variables.
const SessionSecret
Name constant for the SessionSecret setting, for use with Config::get()
const SessionPbkdf2Iterations
Name constant for the SessionPbkdf2Iterations setting, for use with Config::get()
const SessionInsecureSecrets
Name constant for the SessionInsecureSecrets setting, for use with Config::get()
const SecretKey
Name constant for the SecretKey setting, for use with Config::get()
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form stripping il...
setSessionId(SessionId $sessionId)
Set the session for this request.
foreach( $res as $row) $serialized