30use Psr\Log\LoggerInterface;
33use Wikimedia\AtEase\AtEase;
153 $phpSessionHandling = \RequestContext::getMain()->getConfig()->get(
'PHPSessionHandling' );
154 $this->usePhpSessionHandling = $phpSessionHandling !==
'disable';
157 throw new \InvalidArgumentException(
158 "Refusing to create session for unverified user {$info->getUserInfo()}"
162 throw new \InvalidArgumentException(
'Cannot create session without a provider' );
165 throw new \InvalidArgumentException(
'SessionId and SessionInfo don\'t match' );
172 $this->hookRunner =
new HookRunner( $hookContainer );
181 if ( !is_array(
$blob ) ||
182 !isset(
$blob[
'metadata'] ) || !is_array(
$blob[
'metadata'] ) ||
183 !isset(
$blob[
'data'] ) || !is_array(
$blob[
'data'] )
186 $this->dataDirty =
true;
187 $this->metaDirty =
true;
188 $this->persistenceChangeType =
'no-store';
189 $this->logger->debug(
190 'SessionBackend "{session}" is unsaved, marking dirty in constructor',
192 'session' => $this->id->__toString(),
195 $this->data =
$blob[
'data'];
196 if ( isset(
$blob[
'metadata'][
'loggedOut'] ) ) {
197 $this->loggedOut = (int)
$blob[
'metadata'][
'loggedOut'];
199 if ( isset(
$blob[
'metadata'][
'expires'] ) ) {
200 $this->expires = (int)
$blob[
'metadata'][
'expires'];
202 $this->metaDirty =
true;
203 $this->persistenceChangeType =
'no-expiry';
204 $this->logger->debug(
205 'SessionBackend "{session}" metadata dirty due to missing expiration timestamp',
207 'session' => $this->id->__toString(),
211 $this->dataHash = md5(
serialize( $this->data ) );
221 $this->requests[$index] = $request;
222 $session =
new Session( $this, $index, $this->logger );
232 unset( $this->requests[$index] );
233 if ( !$this->
shutdown && !count( $this->requests ) ) {
235 $this->provider->getManager()->deregisterSessionBackend( $this );
270 if ( $this->provider->persistsSessionId() ) {
271 $oldId = (string)$this->
id;
272 $restart = $this->usePhpSessionHandling && $oldId === session_id() &&
278 session_write_close();
281 $this->provider->getManager()->changeBackendId( $this );
282 $this->provider->sessionIdWasReset( $this, $oldId );
283 $this->metaDirty =
true;
284 $this->logger->debug(
285 'SessionBackend "{session}" metadata dirty due to ID reset (formerly "{oldId}")',
287 'session' => $this->id->__toString(),
292 session_id( (
string)$this->
id );
293 AtEase::quietCall(
'session_start' );
299 $this->store->delete( $this->store->makeKey(
'MWSession', $oldId ) );
333 $this->forcePersist =
true;
334 $this->metaDirty =
true;
335 $this->logger->debug(
336 'SessionBackend "{session}" force-persist due to persist()',
338 'session' => $this->id->__toString(),
353 session_id() === (
string)$this->
id
355 $this->logger->debug(
356 'SessionBackend "{session}" Closing PHP session for unpersist',
357 [
'session' => $this->id->__toString() ]
359 session_write_close();
364 $this->forcePersist =
true;
365 $this->metaDirty =
true;
369 $this->store->delete( $this->store->makeKey(
'MWSession', (
string)$this->id ) );
390 if ( $this->remember !== (
bool)
$remember ) {
392 $this->metaDirty =
true;
393 $this->logger->debug(
394 'SessionBackend "{session}" metadata dirty due to remember-user change',
396 'session' => $this->id->__toString(),
408 if ( !isset( $this->requests[$index] ) ) {
409 throw new \InvalidArgumentException(
'Invalid session index' );
411 return $this->requests[$index];
427 return $this->provider->getAllowedUserRights( $this );
435 return $this->provider->canChangeUser();
447 throw new \BadMethodCallException(
448 'Cannot set user on this session; check $session->canSetUser() first'
453 $this->metaDirty =
true;
454 $this->logger->debug(
455 'SessionBackend "{session}" metadata dirty due to user change',
457 'session' => $this->id->__toString(),
468 if ( !isset( $this->requests[$index] ) ) {
469 throw new \InvalidArgumentException(
'Invalid session index' );
471 return $this->provider->suggestLoginUsername( $this->requests[$index] );
487 if ( $this->forceHTTPS !== (
bool)$force ) {
488 $this->forceHTTPS = (bool)$force;
489 $this->metaDirty =
true;
490 $this->logger->debug(
491 'SessionBackend "{session}" metadata dirty due to force-HTTPS change',
493 'session' => $this->id->__toString(),
512 if ( $this->loggedOut !== $ts ) {
513 $this->loggedOut = $ts;
514 $this->metaDirty =
true;
515 $this->logger->debug(
516 'SessionBackend "{session}" metadata dirty due to logged-out-timestamp change',
518 'session' => $this->id->__toString(),
538 if ( $metadata !==
null && !is_array( $metadata ) ) {
539 throw new \InvalidArgumentException(
'$metadata must be an array or null' );
541 if ( $this->providerMetadata !== $metadata ) {
542 $this->providerMetadata = $metadata;
543 $this->metaDirty =
true;
544 $this->logger->debug(
545 'SessionBackend "{session}" metadata dirty due to provider metadata change',
547 'session' => $this->id->__toString(),
575 foreach ( $newData as $key => $value ) {
576 if ( !array_key_exists( $key,
$data ) ||
$data[$key] !== $value ) {
577 $data[$key] = $value;
578 $this->dataDirty =
true;
579 $this->logger->debug(
580 'SessionBackend "{session}" data dirty due to addData(): {callers}',
582 'session' => $this->id->__toString(),
594 $this->dataDirty =
true;
595 $this->logger->debug(
596 'SessionBackend "{session}" data dirty due to dirty(): {callers}',
598 'session' => $this->id->__toString(),
610 if ( time() + $this->lifetime / 2 > $this->expires ) {
611 $this->metaDirty =
true;
612 $this->logger->debug(
613 'SessionBackend "{callers}" metadata dirty for renew(): {callers}',
615 'session' => $this->id->__toString(),
619 $this->persistenceChangeType =
'renew';
620 $this->forcePersist =
true;
621 $this->logger->debug(
622 'SessionBackend "{session}" force-persist for renew(): {callers}',
624 'session' => $this->id->__toString(),
641 return new \Wikimedia\ScopedCallback(
function () {
668 public function save( $closing =
false ) {
669 $anon = $this->user->isAnon();
671 if ( !$anon && $this->provider->getManager()->isUserSessionPrevented( $this->user->getName() ) ) {
672 $this->logger->debug(
673 'SessionBackend "{session}" not saving, user {user} was ' .
674 'passed to SessionManager::preventSessionsForUser',
676 'session' => $this->id->__toString(),
677 'user' => $this->user->__toString(),
684 if ( !$anon && !$this->user->getToken(
false ) ) {
685 $this->logger->debug(
686 'SessionBackend "{session}" creating token for user {user} on save',
688 'session' => $this->id->__toString(),
689 'user' => $this->user->__toString(),
691 $this->user->setToken();
695 \DeferredUpdates::addCallableUpdate(
static function () use (
$user ) {
699 $this->metaDirty =
true;
703 if ( !$this->metaDirty && !$this->dataDirty &&
704 $this->dataHash !== md5(
serialize( $this->data ) )
706 $this->logger->debug(
707 'SessionBackend "{session}" data dirty due to hash mismatch, {expected} !== {got}',
709 'session' => $this->id->__toString(),
710 'expected' => $this->dataHash,
711 'got' => md5(
serialize( $this->data ) ),
713 $this->dataDirty =
true;
716 if ( !$this->metaDirty && !$this->dataDirty && !$this->forcePersist ) {
720 $this->logger->debug(
721 'SessionBackend "{session}" save: dataDirty={dataDirty} ' .
722 'metaDirty={metaDirty} forcePersist={forcePersist}',
724 'session' => $this->id->__toString(),
725 'dataDirty' => (
int)$this->dataDirty,
726 'metaDirty' => (
int)$this->metaDirty,
727 'forcePersist' => (
int)$this->forcePersist,
731 if ( $this->metaDirty || $this->forcePersist ) {
733 foreach ( $this->requests as $request ) {
736 $this->provider->persistSession( $this, $request );
742 foreach ( $this->requests as $request ) {
743 if ( $request->getSessionId() === $this->id ) {
745 $this->provider->unpersistSession( $request );
751 $this->forcePersist =
false;
752 $this->persistenceChangeType =
null;
754 if ( !$this->metaDirty && !$this->dataDirty ) {
759 $metadata = $origMetadata = [
760 'provider' => (string)$this->provider,
761 'providerMetadata' => $this->providerMetadata,
762 'userId' => $anon ? 0 : $this->user->getId(),
764 ->isValid( $this->user->getName() ) ? $this->user->getName() :
null,
765 'userToken' => $anon ? null : $this->user->getToken(),
773 $this->hookRunner->onSessionMetadata( $this, $metadata, $this->requests );
775 foreach ( $origMetadata as $k => $v ) {
776 if ( $metadata[$k] !== $v ) {
777 throw new \UnexpectedValueException(
"SessionMetadata hook changed metadata key \"$k\"" );
781 $flags = $this->
persist ? 0 : CachedBagOStuff::WRITE_CACHE_ONLY;
782 $flags |= CachedBagOStuff::WRITE_SYNC;
784 $this->store->makeKey(
'MWSession', (
string)$this->id ),
786 'data' => $this->data,
787 'metadata' => $metadata,
789 $metadata[
'expires'],
793 $this->metaDirty =
false;
794 $this->dataDirty =
false;
795 $this->dataHash = md5(
serialize( $this->data ) );
796 $this->expires = $metadata[
'expires'];
804 if ( !$this->checkPHPSessionRecursionGuard ) {
805 $this->checkPHPSessionRecursionGuard =
true;
806 $reset = new \Wikimedia\ScopedCallback(
function () {
807 $this->checkPHPSessionRecursionGuard =
false;
813 $this->logger->debug(
814 'SessionBackend "{session}" Taking over PHP session',
816 'session' => $this->id->__toString(),
818 session_id( (
string)$this->
id );
819 AtEase::quietCall(
'session_start' );
836 $verb =
$persist ?
'Persisting' :
'Unpersisting';
837 if ( $this->persistenceChangeType ===
'renew' ) {
838 $message =
"$verb session for renewal";
839 } elseif ( $this->persistenceChangeType ===
'no-store' ) {
840 $message =
"$verb session due to no pre-existing stored session";
841 } elseif ( $this->persistenceChangeType ===
'no-expiry' ) {
842 $message =
"$verb session due to lack of stored expiry";
843 } elseif ( $this->persistenceChangeType ===
null ) {
844 $message =
"$verb session for unknown reason";
852 if ( $this->persistenceChangeData
853 && $this->persistenceChangeData[
'id'] ===
$id
854 && $this->persistenceChangeData[
'user'] ===
$user
855 && $this->persistenceChangeData[
'message'] === $message
859 $this->persistenceChangeData = [
'id' =>
$id,
'user' =>
$user,
'message' => $message ];
861 $this->logger->info( $message, [
865 'clientip' => $request->
getIP(),
866 'userAgent' => $request->
getHeader(
'user-agent' ),
wfReadOnly()
Check whether the wiki is in read-only mode.
wfGetAllCallers( $limit=3)
Return a string consisting of callers in the stack.
Wrapper around a BagOStuff that caches data in memory.
makeKey( $collection,... $components)
Make a cache key for the global keyspace and given components.
get( $key, $flags=0)
Get an item with the given key.
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
saveSettings()
Save this user's settings into the database.
isAnon()
Get whether the user is anonymous.
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form stripping il...
getIP()
Work out the IP address based on various globals For trusted proxies, use the XFF client IP (first of...
getHeader( $name, $flags=0)
Get a request header, or false if it isn't set.