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