MediaWiki  1.27.1
SessionManager.php
Go to the documentation of this file.
1 <?php
24 namespace MediaWiki\Session;
25 
30 use Config;
32 use User;
34 
41 final class SessionManager implements SessionManagerInterface {
43  private static $instance = null;
44 
46  private static $globalSession = null;
47 
49  private static $globalSessionRequest = null;
50 
52  private $logger;
53 
55  private $config;
56 
58  private $store;
59 
61  private $sessionProviders = null;
62 
64  private $varyCookies = null;
65 
67  private $varyHeaders = null;
68 
70  private $allSessionBackends = [];
71 
73  private $allSessionIds = [];
74 
76  private $preventUsers = [];
77 
83  public static function singleton() {
84  if ( self::$instance === null ) {
85  self::$instance = new self();
86  }
87  return self::$instance;
88  }
89 
98  public static function getGlobalSession() {
100  $id = '';
101  } else {
102  $id = session_id();
103  }
104 
105  $request = \RequestContext::getMain()->getRequest();
106  if (
107  !self::$globalSession // No global session is set up yet
108  || self::$globalSessionRequest !== $request // The global WebRequest changed
109  || $id !== '' && self::$globalSession->getId() !== $id // Someone messed with session_id()
110  ) {
111  self::$globalSessionRequest = $request;
112  if ( $id === '' ) {
113  // session_id() wasn't used, so fetch the Session from the WebRequest.
114  // We use $request->getSession() instead of $singleton->getSessionForRequest()
115  // because doing the latter would require a public
116  // "$request->getSessionId()" method that would confuse end
117  // users by returning SessionId|null where they'd expect it to
118  // be short for $request->getSession()->getId(), and would
119  // wind up being a duplicate of the code in
120  // $request->getSession() anyway.
121  self::$globalSession = $request->getSession();
122  } else {
123  // Someone used session_id(), so we need to follow suit.
124  // Note this overwrites whatever session might already be
125  // associated with $request with the one for $id.
126  self::$globalSession = self::singleton()->getSessionById( $id, true, $request )
127  ?: $request->getSession();
128  }
129  }
130  return self::$globalSession;
131  }
132 
139  public function __construct( $options = [] ) {
140  if ( isset( $options['config'] ) ) {
141  $this->config = $options['config'];
142  if ( !$this->config instanceof Config ) {
143  throw new \InvalidArgumentException(
144  '$options[\'config\'] must be an instance of Config'
145  );
146  }
147  } else {
148  $this->config = \ConfigFactory::getDefaultInstance()->makeConfig( 'main' );
149  }
150 
151  if ( isset( $options['logger'] ) ) {
152  if ( !$options['logger'] instanceof LoggerInterface ) {
153  throw new \InvalidArgumentException(
154  '$options[\'logger\'] must be an instance of LoggerInterface'
155  );
156  }
157  $this->setLogger( $options['logger'] );
158  } else {
159  $this->setLogger( \MediaWiki\Logger\LoggerFactory::getInstance( 'session' ) );
160  }
161 
162  if ( isset( $options['store'] ) ) {
163  if ( !$options['store'] instanceof BagOStuff ) {
164  throw new \InvalidArgumentException(
165  '$options[\'store\'] must be an instance of BagOStuff'
166  );
167  }
168  $store = $options['store'];
169  } else {
170  $store = \ObjectCache::getInstance( $this->config->get( 'SessionCacheType' ) );
171  }
172  $this->store = $store instanceof CachedBagOStuff ? $store : new CachedBagOStuff( $store );
173 
174  register_shutdown_function( [ $this, 'shutdown' ] );
175  }
176 
177  public function setLogger( LoggerInterface $logger ) {
178  $this->logger = $logger;
179  }
180 
182  $info = $this->getSessionInfoForRequest( $request );
183 
184  if ( !$info ) {
185  $session = $this->getEmptySession( $request );
186  } else {
187  $session = $this->getSessionFromInfo( $info, $request );
188  }
189  return $session;
190  }
191 
192  public function getSessionById( $id, $create = false, WebRequest $request = null ) {
193  if ( !self::validateSessionId( $id ) ) {
194  throw new \InvalidArgumentException( 'Invalid session ID' );
195  }
196  if ( !$request ) {
197  $request = new FauxRequest;
198  }
199 
200  $session = null;
201  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [ 'id' => $id, 'idIsSafe' => true ] );
202 
203  // If we already have the backend loaded, use it directly
204  if ( isset( $this->allSessionBackends[$id] ) ) {
205  return $this->getSessionFromInfo( $info, $request );
206  }
207 
208  // Test if the session is in storage, and if so try to load it.
209  $key = wfMemcKey( 'MWSession', $id );
210  if ( is_array( $this->store->get( $key ) ) ) {
211  $create = false; // If loading fails, don't bother creating because it probably will fail too.
212  if ( $this->loadSessionInfoFromStore( $info, $request ) ) {
213  $session = $this->getSessionFromInfo( $info, $request );
214  }
215  }
216 
217  if ( $create && $session === null ) {
218  $ex = null;
219  try {
220  $session = $this->getEmptySessionInternal( $request, $id );
221  } catch ( \Exception $ex ) {
222  $this->logger->error( 'Failed to create empty session: {exception}',
223  [
224  'method' => __METHOD__,
225  'exception' => $ex,
226  ] );
227  $session = null;
228  }
229  }
230 
231  return $session;
232  }
233 
234  public function getEmptySession( WebRequest $request = null ) {
235  return $this->getEmptySessionInternal( $request );
236  }
237 
244  private function getEmptySessionInternal( WebRequest $request = null, $id = null ) {
245  if ( $id !== null ) {
246  if ( !self::validateSessionId( $id ) ) {
247  throw new \InvalidArgumentException( 'Invalid session ID' );
248  }
249 
250  $key = wfMemcKey( 'MWSession', $id );
251  if ( is_array( $this->store->get( $key ) ) ) {
252  throw new \InvalidArgumentException( 'Session ID already exists' );
253  }
254  }
255  if ( !$request ) {
256  $request = new FauxRequest;
257  }
258 
259  $infos = [];
260  foreach ( $this->getProviders() as $provider ) {
261  $info = $provider->newSessionInfo( $id );
262  if ( !$info ) {
263  continue;
264  }
265  if ( $info->getProvider() !== $provider ) {
266  throw new \UnexpectedValueException(
267  "$provider returned an empty session info for a different provider: $info"
268  );
269  }
270  if ( $id !== null && $info->getId() !== $id ) {
271  throw new \UnexpectedValueException(
272  "$provider returned empty session info with a wrong id: " .
273  $info->getId() . ' != ' . $id
274  );
275  }
276  if ( !$info->isIdSafe() ) {
277  throw new \UnexpectedValueException(
278  "$provider returned empty session info with id flagged unsafe"
279  );
280  }
281  $compare = $infos ? SessionInfo::compare( $infos[0], $info ) : -1;
282  if ( $compare > 0 ) {
283  continue;
284  }
285  if ( $compare === 0 ) {
286  $infos[] = $info;
287  } else {
288  $infos = [ $info ];
289  }
290  }
291 
292  // Make sure there's exactly one
293  if ( count( $infos ) > 1 ) {
294  throw new \UnexpectedValueException(
295  'Multiple empty sessions tied for top priority: ' . implode( ', ', $infos )
296  );
297  } elseif ( count( $infos ) < 1 ) {
298  throw new \UnexpectedValueException( 'No provider could provide an empty session!' );
299  }
300 
301  return $this->getSessionFromInfo( $infos[0], $request );
302  }
303 
304  public function invalidateSessionsForUser( User $user ) {
305  $user->setToken();
306  $user->saveSettings();
307 
308  $authUser = \MediaWiki\Auth\AuthManager::callLegacyAuthPlugin( 'getUserInstance', [ &$user ] );
309  if ( $authUser ) {
310  $authUser->resetAuthToken();
311  }
312 
313  foreach ( $this->getProviders() as $provider ) {
314  $provider->invalidateSessionsForUser( $user );
315  }
316  }
317 
318  public function getVaryHeaders() {
319  // @codeCoverageIgnoreStart
320  if ( defined( 'MW_NO_SESSION' ) && MW_NO_SESSION !== 'warn' ) {
321  return [];
322  }
323  // @codeCoverageIgnoreEnd
324  if ( $this->varyHeaders === null ) {
325  $headers = [];
326  foreach ( $this->getProviders() as $provider ) {
327  foreach ( $provider->getVaryHeaders() as $header => $options ) {
328  if ( !isset( $headers[$header] ) ) {
329  $headers[$header] = [];
330  }
331  if ( is_array( $options ) ) {
332  $headers[$header] = array_unique( array_merge( $headers[$header], $options ) );
333  }
334  }
335  }
336  $this->varyHeaders = $headers;
337  }
338  return $this->varyHeaders;
339  }
340 
341  public function getVaryCookies() {
342  // @codeCoverageIgnoreStart
343  if ( defined( 'MW_NO_SESSION' ) && MW_NO_SESSION !== 'warn' ) {
344  return [];
345  }
346  // @codeCoverageIgnoreEnd
347  if ( $this->varyCookies === null ) {
348  $cookies = [];
349  foreach ( $this->getProviders() as $provider ) {
350  $cookies = array_merge( $cookies, $provider->getVaryCookies() );
351  }
352  $this->varyCookies = array_values( array_unique( $cookies ) );
353  }
354  return $this->varyCookies;
355  }
356 
362  public static function validateSessionId( $id ) {
363  return is_string( $id ) && preg_match( '/^[a-zA-Z0-9_-]{32,}$/', $id );
364  }
365 
379  public static function autoCreateUser( User $user ) {
380  wfDeprecated( __METHOD__, '1.27' );
381  return \MediaWiki\Auth\AuthManager::singleton()->autoCreateUser(
382  $user,
383  \MediaWiki\Auth\AuthManager::AUTOCREATE_SOURCE_SESSION,
384  false
385  )->isGood();
386  }
387 
397  public function preventSessionsForUser( $username ) {
398  $this->preventUsers[$username] = true;
399 
400  // Instruct the session providers to kill any other sessions too.
401  foreach ( $this->getProviders() as $provider ) {
402  $provider->preventSessionsForUser( $username );
403  }
404  }
405 
412  public function isUserSessionPrevented( $username ) {
413  return !empty( $this->preventUsers[$username] );
414  }
415 
420  protected function getProviders() {
421  if ( $this->sessionProviders === null ) {
422  $this->sessionProviders = [];
423  foreach ( $this->config->get( 'SessionProviders' ) as $spec ) {
424  $provider = \ObjectFactory::getObjectFromSpec( $spec );
425  $provider->setLogger( $this->logger );
426  $provider->setConfig( $this->config );
427  $provider->setManager( $this );
428  if ( isset( $this->sessionProviders[(string)$provider] ) ) {
429  throw new \UnexpectedValueException( "Duplicate provider name \"$provider\"" );
430  }
431  $this->sessionProviders[(string)$provider] = $provider;
432  }
433  }
435  }
436 
447  public function getProvider( $name ) {
448  $providers = $this->getProviders();
449  return isset( $providers[$name] ) ? $providers[$name] : null;
450  }
451 
456  public function shutdown() {
457  if ( $this->allSessionBackends ) {
458  $this->logger->debug( 'Saving all sessions on shutdown' );
459  if ( session_id() !== '' ) {
460  // @codeCoverageIgnoreStart
461  session_write_close();
462  }
463  // @codeCoverageIgnoreEnd
464  foreach ( $this->allSessionBackends as $backend ) {
465  $backend->shutdown();
466  }
467  }
468  }
469 
476  // Call all providers to fetch "the" session
477  $infos = [];
478  foreach ( $this->getProviders() as $provider ) {
479  $info = $provider->provideSessionInfo( $request );
480  if ( !$info ) {
481  continue;
482  }
483  if ( $info->getProvider() !== $provider ) {
484  throw new \UnexpectedValueException(
485  "$provider returned session info for a different provider: $info"
486  );
487  }
488  $infos[] = $info;
489  }
490 
491  // Sort the SessionInfos. Then find the first one that can be
492  // successfully loaded, and then all the ones after it with the same
493  // priority.
494  usort( $infos, 'MediaWiki\\Session\\SessionInfo::compare' );
495  $retInfos = [];
496  while ( $infos ) {
497  $info = array_pop( $infos );
498  if ( $this->loadSessionInfoFromStore( $info, $request ) ) {
499  $retInfos[] = $info;
500  while ( $infos ) {
501  $info = array_pop( $infos );
502  if ( SessionInfo::compare( $retInfos[0], $info ) ) {
503  // We hit a lower priority, stop checking.
504  break;
505  }
506  if ( $this->loadSessionInfoFromStore( $info, $request ) ) {
507  // This is going to error out below, but we want to
508  // provide a complete list.
509  $retInfos[] = $info;
510  } else {
511  // Session load failed, so unpersist it from this request
512  $info->getProvider()->unpersistSession( $request );
513  }
514  }
515  } else {
516  // Session load failed, so unpersist it from this request
517  $info->getProvider()->unpersistSession( $request );
518  }
519  }
520 
521  if ( count( $retInfos ) > 1 ) {
522  $ex = new \OverflowException(
523  'Multiple sessions for this request tied for top priority: ' . implode( ', ', $retInfos )
524  );
525  $ex->sessionInfos = $retInfos;
526  throw $ex;
527  }
528 
529  return $retInfos ? $retInfos[0] : null;
530  }
531 
540  $key = wfMemcKey( 'MWSession', $info->getId() );
541  $blob = $this->store->get( $key );
542 
543  // If we got data from the store and the SessionInfo says to force use,
544  // "fail" means to delete the data from the store and retry. Otherwise,
545  // "fail" is just return false.
546  if ( $info->forceUse() && $blob !== false ) {
547  $failHandler = function () use ( $key, &$info, $request ) {
548  $this->store->delete( $key );
549  return $this->loadSessionInfoFromStore( $info, $request );
550  };
551  } else {
552  $failHandler = function () {
553  return false;
554  };
555  }
556 
557  $newParams = [];
558 
559  if ( $blob !== false ) {
560  // Sanity check: blob must be an array, if it's saved at all
561  if ( !is_array( $blob ) ) {
562  $this->logger->warning( 'Session "{session}": Bad data', [
563  'session' => $info,
564  ] );
565  $this->store->delete( $key );
566  return $failHandler();
567  }
568 
569  // Sanity check: blob has data and metadata arrays
570  if ( !isset( $blob['data'] ) || !is_array( $blob['data'] ) ||
571  !isset( $blob['metadata'] ) || !is_array( $blob['metadata'] )
572  ) {
573  $this->logger->warning( 'Session "{session}": Bad data structure', [
574  'session' => $info,
575  ] );
576  $this->store->delete( $key );
577  return $failHandler();
578  }
579 
580  $data = $blob['data'];
581  $metadata = $blob['metadata'];
582 
583  // Sanity check: metadata must be an array and must contain certain
584  // keys, if it's saved at all
585  if ( !array_key_exists( 'userId', $metadata ) ||
586  !array_key_exists( 'userName', $metadata ) ||
587  !array_key_exists( 'userToken', $metadata ) ||
588  !array_key_exists( 'provider', $metadata )
589  ) {
590  $this->logger->warning( 'Session "{session}": Bad metadata', [
591  'session' => $info,
592  ] );
593  $this->store->delete( $key );
594  return $failHandler();
595  }
596 
597  // First, load the provider from metadata, or validate it against the metadata.
598  $provider = $info->getProvider();
599  if ( $provider === null ) {
600  $newParams['provider'] = $provider = $this->getProvider( $metadata['provider'] );
601  if ( !$provider ) {
602  $this->logger->warning(
603  'Session "{session}": Unknown provider ' . $metadata['provider'],
604  [
605  'session' => $info,
606  ]
607  );
608  $this->store->delete( $key );
609  return $failHandler();
610  }
611  } elseif ( $metadata['provider'] !== (string)$provider ) {
612  $this->logger->warning( 'Session "{session}": Wrong provider ' .
613  $metadata['provider'] . ' !== ' . $provider,
614  [
615  'session' => $info,
616  ] );
617  return $failHandler();
618  }
619 
620  // Load provider metadata from metadata, or validate it against the metadata
621  $providerMetadata = $info->getProviderMetadata();
622  if ( isset( $metadata['providerMetadata'] ) ) {
623  if ( $providerMetadata === null ) {
624  $newParams['metadata'] = $metadata['providerMetadata'];
625  } else {
626  try {
627  $newProviderMetadata = $provider->mergeMetadata(
628  $metadata['providerMetadata'], $providerMetadata
629  );
630  if ( $newProviderMetadata !== $providerMetadata ) {
631  $newParams['metadata'] = $newProviderMetadata;
632  }
633  } catch ( MetadataMergeException $ex ) {
634  $this->logger->warning(
635  'Session "{session}": Metadata merge failed: {exception}',
636  [
637  'session' => $info,
638  'exception' => $ex,
639  ] + $ex->getContext()
640  );
641  return $failHandler();
642  }
643  }
644  }
645 
646  // Next, load the user from metadata, or validate it against the metadata.
647  $userInfo = $info->getUserInfo();
648  if ( !$userInfo ) {
649  // For loading, id is preferred to name.
650  try {
651  if ( $metadata['userId'] ) {
652  $userInfo = UserInfo::newFromId( $metadata['userId'] );
653  } elseif ( $metadata['userName'] !== null ) { // Shouldn't happen, but just in case
654  $userInfo = UserInfo::newFromName( $metadata['userName'] );
655  } else {
656  $userInfo = UserInfo::newAnonymous();
657  }
658  } catch ( \InvalidArgumentException $ex ) {
659  $this->logger->error( 'Session "{session}": {exception}', [
660  'session' => $info,
661  'exception' => $ex,
662  ] );
663  return $failHandler();
664  }
665  $newParams['userInfo'] = $userInfo;
666  } else {
667  // User validation passes if user ID matches, or if there
668  // is no saved ID and the names match.
669  if ( $metadata['userId'] ) {
670  if ( $metadata['userId'] !== $userInfo->getId() ) {
671  $this->logger->warning(
672  'Session "{session}": User ID mismatch, {uid_a} !== {uid_b}',
673  [
674  'session' => $info,
675  'uid_a' => $metadata['userId'],
676  'uid_b' => $userInfo->getId(),
677  ] );
678  return $failHandler();
679  }
680 
681  // If the user was renamed, probably best to fail here.
682  if ( $metadata['userName'] !== null &&
683  $userInfo->getName() !== $metadata['userName']
684  ) {
685  $this->logger->warning(
686  'Session "{session}": User ID matched but name didn\'t (rename?), {uname_a} !== {uname_b}',
687  [
688  'session' => $info,
689  'uname_a' => $metadata['userName'],
690  'uname_b' => $userInfo->getName(),
691  ] );
692  return $failHandler();
693  }
694 
695  } elseif ( $metadata['userName'] !== null ) { // Shouldn't happen, but just in case
696  if ( $metadata['userName'] !== $userInfo->getName() ) {
697  $this->logger->warning(
698  'Session "{session}": User name mismatch, {uname_a} !== {uname_b}',
699  [
700  'session' => $info,
701  'uname_a' => $metadata['userName'],
702  'uname_b' => $userInfo->getName(),
703  ] );
704  return $failHandler();
705  }
706  } elseif ( !$userInfo->isAnon() ) {
707  // Metadata specifies an anonymous user, but the passed-in
708  // user isn't anonymous.
709  $this->logger->warning(
710  'Session "{session}": Metadata has an anonymous user, but a non-anon user was provided',
711  [
712  'session' => $info,
713  ] );
714  return $failHandler();
715  }
716  }
717 
718  // And if we have a token in the metadata, it must match the loaded/provided user.
719  if ( $metadata['userToken'] !== null &&
720  $userInfo->getToken() !== $metadata['userToken']
721  ) {
722  $this->logger->warning( 'Session "{session}": User token mismatch', [
723  'session' => $info,
724  ] );
725  return $failHandler();
726  }
727  if ( !$userInfo->isVerified() ) {
728  $newParams['userInfo'] = $userInfo->verified();
729  }
730 
731  if ( !empty( $metadata['remember'] ) && !$info->wasRemembered() ) {
732  $newParams['remembered'] = true;
733  }
734  if ( !empty( $metadata['forceHTTPS'] ) && !$info->forceHTTPS() ) {
735  $newParams['forceHTTPS'] = true;
736  }
737  if ( !empty( $metadata['persisted'] ) && !$info->wasPersisted() ) {
738  $newParams['persisted'] = true;
739  }
740 
741  if ( !$info->isIdSafe() ) {
742  $newParams['idIsSafe'] = true;
743  }
744  } else {
745  // No metadata, so we can't load the provider if one wasn't given.
746  if ( $info->getProvider() === null ) {
747  $this->logger->warning(
748  'Session "{session}": Null provider and no metadata',
749  [
750  'session' => $info,
751  ] );
752  return $failHandler();
753  }
754 
755  // If no user was provided and no metadata, it must be anon.
756  if ( !$info->getUserInfo() ) {
757  if ( $info->getProvider()->canChangeUser() ) {
758  $newParams['userInfo'] = UserInfo::newAnonymous();
759  } else {
760  $this->logger->info(
761  'Session "{session}": No user provided and provider cannot set user',
762  [
763  'session' => $info,
764  ] );
765  return $failHandler();
766  }
767  } elseif ( !$info->getUserInfo()->isVerified() ) {
768  $this->logger->warning(
769  'Session "{session}": Unverified user provided and no metadata to auth it',
770  [
771  'session' => $info,
772  ] );
773  return $failHandler();
774  }
775 
776  $data = false;
777  $metadata = false;
778 
779  if ( !$info->getProvider()->persistsSessionId() && !$info->isIdSafe() ) {
780  // The ID doesn't come from the user, so it should be safe
781  // (and if not, nothing we can do about it anyway)
782  $newParams['idIsSafe'] = true;
783  }
784  }
785 
786  // Construct the replacement SessionInfo, if necessary
787  if ( $newParams ) {
788  $newParams['copyFrom'] = $info;
789  $info = new SessionInfo( $info->getPriority(), $newParams );
790  }
791 
792  // Allow the provider to check the loaded SessionInfo
793  $providerMetadata = $info->getProviderMetadata();
794  if ( !$info->getProvider()->refreshSessionInfo( $info, $request, $providerMetadata ) ) {
795  return $failHandler();
796  }
797  if ( $providerMetadata !== $info->getProviderMetadata() ) {
798  $info = new SessionInfo( $info->getPriority(), [
799  'metadata' => $providerMetadata,
800  'copyFrom' => $info,
801  ] );
802  }
803 
804  // Give hooks a chance to abort. Combined with the SessionMetadata
805  // hook, this can allow for tying a session to an IP address or the
806  // like.
807  $reason = 'Hook aborted';
808  if ( !\Hooks::run(
809  'SessionCheckInfo',
810  [ &$reason, $info, $request, $metadata, $data ]
811  ) ) {
812  $this->logger->warning( 'Session "{session}": ' . $reason, [
813  'session' => $info,
814  ] );
815  return $failHandler();
816  }
817 
818  return true;
819  }
820 
829  public function getSessionFromInfo( SessionInfo $info, WebRequest $request ) {
830  // @codeCoverageIgnoreStart
831  if ( defined( 'MW_NO_SESSION' ) ) {
832  if ( MW_NO_SESSION === 'warn' ) {
833  // Undocumented safety case for converting existing entry points
834  $this->logger->error( 'Sessions are supposed to be disabled for this entry point', [
835  'exception' => new \BadMethodCallException( 'Sessions are disabled for this entry point' ),
836  ] );
837  } else {
838  throw new \BadMethodCallException( 'Sessions are disabled for this entry point' );
839  }
840  }
841  // @codeCoverageIgnoreEnd
842 
843  $id = $info->getId();
844 
845  if ( !isset( $this->allSessionBackends[$id] ) ) {
846  if ( !isset( $this->allSessionIds[$id] ) ) {
847  $this->allSessionIds[$id] = new SessionId( $id );
848  }
849  $backend = new SessionBackend(
850  $this->allSessionIds[$id],
851  $info,
852  $this->store,
853  $this->logger,
854  $this->config->get( 'ObjectCacheSessionExpiry' )
855  );
856  $this->allSessionBackends[$id] = $backend;
857  $delay = $backend->delaySave();
858  } else {
859  $backend = $this->allSessionBackends[$id];
860  $delay = $backend->delaySave();
861  if ( $info->wasPersisted() ) {
862  $backend->persist();
863  }
864  if ( $info->wasRemembered() ) {
865  $backend->setRememberUser( true );
866  }
867  }
868 
869  $request->setSessionId( $backend->getSessionId() );
870  $session = $backend->getSession( $request );
871 
872  if ( !$info->isIdSafe() ) {
873  $session->resetId();
874  }
875 
876  \ScopedCallback::consume( $delay );
877  return $session;
878  }
879 
885  public function deregisterSessionBackend( SessionBackend $backend ) {
886  $id = $backend->getId();
887  if ( !isset( $this->allSessionBackends[$id] ) || !isset( $this->allSessionIds[$id] ) ||
888  $this->allSessionBackends[$id] !== $backend ||
889  $this->allSessionIds[$id] !== $backend->getSessionId()
890  ) {
891  throw new \InvalidArgumentException( 'Backend was not registered with this SessionManager' );
892  }
893 
894  unset( $this->allSessionBackends[$id] );
895  // Explicitly do not unset $this->allSessionIds[$id]
896  }
897 
903  public function changeBackendId( SessionBackend $backend ) {
904  $sessionId = $backend->getSessionId();
905  $oldId = (string)$sessionId;
906  if ( !isset( $this->allSessionBackends[$oldId] ) || !isset( $this->allSessionIds[$oldId] ) ||
907  $this->allSessionBackends[$oldId] !== $backend ||
908  $this->allSessionIds[$oldId] !== $sessionId
909  ) {
910  throw new \InvalidArgumentException( 'Backend was not registered with this SessionManager' );
911  }
912 
913  $newId = $this->generateSessionId();
914 
915  unset( $this->allSessionBackends[$oldId], $this->allSessionIds[$oldId] );
916  $sessionId->setId( $newId );
917  $this->allSessionBackends[$newId] = $backend;
918  $this->allSessionIds[$newId] = $sessionId;
919  }
920 
925  public function generateSessionId() {
926  do {
927  $id = wfBaseConvert( \MWCryptRand::generateHex( 40 ), 16, 32, 32 );
928  $key = wfMemcKey( 'MWSession', $id );
929  } while ( isset( $this->allSessionIds[$id] ) || is_array( $this->store->get( $key ) ) );
930  return $id;
931  }
932 
939  $handler->setManager( $this, $this->store, $this->logger );
940  }
941 
945  public static function resetCache() {
946  if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
947  // @codeCoverageIgnoreStart
948  throw new MWException( __METHOD__ . ' may only be called from unit tests!' );
949  // @codeCoverageIgnoreEnd
950  }
951 
952  self::$globalSession = null;
953  self::$globalSessionRequest = null;
954  }
955 
958 }
getVaryCookies()
Return the list of cookies that need varying on.
const MIN_PRIORITY
Minimum allowed priority.
Definition: SessionInfo.php:36
getSessionId()
Fetch the SessionId object.
This is the actual workhorse for Session.
getProviders()
Get the available SessionProviders.
getUserInfo()
Return the user.
magic word the default is to use $key to get the and $key value or $key value text $key value html to format the value $key
Definition: hooks.txt:2321
static getObjectFromSpec($spec)
Instantiate an object based on a specification array.
saveSettings()
Save this user's settings into the database.
Definition: User.php:3767
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
changeBackendId(SessionBackend $backend)
Change a SessionBackend's ID.
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:189
getEmptySessionInternal(WebRequest $request=null, $id=null)
getPriority()
Return the priority.
static getInstance($id)
Get a cached instance of the specified type of cache object.
Definition: ObjectCache.php:92
getEmptySession(WebRequest $request=null)
Fetch a new, empty session.
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:177
static SessionManager null $instance
static newFromId($id, $verified=false)
Create an instance for a logged-in user by ID.
Definition: UserInfo.php:84
static autoCreateUser(User $user)
Auto-create the given user, if necessary.
shutdown()
Save all active sessions on shutdown.
A helper class for throttling authentication attempts.
getId()
Return the session ID.
setManager(SessionManager $manager, BagOStuff $store, LoggerInterface $logger)
Set the manager, store, and logger.
setToken($token=false)
Set the random token (used for persistent authentication) Called from loadDefaults() among other plac...
Definition: User.php:2591
Subclass of UnexpectedValueException that can be annotated with additional data for debug logging...
forceUse()
Force use of this SessionInfo if validation fails.
delaySave()
Delay automatic saving while multiple updates are being made.
getSessionFromInfo(SessionInfo $info, WebRequest $request)
Create a session corresponding to the passed SessionInfo.
setupPHPSessionHandler(PHPSessionHandler $handler)
Call setters on a PHPSessionHandler.
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
const MW_NO_SESSION
Definition: load.php:30
static newAnonymous()
Create an instance for an anonymous (i.e.
Definition: UserInfo.php:74
static resetCache()
Reset the internal caching for unit testing.
getSessionForRequest(WebRequest $request)
Fetch the session for a request.
static getMain()
Static methods.
getSessionInfoForRequest(WebRequest $request)
Fetch the SessionInfo(s) for a request.
getProvider($name)
Get a session provider by name.
deregisterSessionBackend(SessionBackend $backend)
Deregister a SessionBackend.
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context $options
Definition: hooks.txt:1004
getProvider()
Return the provider.
isUserSessionPrevented($username)
Test if a user is prevented.
setLogger(LoggerInterface $logger)
wfDeprecated($function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
static WebRequest null $globalSessionRequest
static callLegacyAuthPlugin($method, array $params, $return=null)
Call a legacy AuthPlugin method, if necessary.
getId()
Returns the session ID.
static run($event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:131
getProviderMetadata()
Return provider metadata.
wasPersisted()
Return whether the session is persisted.
Adapter for PHP's session handling.
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
static compare($a, $b)
Compare two SessionInfo objects by priority.
static singleton()
Get the global SessionManager.
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 local account $user
Definition: hooks.txt:242
setSessionId(SessionId $sessionId)
Set the session for this request.
Definition: WebRequest.php:711
generateSessionId()
Generate a new random session ID.
static getDefaultInstance()
static isEnabled()
Test whether the handler is installed and enabled.
static getGlobalSession()
Get the "global" session.
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
invalidateSessionsForUser(User $user)
Invalidate sessions for a user.
this hook is for auditing only or null if authentication failed before getting that far $username
Definition: hooks.txt:762
wfBaseConvert($input, $sourceBase, $destBase, $pad=1, $lowercase=true, $engine= 'auto')
Convert an arbitrarily-long digit string from one numeric base to another, optionally zero-padding to...
error also a ContextSource you ll probably need to make sure the header is varied on $request
Definition: hooks.txt:2418
preventSessionsForUser($username)
Prevent future sessions for the user.
static validateSessionId($id)
Validate a session ID.
Value object holding the session ID in a manner that can be globally updated.
Definition: SessionId.php:38
static generateHex($chars, $forceStrong=false)
Generate a run of (ideally) cryptographically random data and return it in hexadecimal string format...
getVaryHeaders()
Return the HTTP headers that need varying on.
static consume(ScopedCallback &$sc=null)
Trigger a scoped callback and destroy it.
static Session null $globalSession
wfMemcKey()
Make a cache key for the local wiki.
This serves as the entry point to the MediaWiki session handling system.
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:762
This exists to make IDEs happy, so they don't see the internal-but-required-to-be-public methods on S...
getSessionById($id, $create=false, WebRequest $request=null)
Fetch a session by ID.
static newFromName($name, $verified=false)
Create an instance for a logged-in user by name.
Definition: UserInfo.php:102
forceHTTPS()
Whether this session should only be used over HTTPS.
Value object returned by SessionProvider.
Definition: SessionInfo.php:34
loadSessionInfoFromStore(SessionInfo &$info, WebRequest $request)
Load and verify the session info against the store.
wasRemembered()
Return whether the user was remembered.
isIdSafe()
Indicate whether the ID is "safe".
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:310