MediaWiki REL1_30
SessionManager.php
Go to the documentation of this file.
1<?php
24namespace MediaWiki\Session;
25
27use MWException;
28use Psr\Log\LoggerInterface;
29use BagOStuff;
31use Config;
32use FauxRequest;
33use User;
34use WebRequest;
35
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() {
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 }
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 = $this->store->makeKey( '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 = $this->store->makeKey( '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
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
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 = $this->store->makeKey( '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
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 = $this->store->makeKey( '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}
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
interface is intended to be more or less compatible with the PHP memcached client.
Definition BagOStuff.php:47
Wrapper around a BagOStuff that caches data in memory.
WebRequest clone which takes values from a provided array.
static generateHex( $chars, $forceStrong=false)
Generate a run of (ideally) cryptographically random data and return it in hexadecimal string format.
MediaWiki exception.
MediaWikiServices is the service locator for the application scope of MediaWiki.
static getInstance()
Returns the global default instance of the top level service locator.
Subclass of UnexpectedValueException that can be annotated with additional data for debug logging.
Adapter for PHP's session handling.
static isEnabled()
Test whether the handler is installed and enabled.
This is the actual workhorse for Session.
getSessionId()
Fetch the SessionId object.
getId()
Returns the session ID.
Value object holding the session ID in a manner that can be globally updated.
Definition SessionId.php:38
Value object returned by SessionProvider.
forceUse()
Force use of this SessionInfo if validation fails.
getProviderMetadata()
Return provider metadata.
getId()
Return the session ID.
getProvider()
Return the provider.
isIdSafe()
Indicate whether the ID is "safe".
getUserInfo()
Return the user.
wasPersisted()
Return whether the session is persisted.
const MIN_PRIORITY
Minimum allowed priority.
getPriority()
Return the priority.
wasRemembered()
Return whether the user was remembered.
forceHTTPS()
Whether this session should only be used over HTTPS.
static compare( $a, $b)
Compare two SessionInfo objects by priority.
This serves as the entry point to the MediaWiki session handling system.
static resetCache()
Reset the internal caching for unit testing.
getVaryHeaders()
Return the HTTP headers that need varying on.
deregisterSessionBackend(SessionBackend $backend)
Deregister a SessionBackend.
invalidateSessionsForUser(User $user)
Invalidate sessions for a user.
static WebRequest null $globalSessionRequest
loadSessionInfoFromStore(SessionInfo &$info, WebRequest $request)
Load and verify the session info against the store.
getVaryCookies()
Return the list of cookies that need varying on.
setupPHPSessionHandler(PHPSessionHandler $handler)
Call setters on a PHPSessionHandler.
getEmptySessionInternal(WebRequest $request=null, $id=null)
static SessionManager null $instance
getEmptySession(WebRequest $request=null)
Create a new, empty session.
static getGlobalSession()
Get the "global" session.
getSessionInfoForRequest(WebRequest $request)
Fetch the SessionInfo(s) for a request.
preventSessionsForUser( $username)
Prevent future sessions for the user.
shutdown()
Save all active sessions on shutdown.
getSessionById( $id, $create=false, WebRequest $request=null)
Fetch a session by ID.
getProvider( $name)
Get a session provider by name.
static autoCreateUser(User $user)
Auto-create the given user, if necessary.
static validateSessionId( $id)
Validate a session ID.
getProviders()
Get the available SessionProviders.
static singleton()
Get the global SessionManager.
changeBackendId(SessionBackend $backend)
Change a SessionBackend's ID.
generateSessionId()
Generate a new random session ID.
setLogger(LoggerInterface $logger)
getSessionFromInfo(SessionInfo $info, WebRequest $request)
Create a Session corresponding to the passed SessionInfo.
getSessionForRequest(WebRequest $request)
Fetch the session for a request (or a new empty session if none is attached to it)
isUserSessionPrevented( $username)
Test if a user is prevented.
A SessionProvider provides SessionInfo and support for Session.
Manages data for an an authenticated session.
Definition Session.php:48
static newFromName( $name, $verified=false)
Create an instance for a logged-in user by name.
Definition UserInfo.php:102
static newAnonymous()
Create an instance for an anonymous (i.e.
Definition UserInfo.php:74
static newFromId( $id, $verified=false)
Create an instance for a logged-in user by ID.
Definition UserInfo.php:84
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition User.php:51
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form stripping il...
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:2775
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:181
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:1971
this hook is for auditing only or null if authentication failed before getting that far $username
Definition hooks.txt:783
Allows to change the fields on the form that will be generated $name
Definition hooks.txt:302
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:901
Interface for configuration instances.
Definition Config.php:28
This exists to make IDEs happy, so they don't see the internal-but-required-to-be-public methods on S...
const MW_NO_SESSION
Definition load.php:30
A helper class for throttling authentication attempts.
$header