MediaWiki  1.29.2
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 
49 final class SessionManager implements SessionManagerInterface {
51  private static $instance = null;
52 
54  private static $globalSession = null;
55 
57  private static $globalSessionRequest = null;
58 
60  private $logger;
61 
63  private $config;
64 
66  private $store;
67 
69  private $sessionProviders = null;
70 
72  private $varyCookies = null;
73 
75  private $varyHeaders = null;
76 
78  private $allSessionBackends = [];
79 
81  private $allSessionIds = [];
82 
84  private $preventUsers = [];
85 
91  public static function singleton() {
92  if ( self::$instance === null ) {
93  self::$instance = new self();
94  }
95  return self::$instance;
96  }
97 
106  public static function getGlobalSession() {
107  if ( !PHPSessionHandler::isEnabled() ) {
108  $id = '';
109  } else {
110  $id = session_id();
111  }
112 
113  $request = \RequestContext::getMain()->getRequest();
114  if (
115  !self::$globalSession // No global session is set up yet
116  || self::$globalSessionRequest !== $request // The global WebRequest changed
117  || $id !== '' && self::$globalSession->getId() !== $id // Someone messed with session_id()
118  ) {
119  self::$globalSessionRequest = $request;
120  if ( $id === '' ) {
121  // session_id() wasn't used, so fetch the Session from the WebRequest.
122  // We use $request->getSession() instead of $singleton->getSessionForRequest()
123  // because doing the latter would require a public
124  // "$request->getSessionId()" method that would confuse end
125  // users by returning SessionId|null where they'd expect it to
126  // be short for $request->getSession()->getId(), and would
127  // wind up being a duplicate of the code in
128  // $request->getSession() anyway.
129  self::$globalSession = $request->getSession();
130  } else {
131  // Someone used session_id(), so we need to follow suit.
132  // Note this overwrites whatever session might already be
133  // associated with $request with the one for $id.
134  self::$globalSession = self::singleton()->getSessionById( $id, true, $request )
135  ?: $request->getSession();
136  }
137  }
138  return self::$globalSession;
139  }
140 
147  public function __construct( $options = [] ) {
148  if ( isset( $options['config'] ) ) {
149  $this->config = $options['config'];
150  if ( !$this->config instanceof Config ) {
151  throw new \InvalidArgumentException(
152  '$options[\'config\'] must be an instance of Config'
153  );
154  }
155  } else {
156  $this->config = MediaWikiServices::getInstance()->getMainConfig();
157  }
158 
159  if ( isset( $options['logger'] ) ) {
160  if ( !$options['logger'] instanceof LoggerInterface ) {
161  throw new \InvalidArgumentException(
162  '$options[\'logger\'] must be an instance of LoggerInterface'
163  );
164  }
165  $this->setLogger( $options['logger'] );
166  } else {
167  $this->setLogger( \MediaWiki\Logger\LoggerFactory::getInstance( 'session' ) );
168  }
169 
170  if ( isset( $options['store'] ) ) {
171  if ( !$options['store'] instanceof BagOStuff ) {
172  throw new \InvalidArgumentException(
173  '$options[\'store\'] must be an instance of BagOStuff'
174  );
175  }
176  $store = $options['store'];
177  } else {
178  $store = \ObjectCache::getInstance( $this->config->get( 'SessionCacheType' ) );
179  }
180  $this->store = $store instanceof CachedBagOStuff ? $store : new CachedBagOStuff( $store );
181 
182  register_shutdown_function( [ $this, 'shutdown' ] );
183  }
184 
185  public function setLogger( LoggerInterface $logger ) {
186  $this->logger = $logger;
187  }
188 
190  $info = $this->getSessionInfoForRequest( $request );
191 
192  if ( !$info ) {
193  $session = $this->getEmptySession( $request );
194  } else {
195  $session = $this->getSessionFromInfo( $info, $request );
196  }
197  return $session;
198  }
199 
200  public function getSessionById( $id, $create = false, WebRequest $request = null ) {
201  if ( !self::validateSessionId( $id ) ) {
202  throw new \InvalidArgumentException( 'Invalid session ID' );
203  }
204  if ( !$request ) {
205  $request = new FauxRequest;
206  }
207 
208  $session = null;
209  $info = new SessionInfo( SessionInfo::MIN_PRIORITY, [ 'id' => $id, 'idIsSafe' => true ] );
210 
211  // If we already have the backend loaded, use it directly
212  if ( isset( $this->allSessionBackends[$id] ) ) {
213  return $this->getSessionFromInfo( $info, $request );
214  }
215 
216  // Test if the session is in storage, and if so try to load it.
217  $key = wfMemcKey( 'MWSession', $id );
218  if ( is_array( $this->store->get( $key ) ) ) {
219  $create = false; // If loading fails, don't bother creating because it probably will fail too.
220  if ( $this->loadSessionInfoFromStore( $info, $request ) ) {
221  $session = $this->getSessionFromInfo( $info, $request );
222  }
223  }
224 
225  if ( $create && $session === null ) {
226  $ex = null;
227  try {
228  $session = $this->getEmptySessionInternal( $request, $id );
229  } catch ( \Exception $ex ) {
230  $this->logger->error( 'Failed to create empty session: {exception}',
231  [
232  'method' => __METHOD__,
233  'exception' => $ex,
234  ] );
235  $session = null;
236  }
237  }
238 
239  return $session;
240  }
241 
242  public function getEmptySession( WebRequest $request = null ) {
243  return $this->getEmptySessionInternal( $request );
244  }
245 
252  private function getEmptySessionInternal( WebRequest $request = null, $id = null ) {
253  if ( $id !== null ) {
254  if ( !self::validateSessionId( $id ) ) {
255  throw new \InvalidArgumentException( 'Invalid session ID' );
256  }
257 
258  $key = wfMemcKey( 'MWSession', $id );
259  if ( is_array( $this->store->get( $key ) ) ) {
260  throw new \InvalidArgumentException( 'Session ID already exists' );
261  }
262  }
263  if ( !$request ) {
264  $request = new FauxRequest;
265  }
266 
267  $infos = [];
268  foreach ( $this->getProviders() as $provider ) {
269  $info = $provider->newSessionInfo( $id );
270  if ( !$info ) {
271  continue;
272  }
273  if ( $info->getProvider() !== $provider ) {
274  throw new \UnexpectedValueException(
275  "$provider returned an empty session info for a different provider: $info"
276  );
277  }
278  if ( $id !== null && $info->getId() !== $id ) {
279  throw new \UnexpectedValueException(
280  "$provider returned empty session info with a wrong id: " .
281  $info->getId() . ' != ' . $id
282  );
283  }
284  if ( !$info->isIdSafe() ) {
285  throw new \UnexpectedValueException(
286  "$provider returned empty session info with id flagged unsafe"
287  );
288  }
289  $compare = $infos ? SessionInfo::compare( $infos[0], $info ) : -1;
290  if ( $compare > 0 ) {
291  continue;
292  }
293  if ( $compare === 0 ) {
294  $infos[] = $info;
295  } else {
296  $infos = [ $info ];
297  }
298  }
299 
300  // Make sure there's exactly one
301  if ( count( $infos ) > 1 ) {
302  throw new \UnexpectedValueException(
303  'Multiple empty sessions tied for top priority: ' . implode( ', ', $infos )
304  );
305  } elseif ( count( $infos ) < 1 ) {
306  throw new \UnexpectedValueException( 'No provider could provide an empty session!' );
307  }
308 
309  return $this->getSessionFromInfo( $infos[0], $request );
310  }
311 
312  public function invalidateSessionsForUser( User $user ) {
313  $user->setToken();
314  $user->saveSettings();
315 
316  $authUser = \MediaWiki\Auth\AuthManager::callLegacyAuthPlugin( 'getUserInstance', [ &$user ] );
317  if ( $authUser ) {
318  $authUser->resetAuthToken();
319  }
320 
321  foreach ( $this->getProviders() as $provider ) {
322  $provider->invalidateSessionsForUser( $user );
323  }
324  }
325 
326  public function getVaryHeaders() {
327  // @codeCoverageIgnoreStart
328  if ( defined( 'MW_NO_SESSION' ) && MW_NO_SESSION !== 'warn' ) {
329  return [];
330  }
331  // @codeCoverageIgnoreEnd
332  if ( $this->varyHeaders === null ) {
333  $headers = [];
334  foreach ( $this->getProviders() as $provider ) {
335  foreach ( $provider->getVaryHeaders() as $header => $options ) {
336  if ( !isset( $headers[$header] ) ) {
337  $headers[$header] = [];
338  }
339  if ( is_array( $options ) ) {
340  $headers[$header] = array_unique( array_merge( $headers[$header], $options ) );
341  }
342  }
343  }
344  $this->varyHeaders = $headers;
345  }
346  return $this->varyHeaders;
347  }
348 
349  public function getVaryCookies() {
350  // @codeCoverageIgnoreStart
351  if ( defined( 'MW_NO_SESSION' ) && MW_NO_SESSION !== 'warn' ) {
352  return [];
353  }
354  // @codeCoverageIgnoreEnd
355  if ( $this->varyCookies === null ) {
356  $cookies = [];
357  foreach ( $this->getProviders() as $provider ) {
358  $cookies = array_merge( $cookies, $provider->getVaryCookies() );
359  }
360  $this->varyCookies = array_values( array_unique( $cookies ) );
361  }
362  return $this->varyCookies;
363  }
364 
370  public static function validateSessionId( $id ) {
371  return is_string( $id ) && preg_match( '/^[a-zA-Z0-9_-]{32,}$/', $id );
372  }
373 
387  public static function autoCreateUser( User $user ) {
388  wfDeprecated( __METHOD__, '1.27' );
389  return \MediaWiki\Auth\AuthManager::singleton()->autoCreateUser(
390  $user,
391  \MediaWiki\Auth\AuthManager::AUTOCREATE_SOURCE_SESSION,
392  false
393  )->isGood();
394  }
395 
405  public function preventSessionsForUser( $username ) {
406  $this->preventUsers[$username] = true;
407 
408  // Instruct the session providers to kill any other sessions too.
409  foreach ( $this->getProviders() as $provider ) {
410  $provider->preventSessionsForUser( $username );
411  }
412  }
413 
420  public function isUserSessionPrevented( $username ) {
421  return !empty( $this->preventUsers[$username] );
422  }
423 
428  protected function getProviders() {
429  if ( $this->sessionProviders === null ) {
430  $this->sessionProviders = [];
431  foreach ( $this->config->get( 'SessionProviders' ) as $spec ) {
432  $provider = \ObjectFactory::getObjectFromSpec( $spec );
433  $provider->setLogger( $this->logger );
434  $provider->setConfig( $this->config );
435  $provider->setManager( $this );
436  if ( isset( $this->sessionProviders[(string)$provider] ) ) {
437  throw new \UnexpectedValueException( "Duplicate provider name \"$provider\"" );
438  }
439  $this->sessionProviders[(string)$provider] = $provider;
440  }
441  }
443  }
444 
455  public function getProvider( $name ) {
456  $providers = $this->getProviders();
457  return isset( $providers[$name] ) ? $providers[$name] : null;
458  }
459 
464  public function shutdown() {
465  if ( $this->allSessionBackends ) {
466  $this->logger->debug( 'Saving all sessions on shutdown' );
467  if ( session_id() !== '' ) {
468  // @codeCoverageIgnoreStart
469  session_write_close();
470  }
471  // @codeCoverageIgnoreEnd
472  foreach ( $this->allSessionBackends as $backend ) {
473  $backend->shutdown();
474  }
475  }
476  }
477 
484  // Call all providers to fetch "the" session
485  $infos = [];
486  foreach ( $this->getProviders() as $provider ) {
487  $info = $provider->provideSessionInfo( $request );
488  if ( !$info ) {
489  continue;
490  }
491  if ( $info->getProvider() !== $provider ) {
492  throw new \UnexpectedValueException(
493  "$provider returned session info for a different provider: $info"
494  );
495  }
496  $infos[] = $info;
497  }
498 
499  // Sort the SessionInfos. Then find the first one that can be
500  // successfully loaded, and then all the ones after it with the same
501  // priority.
502  usort( $infos, 'MediaWiki\\Session\\SessionInfo::compare' );
503  $retInfos = [];
504  while ( $infos ) {
505  $info = array_pop( $infos );
506  if ( $this->loadSessionInfoFromStore( $info, $request ) ) {
507  $retInfos[] = $info;
508  while ( $infos ) {
509  $info = array_pop( $infos );
510  if ( SessionInfo::compare( $retInfos[0], $info ) ) {
511  // We hit a lower priority, stop checking.
512  break;
513  }
514  if ( $this->loadSessionInfoFromStore( $info, $request ) ) {
515  // This is going to error out below, but we want to
516  // provide a complete list.
517  $retInfos[] = $info;
518  } else {
519  // Session load failed, so unpersist it from this request
520  $info->getProvider()->unpersistSession( $request );
521  }
522  }
523  } else {
524  // Session load failed, so unpersist it from this request
525  $info->getProvider()->unpersistSession( $request );
526  }
527  }
528 
529  if ( count( $retInfos ) > 1 ) {
530  $ex = new \OverflowException(
531  'Multiple sessions for this request tied for top priority: ' . implode( ', ', $retInfos )
532  );
533  $ex->sessionInfos = $retInfos;
534  throw $ex;
535  }
536 
537  return $retInfos ? $retInfos[0] : null;
538  }
539 
548  $key = wfMemcKey( 'MWSession', $info->getId() );
549  $blob = $this->store->get( $key );
550 
551  // If we got data from the store and the SessionInfo says to force use,
552  // "fail" means to delete the data from the store and retry. Otherwise,
553  // "fail" is just return false.
554  if ( $info->forceUse() && $blob !== false ) {
555  $failHandler = function () use ( $key, &$info, $request ) {
556  $this->store->delete( $key );
557  return $this->loadSessionInfoFromStore( $info, $request );
558  };
559  } else {
560  $failHandler = function () {
561  return false;
562  };
563  }
564 
565  $newParams = [];
566 
567  if ( $blob !== false ) {
568  // Sanity check: blob must be an array, if it's saved at all
569  if ( !is_array( $blob ) ) {
570  $this->logger->warning( 'Session "{session}": Bad data', [
571  'session' => $info,
572  ] );
573  $this->store->delete( $key );
574  return $failHandler();
575  }
576 
577  // Sanity check: blob has data and metadata arrays
578  if ( !isset( $blob['data'] ) || !is_array( $blob['data'] ) ||
579  !isset( $blob['metadata'] ) || !is_array( $blob['metadata'] )
580  ) {
581  $this->logger->warning( 'Session "{session}": Bad data structure', [
582  'session' => $info,
583  ] );
584  $this->store->delete( $key );
585  return $failHandler();
586  }
587 
588  $data = $blob['data'];
589  $metadata = $blob['metadata'];
590 
591  // Sanity check: metadata must be an array and must contain certain
592  // keys, if it's saved at all
593  if ( !array_key_exists( 'userId', $metadata ) ||
594  !array_key_exists( 'userName', $metadata ) ||
595  !array_key_exists( 'userToken', $metadata ) ||
596  !array_key_exists( 'provider', $metadata )
597  ) {
598  $this->logger->warning( 'Session "{session}": Bad metadata', [
599  'session' => $info,
600  ] );
601  $this->store->delete( $key );
602  return $failHandler();
603  }
604 
605  // First, load the provider from metadata, or validate it against the metadata.
606  $provider = $info->getProvider();
607  if ( $provider === null ) {
608  $newParams['provider'] = $provider = $this->getProvider( $metadata['provider'] );
609  if ( !$provider ) {
610  $this->logger->warning(
611  'Session "{session}": Unknown provider ' . $metadata['provider'],
612  [
613  'session' => $info,
614  ]
615  );
616  $this->store->delete( $key );
617  return $failHandler();
618  }
619  } elseif ( $metadata['provider'] !== (string)$provider ) {
620  $this->logger->warning( 'Session "{session}": Wrong provider ' .
621  $metadata['provider'] . ' !== ' . $provider,
622  [
623  'session' => $info,
624  ] );
625  return $failHandler();
626  }
627 
628  // Load provider metadata from metadata, or validate it against the metadata
629  $providerMetadata = $info->getProviderMetadata();
630  if ( isset( $metadata['providerMetadata'] ) ) {
631  if ( $providerMetadata === null ) {
632  $newParams['metadata'] = $metadata['providerMetadata'];
633  } else {
634  try {
635  $newProviderMetadata = $provider->mergeMetadata(
636  $metadata['providerMetadata'], $providerMetadata
637  );
638  if ( $newProviderMetadata !== $providerMetadata ) {
639  $newParams['metadata'] = $newProviderMetadata;
640  }
641  } catch ( MetadataMergeException $ex ) {
642  $this->logger->warning(
643  'Session "{session}": Metadata merge failed: {exception}',
644  [
645  'session' => $info,
646  'exception' => $ex,
647  ] + $ex->getContext()
648  );
649  return $failHandler();
650  }
651  }
652  }
653 
654  // Next, load the user from metadata, or validate it against the metadata.
655  $userInfo = $info->getUserInfo();
656  if ( !$userInfo ) {
657  // For loading, id is preferred to name.
658  try {
659  if ( $metadata['userId'] ) {
660  $userInfo = UserInfo::newFromId( $metadata['userId'] );
661  } elseif ( $metadata['userName'] !== null ) { // Shouldn't happen, but just in case
662  $userInfo = UserInfo::newFromName( $metadata['userName'] );
663  } else {
664  $userInfo = UserInfo::newAnonymous();
665  }
666  } catch ( \InvalidArgumentException $ex ) {
667  $this->logger->error( 'Session "{session}": {exception}', [
668  'session' => $info,
669  'exception' => $ex,
670  ] );
671  return $failHandler();
672  }
673  $newParams['userInfo'] = $userInfo;
674  } else {
675  // User validation passes if user ID matches, or if there
676  // is no saved ID and the names match.
677  if ( $metadata['userId'] ) {
678  if ( $metadata['userId'] !== $userInfo->getId() ) {
679  $this->logger->warning(
680  'Session "{session}": User ID mismatch, {uid_a} !== {uid_b}',
681  [
682  'session' => $info,
683  'uid_a' => $metadata['userId'],
684  'uid_b' => $userInfo->getId(),
685  ] );
686  return $failHandler();
687  }
688 
689  // If the user was renamed, probably best to fail here.
690  if ( $metadata['userName'] !== null &&
691  $userInfo->getName() !== $metadata['userName']
692  ) {
693  $this->logger->warning(
694  'Session "{session}": User ID matched but name didn\'t (rename?), {uname_a} !== {uname_b}',
695  [
696  'session' => $info,
697  'uname_a' => $metadata['userName'],
698  'uname_b' => $userInfo->getName(),
699  ] );
700  return $failHandler();
701  }
702 
703  } elseif ( $metadata['userName'] !== null ) { // Shouldn't happen, but just in case
704  if ( $metadata['userName'] !== $userInfo->getName() ) {
705  $this->logger->warning(
706  'Session "{session}": User name mismatch, {uname_a} !== {uname_b}',
707  [
708  'session' => $info,
709  'uname_a' => $metadata['userName'],
710  'uname_b' => $userInfo->getName(),
711  ] );
712  return $failHandler();
713  }
714  } elseif ( !$userInfo->isAnon() ) {
715  // Metadata specifies an anonymous user, but the passed-in
716  // user isn't anonymous.
717  $this->logger->warning(
718  'Session "{session}": Metadata has an anonymous user, but a non-anon user was provided',
719  [
720  'session' => $info,
721  ] );
722  return $failHandler();
723  }
724  }
725 
726  // And if we have a token in the metadata, it must match the loaded/provided user.
727  if ( $metadata['userToken'] !== null &&
728  $userInfo->getToken() !== $metadata['userToken']
729  ) {
730  $this->logger->warning( 'Session "{session}": User token mismatch', [
731  'session' => $info,
732  ] );
733  return $failHandler();
734  }
735  if ( !$userInfo->isVerified() ) {
736  $newParams['userInfo'] = $userInfo->verified();
737  }
738 
739  if ( !empty( $metadata['remember'] ) && !$info->wasRemembered() ) {
740  $newParams['remembered'] = true;
741  }
742  if ( !empty( $metadata['forceHTTPS'] ) && !$info->forceHTTPS() ) {
743  $newParams['forceHTTPS'] = true;
744  }
745  if ( !empty( $metadata['persisted'] ) && !$info->wasPersisted() ) {
746  $newParams['persisted'] = true;
747  }
748 
749  if ( !$info->isIdSafe() ) {
750  $newParams['idIsSafe'] = true;
751  }
752  } else {
753  // No metadata, so we can't load the provider if one wasn't given.
754  if ( $info->getProvider() === null ) {
755  $this->logger->warning(
756  'Session "{session}": Null provider and no metadata',
757  [
758  'session' => $info,
759  ] );
760  return $failHandler();
761  }
762 
763  // If no user was provided and no metadata, it must be anon.
764  if ( !$info->getUserInfo() ) {
765  if ( $info->getProvider()->canChangeUser() ) {
766  $newParams['userInfo'] = UserInfo::newAnonymous();
767  } else {
768  $this->logger->info(
769  'Session "{session}": No user provided and provider cannot set user',
770  [
771  'session' => $info,
772  ] );
773  return $failHandler();
774  }
775  } elseif ( !$info->getUserInfo()->isVerified() ) {
776  // probably just a session timeout
777  $this->logger->info(
778  'Session "{session}": Unverified user provided and no metadata to auth it',
779  [
780  'session' => $info,
781  ] );
782  return $failHandler();
783  }
784 
785  $data = false;
786  $metadata = false;
787 
788  if ( !$info->getProvider()->persistsSessionId() && !$info->isIdSafe() ) {
789  // The ID doesn't come from the user, so it should be safe
790  // (and if not, nothing we can do about it anyway)
791  $newParams['idIsSafe'] = true;
792  }
793  }
794 
795  // Construct the replacement SessionInfo, if necessary
796  if ( $newParams ) {
797  $newParams['copyFrom'] = $info;
798  $info = new SessionInfo( $info->getPriority(), $newParams );
799  }
800 
801  // Allow the provider to check the loaded SessionInfo
802  $providerMetadata = $info->getProviderMetadata();
803  if ( !$info->getProvider()->refreshSessionInfo( $info, $request, $providerMetadata ) ) {
804  return $failHandler();
805  }
806  if ( $providerMetadata !== $info->getProviderMetadata() ) {
807  $info = new SessionInfo( $info->getPriority(), [
808  'metadata' => $providerMetadata,
809  'copyFrom' => $info,
810  ] );
811  }
812 
813  // Give hooks a chance to abort. Combined with the SessionMetadata
814  // hook, this can allow for tying a session to an IP address or the
815  // like.
816  $reason = 'Hook aborted';
817  if ( !\Hooks::run(
818  'SessionCheckInfo',
819  [ &$reason, $info, $request, $metadata, $data ]
820  ) ) {
821  $this->logger->warning( 'Session "{session}": ' . $reason, [
822  'session' => $info,
823  ] );
824  return $failHandler();
825  }
826 
827  return true;
828  }
829 
838  public function getSessionFromInfo( SessionInfo $info, WebRequest $request ) {
839  // @codeCoverageIgnoreStart
840  if ( defined( 'MW_NO_SESSION' ) ) {
841  if ( MW_NO_SESSION === 'warn' ) {
842  // Undocumented safety case for converting existing entry points
843  $this->logger->error( 'Sessions are supposed to be disabled for this entry point', [
844  'exception' => new \BadMethodCallException( 'Sessions are disabled for this entry point' ),
845  ] );
846  } else {
847  throw new \BadMethodCallException( 'Sessions are disabled for this entry point' );
848  }
849  }
850  // @codeCoverageIgnoreEnd
851 
852  $id = $info->getId();
853 
854  if ( !isset( $this->allSessionBackends[$id] ) ) {
855  if ( !isset( $this->allSessionIds[$id] ) ) {
856  $this->allSessionIds[$id] = new SessionId( $id );
857  }
858  $backend = new SessionBackend(
859  $this->allSessionIds[$id],
860  $info,
861  $this->store,
862  $this->logger,
863  $this->config->get( 'ObjectCacheSessionExpiry' )
864  );
865  $this->allSessionBackends[$id] = $backend;
866  $delay = $backend->delaySave();
867  } else {
868  $backend = $this->allSessionBackends[$id];
869  $delay = $backend->delaySave();
870  if ( $info->wasPersisted() ) {
871  $backend->persist();
872  }
873  if ( $info->wasRemembered() ) {
874  $backend->setRememberUser( true );
875  }
876  }
877 
878  $request->setSessionId( $backend->getSessionId() );
879  $session = $backend->getSession( $request );
880 
881  if ( !$info->isIdSafe() ) {
882  $session->resetId();
883  }
884 
885  \Wikimedia\ScopedCallback::consume( $delay );
886  return $session;
887  }
888 
894  public function deregisterSessionBackend( SessionBackend $backend ) {
895  $id = $backend->getId();
896  if ( !isset( $this->allSessionBackends[$id] ) || !isset( $this->allSessionIds[$id] ) ||
897  $this->allSessionBackends[$id] !== $backend ||
898  $this->allSessionIds[$id] !== $backend->getSessionId()
899  ) {
900  throw new \InvalidArgumentException( 'Backend was not registered with this SessionManager' );
901  }
902 
903  unset( $this->allSessionBackends[$id] );
904  // Explicitly do not unset $this->allSessionIds[$id]
905  }
906 
912  public function changeBackendId( SessionBackend $backend ) {
913  $sessionId = $backend->getSessionId();
914  $oldId = (string)$sessionId;
915  if ( !isset( $this->allSessionBackends[$oldId] ) || !isset( $this->allSessionIds[$oldId] ) ||
916  $this->allSessionBackends[$oldId] !== $backend ||
917  $this->allSessionIds[$oldId] !== $sessionId
918  ) {
919  throw new \InvalidArgumentException( 'Backend was not registered with this SessionManager' );
920  }
921 
922  $newId = $this->generateSessionId();
923 
924  unset( $this->allSessionBackends[$oldId], $this->allSessionIds[$oldId] );
925  $sessionId->setId( $newId );
926  $this->allSessionBackends[$newId] = $backend;
927  $this->allSessionIds[$newId] = $sessionId;
928  }
929 
934  public function generateSessionId() {
935  do {
936  $id = \Wikimedia\base_convert( \MWCryptRand::generateHex( 40 ), 16, 32, 32 );
937  $key = wfMemcKey( 'MWSession', $id );
938  } while ( isset( $this->allSessionIds[$id] ) || is_array( $this->store->get( $key ) ) );
939  return $id;
940  }
941 
948  $handler->setManager( $this, $this->store, $this->logger );
949  }
950 
955  public static function resetCache() {
956  if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
957  // @codeCoverageIgnoreStart
958  throw new MWException( __METHOD__ . ' may only be called from unit tests!' );
959  // @codeCoverageIgnoreEnd
960  }
961 
962  self::$globalSession = null;
963  self::$globalSessionRequest = null;
964  }
965 
968 }
MediaWiki\Session\SessionManager\isUserSessionPrevented
isUserSessionPrevented( $username)
Test if a user is prevented.
Definition: SessionManager.php:420
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:252
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:547
$request
error also a ContextSource you ll probably need to make sure the header is varied on $request
Definition: hooks.txt:2612
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:189
MediaWiki\Session\SessionManager\generateSessionId
generateSessionId()
Generate a new random session ID.
Definition: SessionManager.php:934
MediaWiki\Session\SessionManager\getVaryCookies
getVaryCookies()
Return the list of cookies that need varying on.
Definition: SessionManager.php:349
MediaWiki\Session\SessionManager\$globalSessionRequest
static WebRequest null $globalSessionRequest
Definition: SessionManager.php:57
MediaWiki\Session\SessionManager\getProviders
getProviders()
Get the available SessionProviders.
Definition: SessionManager.php:428
captcha-old.count
count
Definition: captcha-old.py:225
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:947
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:75
MediaWiki\Session\SessionManager\preventSessionsForUser
preventSessionsForUser( $username)
Prevent future sessions for the user.
Definition: SessionManager.php:405
MediaWiki\Session\SessionManager\$instance
static SessionManager null $instance
Definition: SessionManager.php:51
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\MetadataMergeException
Subclass of UnexpectedValueException that can be annotated with additional data for debug logging.
Definition: MetadataMergeException.php:36
$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:246
MediaWiki\Session\SessionManager\getSessionById
getSessionById( $id, $create=false, WebRequest $request=null)
Fetch a session by ID.
Definition: SessionManager.php:200
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:78
MediaWiki\Session\SessionInfo\forceUse
forceUse()
Force use of this SessionInfo if validation fails.
Definition: SessionInfo.php:209
BagOStuff
interface is intended to be more or less compatible with the PHP memcached client.
Definition: BagOStuff.php:47
MWCryptRand\generateHex
static generateHex( $chars, $forceStrong=false)
Generate a run of (ideally) cryptographically random data and return it in hexadecimal string format.
Definition: MWCryptRand.php:76
$name
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:304
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:60
Config
Interface for configuration instances.
Definition: Config.php:28
MWException
MediaWiki exception.
Definition: MWException.php:26
wfMemcKey
wfMemcKey()
Make a cache key for the local wiki.
Definition: GlobalFunctions.php:2961
MediaWiki\Session\SessionManager\$config
Config $config
Definition: SessionManager.php:63
wfDeprecated
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
Definition: GlobalFunctions.php:1128
MediaWiki\Session\SessionManager\deregisterSessionBackend
deregisterSessionBackend(SessionBackend $backend)
Deregister a SessionBackend.
Definition: SessionManager.php:894
MediaWiki\Session\SessionManager\validateSessionId
static validateSessionId( $id)
Validate a session ID.
Definition: SessionManager.php:370
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:63
MediaWiki\Session\SessionManager\invalidateSessionsForUser
invalidateSessionsForUser(User $user)
Invalidate sessions for a user.
Definition: SessionManager.php:312
MediaWiki\Session\SessionManager\$allSessionIds
SessionId[] $allSessionIds
Definition: SessionManager.php:81
MediaWiki
A helper class for throttling authentication attempts.
MediaWiki\Session\SessionManager\$globalSession
static Session null $globalSession
Definition: SessionManager.php:54
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:91
MediaWiki\Session\SessionManager\shutdown
shutdown()
Save all active sessions on shutdown.
Definition: SessionManager.php:464
MediaWiki\Session
Definition: BotPasswordSessionProvider.php:24
MediaWiki\Session\SessionInfo\wasPersisted
wasPersisted()
Return whether the session is persisted.
Definition: SessionInfo.php:233
MediaWiki\Session\SessionManager\resetCache
static resetCache()
Reset the internal caching for unit testing.
Definition: SessionManager.php:955
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:177
MediaWiki\Auth\AuthManager\callLegacyAuthPlugin
static callLegacyAuthPlugin( $method, array $params, $return=null)
Call a legacy AuthPlugin method, if necessary.
Definition: AuthManager.php:238
MediaWiki\Session\SessionManager\getVaryHeaders
getVaryHeaders()
Return the HTTP headers that need varying on.
Definition: SessionManager.php:326
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
MediaWiki\Session\SessionManager\$sessionProviders
SessionProvider[] $sessionProviders
Definition: SessionManager.php:69
MediaWiki\MediaWikiServices\getInstance
static getInstance()
Returns the global default instance of the top level service locator.
Definition: MediaWikiServices.php:97
MediaWiki\Session\SessionManager\getGlobalSession
static getGlobalSession()
Get the "global" session.
Definition: SessionManager.php:106
MediaWiki\Session\SessionManager\getSessionInfoForRequest
getSessionInfoForRequest(WebRequest $request)
Fetch the SessionInfo(s) for a request.
Definition: SessionManager.php:483
MediaWiki\Session\SessionBackend\getSessionId
getSessionId()
Fetch the SessionId object.
Definition: SessionBackend.php:214
MediaWiki\Session\SessionManager\setLogger
setLogger(LoggerInterface $logger)
Definition: SessionManager.php:185
MediaWiki\Session\SessionManager\getEmptySession
getEmptySession(WebRequest $request=null)
Create a new, empty session.
Definition: SessionManager.php:242
$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:49
CachedBagOStuff
Wrapper around a BagOStuff that caches data in memory.
Definition: CachedBagOStuff.php:36
MediaWiki\Session\SessionManager\$store
CachedBagOStuff null $store
Definition: SessionManager.php:66
MediaWiki\Session\SessionManager\$preventUsers
string[] $preventUsers
Definition: SessionManager.php:84
ObjectFactory\getObjectFromSpec
static getObjectFromSpec( $spec)
Instantiate an object based on a specification array.
Definition: ObjectFactory.php:59
$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:783
RequestContext\getMain
static getMain()
Static methods.
Definition: RequestContext.php:468
MediaWiki\Session\SessionManager\autoCreateUser
static autoCreateUser(User $user)
Auto-create the given user, if necessary.
Definition: SessionManager.php:387
WebRequest
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form stripping il...
Definition: WebRequest.php:38
MediaWiki\Session\SessionManager\getSessionFromInfo
getSessionFromInfo(SessionInfo $info, WebRequest $request)
Create a Session corresponding to the passed SessionInfo.
Definition: SessionManager.php:838
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
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:455
MediaWiki\Session\SessionManager\changeBackendId
changeBackendId(SessionBackend $backend)
Change a SessionBackend's ID.
Definition: SessionManager.php:912
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:50
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:131
$username
this hook is for auditing only or null if authentication failed before getting that far $username
Definition: hooks.txt:783
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:189
$options
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup 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:1049
MediaWiki\Session\SessionManager\$logger
LoggerInterface $logger
Definition: SessionManager.php:60
MediaWiki\Session\SessionManager\$varyCookies
string[] $varyCookies
Definition: SessionManager.php:72
MediaWiki\Session\SessionInfo\getUserInfo
getUserInfo()
Return the user.
Definition: SessionInfo.php:225
array
the array() calling protocol came about after MediaWiki 1.4rc1.
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:147
MediaWiki\Session\SessionBackend
This is the actual workhorse for Session.
Definition: SessionBackend.php:49