MediaWiki REL1_29
SessionManager.php
Go to the documentation of this file.
1<?php
24namespace MediaWiki\Session;
25
28use Psr\Log\LoggerInterface;
33use User;
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 = 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
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 = 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
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}
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
wfMemcKey()
Make a cache key for the local wiki.
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.
getRequest()
Get the WebRequest object.
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:50
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form stripping il...
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
the array() calling protocol came about after MediaWiki 1.4rc1.
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:249
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:183
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:1102
error also a ContextSource you ll probably need to make sure the header is varied on $request
Definition hooks.txt:2723
this hook is for auditing only or null if authentication failed before getting that far $username
Definition hooks.txt:785
Allows to change the fields on the form that will be generated $name
Definition hooks.txt:304
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:903
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:37
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.
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:4
$header