27 use Psr\Log\LoggerInterface;
30 use Wikimedia\AtEase\AtEase;
130 $this->usePhpSessionHandling = $phpSessionHandling !==
'disable';
133 throw new \InvalidArgumentException(
134 "Refusing to create session for unverified user {$info->getUserInfo()}"
138 throw new \InvalidArgumentException(
'Cannot create session without a provider' );
141 throw new \InvalidArgumentException(
'SessionId and SessionInfo don\'t match' );
156 if ( !is_array(
$blob ) ||
157 !isset(
$blob[
'metadata'] ) || !is_array(
$blob[
'metadata'] ) ||
158 !isset(
$blob[
'data'] ) || !is_array(
$blob[
'data'] )
161 $this->dataDirty =
true;
162 $this->metaDirty =
true;
163 $this->logger->debug(
164 'SessionBackend "{session}" is unsaved, marking dirty in constructor',
166 'session' => $this->
id,
169 $this->data =
$blob[
'data'];
170 if ( isset(
$blob[
'metadata'][
'loggedOut'] ) ) {
171 $this->loggedOut = (int)
$blob[
'metadata'][
'loggedOut'];
173 if ( isset(
$blob[
'metadata'][
'expires'] ) ) {
174 $this->expires = (int)
$blob[
'metadata'][
'expires'];
176 $this->metaDirty =
true;
177 $this->logger->debug(
178 'SessionBackend "{session}" metadata dirty due to missing expiration timestamp',
180 'session' => $this->
id,
184 $this->dataHash = md5(
serialize( $this->data ) );
194 $this->requests[$index] = $request;
195 $session =
new Session( $this, $index, $this->logger );
205 unset( $this->requests[$index] );
206 if ( !$this->
shutdown && !count( $this->requests ) ) {
208 $this->provider->getManager()->deregisterSessionBackend( $this );
243 if ( $this->provider->persistsSessionId() ) {
244 $oldId = (string)$this->
id;
245 $restart = $this->usePhpSessionHandling && $oldId === session_id() &&
251 session_write_close();
254 $this->provider->getManager()->changeBackendId( $this );
255 $this->provider->sessionIdWasReset( $this, $oldId );
256 $this->metaDirty =
true;
257 $this->logger->debug(
258 'SessionBackend "{session}" metadata dirty due to ID reset (formerly "{oldId}")',
260 'session' => $this->
id,
265 session_id( (
string)$this->
id );
266 AtEase::quietCall(
'session_start' );
272 $this->store->delete( $this->store->makeKey(
'MWSession', $oldId ) );
306 $this->forcePersist =
true;
307 $this->metaDirty =
true;
308 $this->logger->debug(
309 'SessionBackend "{session}" force-persist due to persist()',
311 'session' => $this->
id,
326 session_id() === (
string)$this->
id
328 $this->logger->debug(
329 'SessionBackend "{session}" Closing PHP session for unpersist',
330 [
'session' => $this->
id ]
332 session_write_close();
337 $this->forcePersist =
true;
338 $this->metaDirty =
true;
342 $this->store->delete( $this->store->makeKey(
'MWSession', (
string)$this->id ) );
363 if ( $this->remember !== (
bool)
$remember ) {
365 $this->metaDirty =
true;
366 $this->logger->debug(
367 'SessionBackend "{session}" metadata dirty due to remember-user change',
369 'session' => $this->
id,
381 if ( !isset( $this->requests[$index] ) ) {
382 throw new \InvalidArgumentException(
'Invalid session index' );
384 return $this->requests[$index];
400 return $this->provider->getAllowedUserRights( $this );
408 return $this->provider->canChangeUser();
420 throw new \BadMethodCallException(
421 'Cannot set user on this session; check $session->canSetUser() first'
426 $this->metaDirty =
true;
427 $this->logger->debug(
428 'SessionBackend "{session}" metadata dirty due to user change',
430 'session' => $this->
id,
441 if ( !isset( $this->requests[$index] ) ) {
442 throw new \InvalidArgumentException(
'Invalid session index' );
444 return $this->provider->suggestLoginUsername( $this->requests[$index] );
460 if ( $this->forceHTTPS !== (
bool)$force ) {
461 $this->forceHTTPS = (bool)$force;
462 $this->metaDirty =
true;
463 $this->logger->debug(
464 'SessionBackend "{session}" metadata dirty due to force-HTTPS change',
466 'session' => $this->
id,
486 if ( $this->loggedOut !== $ts ) {
487 $this->loggedOut = $ts;
488 $this->metaDirty =
true;
489 $this->logger->debug(
490 'SessionBackend "{session}" metadata dirty due to logged-out-timestamp change',
492 'session' => $this->
id,
513 if ( $metadata !==
null && !is_array( $metadata ) ) {
514 throw new \InvalidArgumentException(
'$metadata must be an array or null' );
516 if ( $this->providerMetadata !== $metadata ) {
517 $this->providerMetadata = $metadata;
518 $this->metaDirty =
true;
519 $this->logger->debug(
520 'SessionBackend "{session}" metadata dirty due to provider metadata change',
522 'session' => $this->
id,
550 foreach ( $newData as $key => $value ) {
551 if ( !array_key_exists( $key,
$data ) ||
$data[$key] !== $value ) {
552 $data[$key] = $value;
553 $this->dataDirty =
true;
554 $this->logger->debug(
555 'SessionBackend "{session}" data dirty due to addData(): {callers}',
557 'session' => $this->
id,
569 $this->dataDirty =
true;
570 $this->logger->debug(
571 'SessionBackend "{session}" data dirty due to dirty(): {callers}',
573 'session' => $this->
id,
585 if ( time() + $this->lifetime / 2 > $this->expires ) {
586 $this->metaDirty =
true;
587 $this->logger->debug(
588 'SessionBackend "{callers}" metadata dirty for renew(): {callers}',
590 'session' => $this->
id,
594 $this->forcePersist =
true;
595 $this->logger->debug(
596 'SessionBackend "{session}" force-persist for renew(): {callers}',
598 'session' => $this->
id,
615 return new \Wikimedia\ScopedCallback(
function () {
642 public function save( $closing =
false ) {
643 $anon = $this->user->isAnon();
645 if ( !$anon && $this->provider->getManager()->isUserSessionPrevented( $this->user->getName() ) ) {
646 $this->logger->debug(
647 'SessionBackend "{session}" not saving, user {user} was ' .
648 'passed to SessionManager::preventSessionsForUser',
650 'session' => $this->
id,
651 'user' => $this->user,
658 if ( !$anon && !$this->user->getToken(
false ) ) {
659 $this->logger->debug(
660 'SessionBackend "{session}" creating token for user {user} on save',
662 'session' => $this->
id,
663 'user' => $this->user,
665 $this->user->setToken();
673 $this->metaDirty =
true;
677 if ( !$this->metaDirty && !$this->dataDirty &&
678 $this->dataHash !== md5(
serialize( $this->data ) )
680 $this->logger->debug(
681 'SessionBackend "{session}" data dirty due to hash mismatch, {expected} !== {got}',
683 'session' => $this->
id,
684 'expected' => $this->dataHash,
685 'got' => md5(
serialize( $this->data ) ),
687 $this->dataDirty =
true;
690 if ( !$this->metaDirty && !$this->dataDirty && !$this->forcePersist ) {
694 $this->logger->debug(
695 'SessionBackend "{session}" save: dataDirty={dataDirty} ' .
696 'metaDirty={metaDirty} forcePersist={forcePersist}',
698 'session' => $this->
id,
699 'dataDirty' => (
int)$this->dataDirty,
700 'metaDirty' => (
int)$this->metaDirty,
701 'forcePersist' => (
int)$this->forcePersist,
705 if ( $this->metaDirty || $this->forcePersist ) {
707 foreach ( $this->requests as $request ) {
709 $this->provider->persistSession( $this, $request );
715 foreach ( $this->requests as $request ) {
716 if ( $request->getSessionId() ===
$this->id ) {
717 $this->provider->unpersistSession( $request );
723 $this->forcePersist =
false;
725 if ( !$this->metaDirty && !$this->dataDirty ) {
730 $metadata = $origMetadata = [
731 'provider' => (string)$this->provider,
732 'providerMetadata' => $this->providerMetadata,
733 'userId' => $anon ? 0 : $this->user->getId(),
735 'userToken' => $anon ? null : $this->user->getToken(),
743 \Hooks::run(
'SessionMetadata', [ $this, &$metadata, $this->requests ] );
745 foreach ( $origMetadata as $k => $v ) {
746 if ( $metadata[$k] !== $v ) {
747 throw new \UnexpectedValueException(
"SessionMetadata hook changed metadata key \"$k\"" );
754 $this->store->makeKey(
'MWSession', (
string)$this->id ),
757 'metadata' => $metadata,
759 $metadata[
'expires'],
763 $this->metaDirty =
false;
764 $this->dataDirty =
false;
765 $this->dataHash = md5(
serialize( $this->data ) );
766 $this->expires = $metadata[
'expires'];
774 if ( !$this->checkPHPSessionRecursionGuard ) {
775 $this->checkPHPSessionRecursionGuard =
true;
776 $reset = new \Wikimedia\ScopedCallback(
function () {
777 $this->checkPHPSessionRecursionGuard =
false;
783 $this->logger->debug(
784 'SessionBackend "{session}" Taking over PHP session',
786 'session' => $this->
id,
788 session_id( (
string)$this->
id );
789 AtEase::quietCall(
'session_start' );