MediaWiki  1.32.5
SessionManager.php
Go to the documentation of this file.
1 <?php
24 namespace MediaWiki\Session;
25 
28 use Psr\Log\LoggerInterface;
31 use Config;
33 use User;
35 use Wikimedia\ObjectFactory;
36 
50 final class SessionManager implements SessionManagerInterface {
52  private static $instance = null;
53 
55  private static $globalSession = null;
56 
58  private static $globalSessionRequest = null;
59 
61  private $logger;
62 
64  private $config;
65 
67  private $store;
68 
70  private $sessionProviders = null;
71 
73  private $varyCookies = null;
74 
76  private $varyHeaders = null;
77 
79  private $allSessionBackends = [];
80 
82  private $allSessionIds = [];
83 
85  private $preventUsers = [];
86 
92  public static function singleton() {
93  if ( self::$instance === null ) {
94  self::$instance = new self();
95  }
96  return self::$instance;
97  }
98 
107  public static function getGlobalSession() {
108  if ( !PHPSessionHandler::isEnabled() ) {
109  $id = '';
110  } else {
111  $id = session_id();
112  }
113 
114  $request = \RequestContext::getMain()->getRequest();
115  if (
116  !self::$globalSession // No global session is set up yet
117  || self::$globalSessionRequest !== $request // The global WebRequest changed
118  || $id !== '' && self::$globalSession->getId() !== $id // Someone messed with session_id()
119  ) {
120  self::$globalSessionRequest = $request;
121  if ( $id === '' ) {
122  // session_id() wasn't used, so fetch the Session from the WebRequest.
123  // We use $request->getSession() instead of $singleton->getSessionForRequest()
124  // because doing the latter would require a public
125  // "$request->getSessionId()" method that would confuse end
126  // users by returning SessionId|null where they'd expect it to
127  // be short for $request->getSession()->getId(), and would
128  // wind up being a duplicate of the code in
129  // $request->getSession() anyway.
130  self::$globalSession = $request->getSession();
131  } else {
132  // Someone used session_id(), so we need to follow suit.
133  // Note this overwrites whatever session might already be
134  // associated with $request with the one for $id.
135  self::$globalSession = self::singleton()->getSessionById( $id, true, $request )
136  ?: $request->getSession();
137  }
138  }
139  return self::$globalSession;
140  }
141 
148  public function __construct( $options = [] ) {
149  if ( isset( $options['config'] ) ) {
150  $this->config = $options['config'];
151  if ( !$this->config instanceof Config ) {
152  throw new \InvalidArgumentException(
153  '$options[\'config\'] must be an instance of Config'
154  );
155  }
156  } else {
157  $this->config = MediaWikiServices::getInstance()->getMainConfig();
158  }
159 
160  if ( isset( $options['logger'] ) ) {
161  if ( !$options['logger'] instanceof LoggerInterface ) {
162  throw new \InvalidArgumentException(
163  '$options[\'logger\'] must be an instance of LoggerInterface'
164  );
165  }
166  $this->setLogger( $options['logger'] );
167  } else {
168  $this->setLogger( \MediaWiki\Logger\LoggerFactory::getInstance( 'session' ) );
169  }
170 
171  if ( isset( $options['store'] ) ) {
172  if ( !$options['store'] instanceof BagOStuff ) {
173  throw new \InvalidArgumentException(
174  '$options[\'store\'] must be an instance of BagOStuff'
175  );
176  }
177  $store = $options['store'];
178  } else {
179  $store = \ObjectCache::getInstance( $this->config->get( 'SessionCacheType' ) );
180  }
181  $this->store = $store instanceof CachedBagOStuff ? $store : new CachedBagOStuff( $store );
182 
183  register_shutdown_function( [ $this, 'shutdown' ] );
184  }
185 
186  public function setLogger( LoggerInterface $logger ) {
187  $this->logger = $logger;
188  }
189 
191  $info = $this->getSessionInfoForRequest( $request );
192 
193  if ( !$info ) {
194  $session = $this->getEmptySession( $request );
195  } else {
196  $session = $this->getSessionFromInfo( $info, $request );
197  }
198  return $session;
199  }
200 
201  public function getSessionById( $id, $create = false, WebRequest $request = null ) {
202  if ( !self::validateSessionId( $id ) ) {
203  throw new \InvalidArgumentException( 'Invalid session ID' );
204  }
205  if ( !$request ) {
206  $request = new FauxRequest;
207  }
208 
209  $session = null;
210  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [ 'id' => $id, 'idIsSafe' => true ] );
211 
212  // If we already have the backend loaded, use it directly
213  if ( isset( $this->allSessionBackends[$id] ) ) {
214  return $this->getSessionFromInfo( $info, $request );
215  }
216 
217  // Test if the session is in storage, and if so try to load it.
218  $key = $this->store->makeKey( 'MWSession', $id );
219  if ( is_array( $this->store->get( $key ) ) ) {
220  $create = false; // If loading fails, don't bother creating because it probably will fail too.
221  if ( $this->loadSessionInfoFromStore( $info, $request ) ) {
222  $session = $this->getSessionFromInfo( $info, $request );
223  }
224  }
225 
226  if ( $create && $session === null ) {
227  $ex = null;
228  try {
229  $session = $this->getEmptySessionInternal( $request, $id );
230  } catch ( \Exception $ex ) {
231  $this->logger->error( 'Failed to create empty session: {exception}',
232  [
233  'method' => __METHOD__,
234  'exception' => $ex,
235  ] );
236  $session = null;
237  }
238  }
239 
240  return $session;
241  }
242 
243  public function getEmptySession( WebRequest $request = null ) {
244  return $this->getEmptySessionInternal( $request );
245  }
246 
253  private function getEmptySessionInternal( WebRequest $request = null, $id = null ) {
254  if ( $id !== null ) {
255  if ( !self::validateSessionId( $id ) ) {
256  throw new \InvalidArgumentException( 'Invalid session ID' );
257  }
258 
259  $key = $this->store->makeKey( 'MWSession', $id );
260  if ( is_array( $this->store->get( $key ) ) ) {
261  throw new \InvalidArgumentException( 'Session ID already exists' );
262  }
263  }
264  if ( !$request ) {
265  $request = new FauxRequest;
266  }
267 
268  $infos = [];
269  foreach ( $this->getProviders() as $provider ) {
270  $info = $provider->newSessionInfo( $id );
271  if ( !$info ) {
272  continue;
273  }
274  if ( $info->getProvider() !== $provider ) {
275  throw new \UnexpectedValueException(
276  "$provider returned an empty session info for a different provider: $info"
277  );
278  }
279  if ( $id !== null && $info->getId() !== $id ) {
280  throw new \UnexpectedValueException(
281  "$provider returned empty session info with a wrong id: " .
282  $info->getId() . ' != ' . $id
283  );
284  }
285  if ( !$info->isIdSafe() ) {
286  throw new \UnexpectedValueException(
287  "$provider returned empty session info with id flagged unsafe"
288  );
289  }
290  $compare = $infos ? SessionInfo::compare( $infos[0], $info ) : -1;
291  if ( $compare > 0 ) {
292  continue;
293  }
294  if ( $compare === 0 ) {
295  $infos[] = $info;
296  } else {
297  $infos = [ $info ];
298  }
299  }
300 
301  // Make sure there's exactly one
302  if ( count( $infos ) > 1 ) {
303  throw new \UnexpectedValueException(
304  'Multiple empty sessions tied for top priority: ' . implode( ', ', $infos )
305  );
306  } elseif ( count( $infos ) < 1 ) {
307  throw new \UnexpectedValueException( 'No provider could provide an empty session!' );
308  }
309 
310  return $this->getSessionFromInfo( $infos[0], $request );
311  }
312 
313  public function invalidateSessionsForUser( User $user ) {
314  $user->setToken();
315  $user->saveSettings();
316 
317  $authUser = \MediaWiki\Auth\AuthManager::callLegacyAuthPlugin( 'getUserInstance', [ &$user ] );
318  if ( $authUser ) {
319  $authUser->resetAuthToken();
320  }
321 
322  foreach ( $this->getProviders() as $provider ) {
323  $provider->invalidateSessionsForUser( $user );
324  }
325  }
326 
327  public function getVaryHeaders() {
328  // @codeCoverageIgnoreStart
329  if ( defined( 'MW_NO_SESSION' ) && MW_NO_SESSION !== 'warn' ) {
330  return [];
331  }
332  // @codeCoverageIgnoreEnd
333  if ( $this->varyHeaders === null ) {
334  $headers = [];
335  foreach ( $this->getProviders() as $provider ) {
336  foreach ( $provider->getVaryHeaders() as $header => $options ) {
337  if ( !isset( $headers[$header] ) ) {
338  $headers[$header] = [];
339  }
340  if ( is_array( $options ) ) {
341  $headers[$header] = array_unique( array_merge( $headers[$header], $options ) );
342  }
343  }
344  }
345  $this->varyHeaders = $headers;
346  }
347  return $this->varyHeaders;
348  }
349 
350  public function getVaryCookies() {
351  // @codeCoverageIgnoreStart
352  if ( defined( 'MW_NO_SESSION' ) && MW_NO_SESSION !== 'warn' ) {
353  return [];
354  }
355  // @codeCoverageIgnoreEnd
356  if ( $this->varyCookies === null ) {
357  $cookies = [];
358  foreach ( $this->getProviders() as $provider ) {
359  $cookies = array_merge( $cookies, $provider->getVaryCookies() );
360  }
361  $this->varyCookies = array_values( array_unique( $cookies ) );
362  }
363  return $this->varyCookies;
364  }
365 
371  public static function validateSessionId( $id ) {
372  return is_string( $id ) && preg_match( '/^[a-zA-Z0-9_-]{32,}$/', $id );
373  }
374 
388  public static function autoCreateUser( User $user ) {
389  wfDeprecated( __METHOD__, '1.27' );
390  return \MediaWiki\Auth\AuthManager::singleton()->autoCreateUser(
391  $user,
392  \MediaWiki\Auth\AuthManager::AUTOCREATE_SOURCE_SESSION,
393  false
394  )->isGood();
395  }
396 
406  public function preventSessionsForUser( $username ) {
407  $this->preventUsers[$username] = true;
408 
409  // Instruct the session providers to kill any other sessions too.
410  foreach ( $this->getProviders() as $provider ) {
411  $provider->preventSessionsForUser( $username );
412  }
413  }
414 
421  public function isUserSessionPrevented( $username ) {
422  return !empty( $this->preventUsers[$username] );
423  }
424 
429  protected function getProviders() {
430  if ( $this->sessionProviders === null ) {
431  $this->sessionProviders = [];
432  foreach ( $this->config->get( 'SessionProviders' ) as $spec ) {
433  $provider = ObjectFactory::getObjectFromSpec( $spec );
434  $provider->setLogger( $this->logger );
435  $provider->setConfig( $this->config );
436  $provider->setManager( $this );
437  if ( isset( $this->sessionProviders[(string)$provider] ) ) {
438  throw new \UnexpectedValueException( "Duplicate provider name \"$provider\"" );
439  }
440  $this->sessionProviders[(string)$provider] = $provider;
441  }
442  }
444  }
445 
456  public function getProvider( $name ) {
457  $providers = $this->getProviders();
458  return $providers[$name] ?? null;
459  }
460 
465  public function shutdown() {
466  if ( $this->allSessionBackends ) {
467  $this->logger->debug( 'Saving all sessions on shutdown' );
468  if ( session_id() !== '' ) {
469  // @codeCoverageIgnoreStart
470  session_write_close();
471  }
472  // @codeCoverageIgnoreEnd
473  foreach ( $this->allSessionBackends as $backend ) {
474  $backend->shutdown();
475  }
476  }
477  }
478 
485  // Call all providers to fetch "the" session
486  $infos = [];
487  foreach ( $this->getProviders() as $provider ) {
488  $info = $provider->provideSessionInfo( $request );
489  if ( !$info ) {
490  continue;
491  }
492  if ( $info->getProvider() !== $provider ) {
493  throw new \UnexpectedValueException(
494  "$provider returned session info for a different provider: $info"
495  );
496  }
497  $infos[] = $info;
498  }
499 
500  // Sort the SessionInfos. Then find the first one that can be
501  // successfully loaded, and then all the ones after it with the same
502  // priority.
503  usort( $infos, 'MediaWiki\\Session\\SessionInfo::compare' );
504  $retInfos = [];
505  while ( $infos ) {
506  $info = array_pop( $infos );
507  if ( $this->loadSessionInfoFromStore( $info, $request ) ) {
508  $retInfos[] = $info;
509  while ( $infos ) {
510  $info = array_pop( $infos );
511  if ( SessionInfo::compare( $retInfos[0], $info ) ) {
512  // We hit a lower priority, stop checking.
513  break;
514  }
515  if ( $this->loadSessionInfoFromStore( $info, $request ) ) {
516  // This is going to error out below, but we want to
517  // provide a complete list.
518  $retInfos[] = $info;
519  } else {
520  // Session load failed, so unpersist it from this request
521  $info->getProvider()->unpersistSession( $request );
522  }
523  }
524  } else {
525  // Session load failed, so unpersist it from this request
526  $info->getProvider()->unpersistSession( $request );
527  }
528  }
529 
530  if ( count( $retInfos ) > 1 ) {
531  $ex = new \OverflowException(
532  'Multiple sessions for this request tied for top priority: ' . implode( ', ', $retInfos )
533  );
534  $ex->sessionInfos = $retInfos;
535  throw $ex;
536  }
537 
538  return $retInfos ? $retInfos[0] : null;
539  }
540 
549  $key = $this->store->makeKey( 'MWSession', $info->getId() );
550  $blob = $this->store->get( $key );
551 
552  // If we got data from the store and the SessionInfo says to force use,
553  // "fail" means to delete the data from the store and retry. Otherwise,
554  // "fail" is just return false.
555  if ( $info->forceUse() && $blob !== false ) {
556  $failHandler = function () use ( $key, &$info, $request ) {
557  $this->store->delete( $key );
558  return $this->loadSessionInfoFromStore( $info, $request );
559  };
560  } else {
561  $failHandler = function () {
562  return false;
563  };
564  }
565 
566  $newParams = [];
567 
568  if ( $blob !== false ) {
569  // Sanity check: blob must be an array, if it's saved at all
570  if ( !is_array( $blob ) ) {
571  $this->logger->warning( 'Session "{session}": Bad data', [
572  'session' => $info,
573  ] );
574  $this->store->delete( $key );
575  return $failHandler();
576  }
577 
578  // Sanity check: blob has data and metadata arrays
579  if ( !isset( $blob['data'] ) || !is_array( $blob['data'] ) ||
580  !isset( $blob['metadata'] ) || !is_array( $blob['metadata'] )
581  ) {
582  $this->logger->warning( 'Session "{session}": Bad data structure', [
583  'session' => $info,
584  ] );
585  $this->store->delete( $key );
586  return $failHandler();
587  }
588 
589  $data = $blob['data'];
590  $metadata = $blob['metadata'];
591 
592  // Sanity check: metadata must be an array and must contain certain
593  // keys, if it's saved at all
594  if ( !array_key_exists( 'userId', $metadata ) ||
595  !array_key_exists( 'userName', $metadata ) ||
596  !array_key_exists( 'userToken', $metadata ) ||
597  !array_key_exists( 'provider', $metadata )
598  ) {
599  $this->logger->warning( 'Session "{session}": Bad metadata', [
600  'session' => $info,
601  ] );
602  $this->store->delete( $key );
603  return $failHandler();
604  }
605 
606  // First, load the provider from metadata, or validate it against the metadata.
607  $provider = $info->getProvider();
608  if ( $provider === null ) {
609  $newParams['provider'] = $provider = $this->getProvider( $metadata['provider'] );
610  if ( !$provider ) {
611  $this->logger->warning(
612  'Session "{session}": Unknown provider ' . $metadata['provider'],
613  [
614  'session' => $info,
615  ]
616  );
617  $this->store->delete( $key );
618  return $failHandler();
619  }
620  } elseif ( $metadata['provider'] !== (string)$provider ) {
621  $this->logger->warning( 'Session "{session}": Wrong provider ' .
622  $metadata['provider'] . ' !== ' . $provider,
623  [
624  'session' => $info,
625  ] );
626  return $failHandler();
627  }
628 
629  // Load provider metadata from metadata, or validate it against the metadata
630  $providerMetadata = $info->getProviderMetadata();
631  if ( isset( $metadata['providerMetadata'] ) ) {
632  if ( $providerMetadata === null ) {
633  $newParams['metadata'] = $metadata['providerMetadata'];
634  } else {
635  try {
636  $newProviderMetadata = $provider->mergeMetadata(
637  $metadata['providerMetadata'], $providerMetadata
638  );
639  if ( $newProviderMetadata !== $providerMetadata ) {
640  $newParams['metadata'] = $newProviderMetadata;
641  }
642  } catch ( MetadataMergeException $ex ) {
643  $this->logger->warning(
644  'Session "{session}": Metadata merge failed: {exception}',
645  [
646  'session' => $info,
647  'exception' => $ex,
648  ] + $ex->getContext()
649  );
650  return $failHandler();
651  }
652  }
653  }
654 
655  // Next, load the user from metadata, or validate it against the metadata.
656  $userInfo = $info->getUserInfo();
657  if ( !$userInfo ) {
658  // For loading, id is preferred to name.
659  try {
660  if ( $metadata['userId'] ) {
661  $userInfo = UserInfo::newFromId( $metadata['userId'] );
662  } elseif ( $metadata['userName'] !== null ) { // Shouldn't happen, but just in case
663  $userInfo = UserInfo::newFromName( $metadata['userName'] );
664  } else {
665  $userInfo = UserInfo::newAnonymous();
666  }
667  } catch ( \InvalidArgumentException $ex ) {
668  $this->logger->error( 'Session "{session}": {exception}', [
669  'session' => $info,
670  'exception' => $ex,
671  ] );
672  return $failHandler();
673  }
674  $newParams['userInfo'] = $userInfo;
675  } else {
676  // User validation passes if user ID matches, or if there
677  // is no saved ID and the names match.
678  if ( $metadata['userId'] ) {
679  if ( $metadata['userId'] !== $userInfo->getId() ) {
680  $this->logger->warning(
681  'Session "{session}": User ID mismatch, {uid_a} !== {uid_b}',
682  [
683  'session' => $info,
684  'uid_a' => $metadata['userId'],
685  'uid_b' => $userInfo->getId(),
686  ] );
687  return $failHandler();
688  }
689 
690  // If the user was renamed, probably best to fail here.
691  if ( $metadata['userName'] !== null &&
692  $userInfo->getName() !== $metadata['userName']
693  ) {
694  $this->logger->warning(
695  'Session "{session}": User ID matched but name didn\'t (rename?), {uname_a} !== {uname_b}',
696  [
697  'session' => $info,
698  'uname_a' => $metadata['userName'],
699  'uname_b' => $userInfo->getName(),
700  ] );
701  return $failHandler();
702  }
703 
704  } elseif ( $metadata['userName'] !== null ) { // Shouldn't happen, but just in case
705  if ( $metadata['userName'] !== $userInfo->getName() ) {
706  $this->logger->warning(
707  'Session "{session}": User name mismatch, {uname_a} !== {uname_b}',
708  [
709  'session' => $info,
710  'uname_a' => $metadata['userName'],
711  'uname_b' => $userInfo->getName(),
712  ] );
713  return $failHandler();
714  }
715  } elseif ( !$userInfo->isAnon() ) {
716  // Metadata specifies an anonymous user, but the passed-in
717  // user isn't anonymous.
718  $this->logger->warning(
719  'Session "{session}": Metadata has an anonymous user, but a non-anon user was provided',
720  [
721  'session' => $info,
722  ] );
723  return $failHandler();
724  }
725  }
726 
727  // And if we have a token in the metadata, it must match the loaded/provided user.
728  if ( $metadata['userToken'] !== null &&
729  $userInfo->getToken() !== $metadata['userToken']
730  ) {
731  $this->logger->warning( 'Session "{session}": User token mismatch', [
732  'session' => $info,
733  ] );
734  return $failHandler();
735  }
736  if ( !$userInfo->isVerified() ) {
737  $newParams['userInfo'] = $userInfo->verified();
738  }
739 
740  if ( !empty( $metadata['remember'] ) && !$info->wasRemembered() ) {
741  $newParams['remembered'] = true;
742  }
743  if ( !empty( $metadata['forceHTTPS'] ) && !$info->forceHTTPS() ) {
744  $newParams['forceHTTPS'] = true;
745  }
746  if ( !empty( $metadata['persisted'] ) && !$info->wasPersisted() ) {
747  $newParams['persisted'] = true;
748  }
749 
750  if ( !$info->isIdSafe() ) {
751  $newParams['idIsSafe'] = true;
752  }
753  } else {
754  // No metadata, so we can't load the provider if one wasn't given.
755  if ( $info->getProvider() === null ) {
756  $this->logger->warning(
757  'Session "{session}": Null provider and no metadata',
758  [
759  'session' => $info,
760  ] );
761  return $failHandler();
762  }
763 
764  // If no user was provided and no metadata, it must be anon.
765  if ( !$info->getUserInfo() ) {
766  if ( $info->getProvider()->canChangeUser() ) {
767  $newParams['userInfo'] = UserInfo::newAnonymous();
768  } else {
769  $this->logger->info(
770  'Session "{session}": No user provided and provider cannot set user',
771  [
772  'session' => $info,
773  ] );
774  return $failHandler();
775  }
776  } elseif ( !$info->getUserInfo()->isVerified() ) {
777  // probably just a session timeout
778  $this->logger->info(
779  'Session "{session}": Unverified user provided and no metadata to auth it',
780  [
781  'session' => $info,
782  ] );
783  return $failHandler();
784  }
785 
786  $data = false;
787  $metadata = false;
788 
789  if ( !$info->getProvider()->persistsSessionId() && !$info->isIdSafe() ) {
790  // The ID doesn't come from the user, so it should be safe
791  // (and if not, nothing we can do about it anyway)
792  $newParams['idIsSafe'] = true;
793  }
794  }
795 
796  // Construct the replacement SessionInfo, if necessary
797  if ( $newParams ) {
798  $newParams['copyFrom'] = $info;
799  $info = new SessionInfo( $info->getPriority(), $newParams );
800  }
801 
802  // Allow the provider to check the loaded SessionInfo
803  $providerMetadata = $info->getProviderMetadata();
804  if ( !$info->getProvider()->refreshSessionInfo( $info, $request, $providerMetadata ) ) {
805  return $failHandler();
806  }
807  if ( $providerMetadata !== $info->getProviderMetadata() ) {
808  $info = new SessionInfo( $info->getPriority(), [
809  'metadata' => $providerMetadata,
810  'copyFrom' => $info,
811  ] );
812  }
813 
814  // Give hooks a chance to abort. Combined with the SessionMetadata
815  // hook, this can allow for tying a session to an IP address or the
816  // like.
817  $reason = 'Hook aborted';
818  if ( !\Hooks::run(
819  'SessionCheckInfo',
820  [ &$reason, $info, $request, $metadata, $data ]
821  ) ) {
822  $this->logger->warning( 'Session "{session}": ' . $reason, [
823  'session' => $info,
824  ] );
825  return $failHandler();
826  }
827 
828  return true;
829  }
830 
839  public function getSessionFromInfo( SessionInfo $info, WebRequest $request ) {
840  // @codeCoverageIgnoreStart
841  if ( defined( 'MW_NO_SESSION' ) ) {
842  if ( MW_NO_SESSION === 'warn' ) {
843  // Undocumented safety case for converting existing entry points
844  $this->logger->error( 'Sessions are supposed to be disabled for this entry point', [
845  'exception' => new \BadMethodCallException( 'Sessions are disabled for this entry point' ),
846  ] );
847  } else {
848  throw new \BadMethodCallException( 'Sessions are disabled for this entry point' );
849  }
850  }
851  // @codeCoverageIgnoreEnd
852 
853  $id = $info->getId();
854 
855  if ( !isset( $this->allSessionBackends[$id] ) ) {
856  if ( !isset( $this->allSessionIds[$id] ) ) {
857  $this->allSessionIds[$id] = new SessionId( $id );
858  }
859  $backend = new SessionBackend(
860  $this->allSessionIds[$id],
861  $info,
862  $this->store,
863  $this->logger,
864  $this->config->get( 'ObjectCacheSessionExpiry' )
865  );
866  $this->allSessionBackends[$id] = $backend;
867  $delay = $backend->delaySave();
868  } else {
869  $backend = $this->allSessionBackends[$id];
870  $delay = $backend->delaySave();
871  if ( $info->wasPersisted() ) {
872  $backend->persist();
873  }
874  if ( $info->wasRemembered() ) {
875  $backend->setRememberUser( true );
876  }
877  }
878 
879  $request->setSessionId( $backend->getSessionId() );
880  $session = $backend->getSession( $request );
881 
882  if ( !$info->isIdSafe() ) {
883  $session->resetId();
884  }
885 
886  \Wikimedia\ScopedCallback::consume( $delay );
887  return $session;
888  }
889 
895  public function deregisterSessionBackend( SessionBackend $backend ) {
896  $id = $backend->getId();
897  if ( !isset( $this->allSessionBackends[$id] ) || !isset( $this->allSessionIds[$id] ) ||
898  $this->allSessionBackends[$id] !== $backend ||
899  $this->allSessionIds[$id] !== $backend->getSessionId()
900  ) {
901  throw new \InvalidArgumentException( 'Backend was not registered with this SessionManager' );
902  }
903 
904  unset( $this->allSessionBackends[$id] );
905  // Explicitly do not unset $this->allSessionIds[$id]
906  }
907 
913  public function changeBackendId( SessionBackend $backend ) {
914  $sessionId = $backend->getSessionId();
915  $oldId = (string)$sessionId;
916  if ( !isset( $this->allSessionBackends[$oldId] ) || !isset( $this->allSessionIds[$oldId] ) ||
917  $this->allSessionBackends[$oldId] !== $backend ||
918  $this->allSessionIds[$oldId] !== $sessionId
919  ) {
920  throw new \InvalidArgumentException( 'Backend was not registered with this SessionManager' );
921  }
922 
923  $newId = $this->generateSessionId();
924 
925  unset( $this->allSessionBackends[$oldId], $this->allSessionIds[$oldId] );
926  $sessionId->setId( $newId );
927  $this->allSessionBackends[$newId] = $backend;
928  $this->allSessionIds[$newId] = $sessionId;
929  }
930 
935  public function generateSessionId() {
936  do {
937  $id = \Wikimedia\base_convert( \MWCryptRand::generateHex( 40 ), 16, 32, 32 );
938  $key = $this->store->makeKey( 'MWSession', $id );
939  } while ( isset( $this->allSessionIds[$id] ) || is_array( $this->store->get( $key ) ) );
940  return $id;
941  }
942 
949  $handler->setManager( $this, $this->store, $this->logger );
950  }
951 
956  public static function resetCache() {
957  if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
958  // @codeCoverageIgnoreStart
959  throw new MWException( __METHOD__ . ' may only be called from unit tests!' );
960  // @codeCoverageIgnoreEnd
961  }
962 
963  self::$globalSession = null;
964  self::$globalSessionRequest = null;
965  }
966 
969 }
MediaWiki\Session\SessionManager\isUserSessionPrevented
isUserSessionPrevented( $username)
Test if a user is prevented.
Definition: SessionManager.php:421
MediaWiki\Session\UserInfo\newAnonymous
static newAnonymous()
Create an instance for an anonymous (i.e.
Definition: UserInfo.php:74
MediaWiki\Session\SessionManager\getEmptySessionInternal
getEmptySessionInternal(WebRequest $request=null, $id=null)
Definition: SessionManager.php:253
$user
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a account $user
Definition: hooks.txt:244
FauxRequest
WebRequest clone which takes values from a provided array.
Definition: FauxRequest.php:33
MediaWiki\Session\SessionInfo\forceHTTPS
forceHTTPS()
Whether this session should only be used over HTTPS.
Definition: SessionInfo.php:268
MediaWiki\Session\SessionManager\loadSessionInfoFromStore
loadSessionInfoFromStore(SessionInfo &$info, WebRequest $request)
Load and verify the session info against the store.
Definition: SessionManager.php:548
MW_NO_SESSION
const MW_NO_SESSION
Definition: load.php:30
false
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:187
MediaWiki\Session\SessionManager\generateSessionId
generateSessionId()
Generate a new random session ID.
Definition: SessionManager.php:935
MediaWiki\Session\SessionManager\getVaryCookies
getVaryCookies()
Return the list of cookies that need varying on.
Definition: SessionManager.php:350
MediaWiki\Session\SessionManager\$globalSessionRequest
static WebRequest null $globalSessionRequest
Definition: SessionManager.php:58
MediaWiki\Session\SessionManager\getProviders
getProviders()
Get the available SessionProviders.
Definition: SessionManager.php:429
captcha-old.count
count
Definition: captcha-old.py:249
MediaWiki\Session\SessionBackend\getId
getId()
Returns the session ID.
Definition: SessionBackend.php:205
MediaWiki\Session\SessionManager\setupPHPSessionHandler
setupPHPSessionHandler(PHPSessionHandler $handler)
Call setters on a PHPSessionHandler.
Definition: SessionManager.php:948
MediaWiki\Session\SessionInfo\compare
static compare( $a, $b)
Compare two SessionInfo objects by priority.
Definition: SessionInfo.php:284
MediaWiki\Session\SessionManager\$varyHeaders
array $varyHeaders
Definition: SessionManager.php:76
MediaWiki\Session\SessionManager\preventSessionsForUser
preventSessionsForUser( $username)
Prevent future sessions for the user.
Definition: SessionManager.php:406
MediaWiki\Session\SessionManager\$instance
static SessionManager null $instance
Definition: SessionManager.php:52
MediaWiki\Session\MetadataMergeException
Subclass of UnexpectedValueException that can be annotated with additional data for debug logging.
Definition: MetadataMergeException.php:35
MediaWiki\Session\SessionManager\getSessionById
getSessionById( $id, $create=false, WebRequest $request=null)
Fetch a session by ID.
Definition: SessionManager.php:201
MediaWiki\Session\PHPSessionHandler\isEnabled
static isEnabled()
Test whether the handler is installed and enabled.
Definition: PHPSessionHandler.php:100
MediaWiki\Session\SessionInfo\getPriority
getPriority()
Return the priority.
Definition: SessionInfo.php:217
MediaWiki\Session\SessionInfo\getId
getId()
Return the session ID.
Definition: SessionInfo.php:178
MediaWiki\Session\SessionManager\$allSessionBackends
SessionBackend[] $allSessionBackends
Definition: SessionManager.php:79
MediaWiki\Session\SessionInfo\forceUse
forceUse()
Force use of this SessionInfo if validation fails.
Definition: SessionInfo.php:209
BagOStuff
Class representing a cache/ephemeral data store.
Definition: BagOStuff.php:58
User
User
Definition: All_system_messages.txt:425
php
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
MediaWiki\Session\MetadataMergeException\getContext
getContext()
Get context data.
Definition: MetadataMergeException.php:59
Config
Interface for configuration instances.
Definition: Config.php:28
MWException
MediaWiki exception.
Definition: MWException.php:26
MediaWiki\Session\SessionManager\$config
Config $config
Definition: SessionManager.php:64
wfDeprecated
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
Definition: GlobalFunctions.php:1118
MediaWiki\Session\SessionManager\deregisterSessionBackend
deregisterSessionBackend(SessionBackend $backend)
Deregister a SessionBackend.
Definition: SessionManager.php:895
MediaWiki\Session\SessionManager\validateSessionId
static validateSessionId( $id)
Validate a session ID.
Definition: SessionManager.php:371
MediaWiki\Session\Session
Manages data for an an authenticated session.
Definition: Session.php:48
MediaWiki\Session\SessionProvider
A SessionProvider provides SessionInfo and support for Session.
Definition: SessionProvider.php:78
MediaWiki\Session\SessionInfo\getProvider
getProvider()
Return the provider.
Definition: SessionInfo.php:170
$blob
$blob
Definition: testCompression.php:65
MediaWiki\Session\SessionManager\invalidateSessionsForUser
invalidateSessionsForUser(User $user)
Invalidate sessions for a user.
Definition: SessionManager.php:313
MediaWiki\Session\SessionManager\$allSessionIds
SessionId[] $allSessionIds
Definition: SessionManager.php:82
MediaWiki
A helper class for throttling authentication attempts.
MediaWiki\Session\SessionManager\$globalSession
static Session null $globalSession
Definition: SessionManager.php:55
store
MediaWiki s SiteStore can be cached and stored in a flat in a json format If the SiteStore is frequently the file cache may provide a performance benefit over a database store
Definition: sitescache.txt:1
ObjectCache\getInstance
static getInstance( $id)
Get a cached instance of the specified type of cache object.
Definition: ObjectCache.php:92
MediaWiki\Session\SessionManager\singleton
static singleton()
Get the global SessionManager.
Definition: SessionManager.php:92
use
as see the revision history and available at free of to any person obtaining a copy of this software and associated documentation to deal in the Software without including without limitation the rights to use
Definition: MIT-LICENSE.txt:10
MediaWiki\Session\SessionManager\shutdown
shutdown()
Save all active sessions on shutdown.
Definition: SessionManager.php:465
MediaWiki\Session
Definition: BotPasswordSessionProvider.php:24
MediaWiki\Session\SessionInfo\wasPersisted
wasPersisted()
Return whether the session is persisted.
Definition: SessionInfo.php:233
array
The wiki should then use memcached to cache various data To use multiple just add more items to the array To increase the weight of a make its entry a array("192.168.0.1:11211", 2))
MediaWiki\Session\SessionManager\resetCache
static resetCache()
Reset the internal caching for unit testing.
Definition: SessionManager.php:956
string
This code would result in ircNotify being run twice when an article is and once for brion Hooks can return three possible true was required This is the default since MediaWiki *some string
Definition: hooks.txt:175
MediaWiki\Auth\AuthManager\callLegacyAuthPlugin
static callLegacyAuthPlugin( $method, array $params, $return=null)
Call a legacy AuthPlugin method, if necessary.
Definition: AuthManager.php:239
MediaWiki\Session\SessionManager\getVaryHeaders
getVaryHeaders()
Return the HTTP headers that need varying on.
Definition: SessionManager.php:327
MediaWiki\Session\SessionInfo\getProviderMetadata
getProviderMetadata()
Return provider metadata.
Definition: SessionInfo.php:241
MediaWiki\Session\PHPSessionHandler
Adapter for PHP's session handling.
Definition: PHPSessionHandler.php:34
$request
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g. with the RejectParserCacheValue hook) because MediaWiki won 't do it for you. & $defaults also a ContextSource after deleting those rows but within the same transaction you ll probably need to make sure the header is varied on $request
Definition: hooks.txt:2683
$name
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:302
MediaWiki\Session\SessionManager\$sessionProviders
SessionProvider[] $sessionProviders
Definition: SessionManager.php:70
MediaWiki\MediaWikiServices\getInstance
static getInstance()
Returns the global default instance of the top level service locator.
Definition: MediaWikiServices.php:120
MediaWiki\Session\SessionManager\getGlobalSession
static getGlobalSession()
Get the "global" session.
Definition: SessionManager.php:107
MediaWiki\Session\SessionManager\getSessionInfoForRequest
getSessionInfoForRequest(WebRequest $request)
Fetch the SessionInfo(s) for a request.
Definition: SessionManager.php:484
MediaWiki\Session\SessionBackend\getSessionId
getSessionId()
Fetch the SessionId object.
Definition: SessionBackend.php:214
MediaWiki\Session\SessionManager\setLogger
setLogger(LoggerInterface $logger)
Definition: SessionManager.php:186
MediaWiki\Session\SessionManager\getEmptySession
getEmptySession(WebRequest $request=null)
Create a new, empty session.
Definition: SessionManager.php:243
$header
$header
Definition: updateCredits.php:35
MediaWiki\Session\UserInfo\newFromName
static newFromName( $name, $verified=false)
Create an instance for a logged-in user by name.
Definition: UserInfo.php:102
MediaWiki\Session\SessionManager
This serves as the entry point to the MediaWiki session handling system.
Definition: SessionManager.php:50
CachedBagOStuff
Wrapper around a BagOStuff that caches data in memory.
Definition: CachedBagOStuff.php:36
MediaWiki\Session\SessionManager\$store
CachedBagOStuff null $store
Definition: SessionManager.php:67
MediaWiki\Session\SessionManager\$preventUsers
string[] $preventUsers
Definition: SessionManager.php:85
MWCryptRand\generateHex
static generateHex( $chars)
Generate a run of cryptographically random data and return it in hexadecimal string format.
Definition: MWCryptRand.php:71
$handler
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output modifiable modifiable after all normalizations have been except for the $wgMaxImageArea check set to true or false to override the $wgMaxImageArea check result gives extension the possibility to transform it themselves $handler
Definition: hooks.txt:813
RequestContext\getMain
static getMain()
Get the RequestContext object associated with the main request.
Definition: RequestContext.php:434
MediaWiki\Session\SessionManager\autoCreateUser
static autoCreateUser(User $user)
Auto-create the given user, if necessary.
Definition: SessionManager.php:388
WebRequest
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form stripping il...
Definition: WebRequest.php:41
MediaWiki\Session\SessionManager\getSessionFromInfo
getSessionFromInfo(SessionInfo $info, WebRequest $request)
Create a Session corresponding to the passed SessionInfo.
Definition: SessionManager.php:839
MediaWiki\Session\SessionInfo
Value object returned by SessionProvider.
Definition: SessionInfo.php:34
MediaWiki\Session\SessionManagerInterface
This exists to make IDEs happy, so they don't see the internal-but-required-to-be-public methods on S...
Definition: SessionManagerInterface.php:37
$options
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped & $options
Definition: hooks.txt:2044
MediaWiki\Session\SessionBackend\delaySave
delaySave()
Delay automatic saving while multiple updates are being made.
Definition: SessionBackend.php:591
MediaWiki\Session\SessionId
Value object holding the session ID in a manner that can be globally updated.
Definition: SessionId.php:38
MediaWiki\Session\UserInfo\newFromId
static newFromId( $id, $verified=false)
Create an instance for a logged-in user by ID.
Definition: UserInfo.php:84
as
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
MediaWikiServices
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency MediaWikiServices
Definition: injection.txt:23
MediaWiki\Session\SessionManager\getProvider
getProvider( $name)
Get a session provider by name.
Definition: SessionManager.php:456
MediaWiki\Session\SessionManager\changeBackendId
changeBackendId(SessionBackend $backend)
Change a SessionBackend's ID.
Definition: SessionManager.php:913
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:47
MediaWiki\Session\SessionInfo\MIN_PRIORITY
const MIN_PRIORITY
Minimum allowed priority.
Definition: SessionInfo.php:36
Hooks\run
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200
$username
this hook is for auditing only or null if authentication failed before getting that far $username
Definition: hooks.txt:813
MediaWiki\Session\SessionManager\getSessionForRequest
getSessionForRequest(WebRequest $request)
Fetch the session for a request (or a new empty session if none is attached to it)
Definition: SessionManager.php:190
MediaWiki\Session\SessionManager\$logger
LoggerInterface $logger
Definition: SessionManager.php:61
MediaWiki\Session\SessionManager\$varyCookies
string[] $varyCookies
Definition: SessionManager.php:73
MediaWiki\Session\SessionInfo\getUserInfo
getUserInfo()
Return the user.
Definition: SessionInfo.php:225
MediaWiki\Session\SessionInfo\wasRemembered
wasRemembered()
Return whether the user was remembered.
Definition: SessionInfo.php:260
MediaWiki\Session\SessionInfo\isIdSafe
isIdSafe()
Indicate whether the ID is "safe".
Definition: SessionInfo.php:194
MediaWiki\Session\SessionManager\__construct
__construct( $options=[])
Definition: SessionManager.php:148
MediaWiki\Session\SessionBackend
This is the actual workhorse for Session.
Definition: SessionBackend.php:49