26use Psr\Log\LoggerInterface;
48class 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();
245 $this->backend->setLoggedOutTimestamp( $ts );
254 return $this->backend->getProviderMetadata();
261 $data = &$this->backend->getData();
264 $this->backend->dirty();
266 if ( $this->backend->canSetUser() ) {
267 $this->backend->setUser(
new User );
269 $this->backend->save();
277 $this->backend->renew();
290 $request->
setSessionId( $this->backend->getSessionId() );
291 return $this->backend->getSession( $request );
300 public function get( $key, $default = null ) {
301 $data = &$this->backend->getData();
302 return array_key_exists( $key, $data ) ? $data[$key] : $default;
312 $data = &$this->backend->getData();
313 return array_key_exists( $key, $data );
321 public function set( $key, $value ) {
322 $data = &$this->backend->getData();
323 if ( !array_key_exists( $key, $data ) || $data[$key] !== $value ) {
324 $data[$key] = $value;
325 $this->backend->dirty();
333 public function remove( $key ) {
334 $data = &$this->backend->getData();
335 if ( array_key_exists( $key, $data ) ) {
336 unset( $data[$key] );
337 $this->backend->dirty();
348 public function hasToken(
string $key =
'default' ): bool {
349 $secrets = $this->
get(
'wsTokenSecrets' );
350 if ( !is_array( $secrets ) ) {
353 return isset( $secrets[$key] ) && is_string( $secrets[$key] );
366 public function getToken( $salt =
'', $key =
'default' ) {
368 $secrets = $this->
get(
'wsTokenSecrets' );
369 if ( !is_array( $secrets ) ) {
372 if ( isset( $secrets[$key] ) && is_string( $secrets[$key] ) ) {
373 $secret = $secrets[$key];
375 $secret = \MWCryptRand::generateHex( 32 );
376 $secrets[$key] = $secret;
377 $this->
set(
'wsTokenSecrets', $secrets );
380 if ( is_array( $salt ) ) {
381 $salt = implode(
'|', $salt );
383 return new Token( $secret, (
string)$salt, $new );
394 $secrets = $this->
get(
'wsTokenSecrets' );
395 if ( is_array( $secrets ) && isset( $secrets[$key] ) ) {
396 unset( $secrets[$key] );
397 $this->
set(
'wsTokenSecrets', $secrets );
405 $this->
remove(
'wsTokenSecrets' );
416 $userSecret = $this->
get(
'wsSessionSecret', null );
417 if ( $userSecret ===
null ) {
418 $userSecret = \MWCryptRand::generateHex( 32 );
419 $this->
set(
'wsSessionSecret', $userSecret );
421 $iterations = $this->
get(
'wsSessionPbkdf2Iterations', null );
422 if ( $iterations ===
null ) {
424 $this->
set(
'wsSessionPbkdf2Iterations', $iterations );
427 $keymats = hash_pbkdf2(
'sha256', $wikiSecret, $userSecret, $iterations, 64,
true );
429 substr( $keymats, 0, 32 ),
430 substr( $keymats, 32, 32 ),
441 if ( self::$encryptionAlgorithm ===
null ) {
442 if ( function_exists(
'openssl_encrypt' ) ) {
443 $methods = openssl_get_cipher_methods();
444 if ( in_array(
'aes-256-ctr', $methods,
true ) ) {
445 self::$encryptionAlgorithm = [
'openssl',
'aes-256-ctr' ];
446 return self::$encryptionAlgorithm;
448 if ( in_array(
'aes-256-cbc', $methods,
true ) ) {
449 self::$encryptionAlgorithm = [
'openssl',
'aes-256-cbc' ];
450 return self::$encryptionAlgorithm;
456 self::$encryptionAlgorithm = [
'insecure' ];
457 return self::$encryptionAlgorithm;
460 throw new \BadMethodCallException(
461 'Encryption is not available. You really should install the PHP OpenSSL extension. ' .
462 'But if you really can\'t and you\'re willing ' .
463 'to accept insecure storage of sensitive session data, set ' .
464 '$wgSessionInsecureSecrets = true in LocalSettings.php to make this exception go away.'
468 return self::$encryptionAlgorithm;
480 list( $encKey, $hmacKey ) = $this->getSecretKeys();
488 $iv = random_bytes( 16 );
489 $algorithm = self::getEncryptionAlgorithm();
490 switch ( $algorithm[0] ) {
492 $ciphertext = openssl_encrypt(
$serialized, $algorithm[1], $encKey, OPENSSL_RAW_DATA, $iv );
493 if ( $ciphertext ===
false ) {
494 throw new \UnexpectedValueException(
'Encryption failed: ' . openssl_error_string() );
498 $ex = new \Exception(
'No encryption is available, storing data as plain text' );
499 $this->logger->warning( $ex->getMessage(), [
'exception' => $ex ] );
503 throw new \LogicException(
'invalid algorithm' );
507 $sealed = base64_encode( $iv ) .
'.' . base64_encode( $ciphertext );
508 $hmac = hash_hmac(
'sha256', $sealed, $hmacKey,
true );
509 $encrypted = base64_encode( $hmac ) .
'.' . $sealed;
512 $this->
set( $key, $encrypted );
523 $encrypted = $this->
get( $key, null );
524 if ( $encrypted ===
null ) {
533 $pieces = explode(
'.', $encrypted, 4 );
534 if ( count( $pieces ) !== 3 ) {
535 $ex = new \Exception(
'Invalid sealed-secret format' );
536 $this->logger->warning( $ex->getMessage(), [
'exception' => $ex ] );
539 list( $hmac, $iv, $ciphertext ) = $pieces;
540 list( $encKey, $hmacKey ) = $this->getSecretKeys();
541 $integCalc = hash_hmac(
'sha256', $iv .
'.' . $ciphertext, $hmacKey,
true );
542 if ( !hash_equals( $integCalc, base64_decode( $hmac ) ) ) {
543 $ex = new \Exception(
'Sealed secret has been tampered with, aborting.' );
544 $this->logger->warning( $ex->getMessage(), [
'exception' => $ex ] );
549 $algorithm = self::getEncryptionAlgorithm();
550 switch ( $algorithm[0] ) {
552 $serialized = openssl_decrypt( base64_decode( $ciphertext ), $algorithm[1], $encKey,
553 OPENSSL_RAW_DATA, base64_decode( $iv ) );
555 $ex = new \Exception(
'Decyption failed: ' . openssl_error_string() );
556 $this->logger->debug( $ex->getMessage(), [
'exception' => $ex ] );
561 $ex = new \Exception(
562 'No encryption is available, retrieving data that was stored as plain text'
564 $this->logger->warning( $ex->getMessage(), [
'exception' => $ex ] );
568 throw new \LogicException(
'invalid algorithm' );
586 return $this->backend->delaySave();
594 $this->backend->save();
604 $data = &$this->backend->getData();
605 return count( $data );
609 #[\ReturnTypeWillChange]
611 $data = &$this->backend->getData();
612 return current( $data );
616 #[\ReturnTypeWillChange]
618 $data = &$this->backend->getData();
624 $data = &$this->backend->getData();
630 $data = &$this->backend->getData();
636 $data = &$this->backend->getData();
637 return key( $data ) !==
null;
646 $data = &$this->backend->getData();
647 return isset( $data[$offset] );
658 #[\ReturnTypeWillChange]
660 $data = &$this->backend->getData();
661 if ( !array_key_exists( $offset, $data ) ) {
662 $ex = new \Exception(
"Undefined index (auto-adds to session with a null value): $offset" );
663 $this->logger->debug( $ex->getMessage(), [
'exception' => $ex ] );
665 return $data[$offset];
670 $this->set( $offset, $value );
675 $this->remove( $offset );
unserialize( $serialized)
$wgSecretKey
This should always be customised in LocalSettings.php.
$wgSessionPbkdf2Iterations
Number of internal PBKDF2 iterations to use when deriving session secrets.
$wgSessionSecret
Secret for session storage.
$wgSessionInsecureSecrets
If for some reason you can't install the PHP OpenSSL extension, you can set this to true to make Medi...
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
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