Merge "docs: Fix typo"
[mediawiki.git] / includes / session / SessionProvider.php
blob13764d1bf739eb3d8e6e1f383142dd88576d15f5
1 <?php
2 /**
3 * MediaWiki session provider base class
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
20 * @file
21 * @ingroup Session
24 namespace MediaWiki\Session;
26 use ErrorPageError;
27 use InvalidArgumentException;
28 use MediaWiki\Api\ApiUsageException;
29 use MediaWiki\Config\Config;
30 use MediaWiki\Context\RequestContext;
31 use MediaWiki\HookContainer\HookContainer;
32 use MediaWiki\HookContainer\HookRunner;
33 use MediaWiki\Language\Language;
34 use MediaWiki\MainConfigNames;
35 use MediaWiki\Message\Message;
36 use MediaWiki\Request\WebRequest;
37 use MediaWiki\Rest\Handler;
38 use MediaWiki\Rest\HttpException;
39 use MediaWiki\Rest\LocalizedHttpException;
40 use MediaWiki\Rest\Module\Module;
41 use MediaWiki\Rest\RequestInterface;
42 use MediaWiki\User\User;
43 use MediaWiki\User\UserNameUtils;
44 use MWRestrictions;
45 use Psr\Log\LoggerInterface;
46 use Stringable;
47 use Wikimedia\Message\MessageParam;
48 use Wikimedia\Message\MessageSpecifier;
49 use Wikimedia\Message\MessageValue;
51 /**
52 * A SessionProvider provides SessionInfo and support for Session
54 * A SessionProvider is responsible for taking a WebRequest and determining
55 * the authenticated session that it's a part of. It does this by returning an
56 * SessionInfo object with basic information about the session it thinks is
57 * associated with the request, namely the session ID and possibly the
58 * authenticated user the session belongs to.
60 * The SessionProvider also provides for updating the WebResponse with
61 * information necessary to provide the client with data that the client will
62 * send with later requests, and for populating the Vary and Key headers with
63 * the data necessary to correctly vary the cache on these client requests.
65 * An important part of the latter is indicating whether it even *can* tell the
66 * client to include such data in future requests, via the persistsSessionId()
67 * and canChangeUser() methods. The cases are (in order of decreasing
68 * commonness):
69 * - Cannot persist ID, no changing User: The request identifies and
70 * authenticates a particular local user, and the client cannot be
71 * instructed to include an arbitrary session ID with future requests. For
72 * example, OAuth or SSL certificate auth.
73 * - Can persist ID and can change User: The client can be instructed to
74 * return at least one piece of arbitrary data, that being the session ID.
75 * The user identity might also be given to the client, otherwise it's saved
76 * in the session data. For example, cookie-based sessions.
77 * - Can persist ID but no changing User: The request uniquely identifies and
78 * authenticates a local user, and the client can be instructed to return an
79 * arbitrary session ID with future requests. For example, HTTP Digest
80 * authentication might somehow use the 'opaque' field as a session ID
81 * (although getting MediaWiki to return 401 responses without breaking
82 * other stuff might be a challenge).
83 * - Cannot persist ID but can change User: I can't think of a way this
84 * would make sense.
86 * Note that many methods that are technically "cannot persist ID" could be
87 * turned into "can persist ID but not change User" using a session cookie,
88 * as implemented by ImmutableSessionProviderWithCookie. If doing so, different
89 * session cookie names should be used for different providers to avoid
90 * collisions.
92 * @stable to extend
93 * @ingroup Session
94 * @since 1.27
95 * @see https://www.mediawiki.org/wiki/Manual:SessionManager_and_AuthManager
97 abstract class SessionProvider implements Stringable, SessionProviderInterface {
99 protected LoggerInterface $logger;
100 protected Config $config;
101 protected SessionManager $manager;
102 private HookContainer $hookContainer;
103 private HookRunner $hookRunner;
104 protected UserNameUtils $userNameUtils;
106 /** @var int Session priority. Used for the default newSessionInfo(), but
107 * could be used by subclasses too.
109 protected $priority;
112 * @stable to call
114 public function __construct() {
115 $this->priority = SessionInfo::MIN_PRIORITY + 10;
119 * Initialise with dependencies of a SessionProvider
121 * @since 1.37
122 * @internal In production code SessionManager will initialize the
123 * SessionProvider, in tests SessionProviderTestTrait must be used.
125 public function init(
126 LoggerInterface $logger,
127 Config $config,
128 SessionManager $manager,
129 HookContainer $hookContainer,
130 UserNameUtils $userNameUtils
132 $this->logger = $logger;
133 $this->config = $config;
134 $this->manager = $manager;
135 $this->hookContainer = $hookContainer;
136 $this->hookRunner = new HookRunner( $hookContainer );
137 $this->userNameUtils = $userNameUtils;
138 $this->postInitSetup();
142 * A provider can override this to do any necessary setup after init()
143 * is called.
145 * @since 1.37
146 * @stable to override
148 protected function postInitSetup() {
152 * Sets a logger instance on the object.
154 * @deprecated since 1.37. For extension-defined session providers
155 * that were using this method to trigger other work, please override
156 * SessionProvider::postInitSetup instead. If your extension
157 * was using this to explicitly change the logger of an existing
158 * SessionProvider object, please file a report on phabricator
159 * - there is no non-deprecated way to do this anymore.
160 * @param LoggerInterface $logger
162 public function setLogger( LoggerInterface $logger ) {
163 wfDeprecated( __METHOD__, '1.37' );
164 $this->logger = $logger;
168 * Set configuration
170 * @deprecated since 1.37. For extension-defined session providers
171 * that were using this method to trigger other work, please override
172 * SessionProvider::postInitSetup instead. If your extension
173 * was using this to explicitly change the Config of an existing
174 * SessionProvider object, please file a report on phabricator
175 * - there is no non-deprecated way to do this anymore.
176 * @param Config $config
178 public function setConfig( Config $config ) {
179 wfDeprecated( __METHOD__, '1.37' );
180 $this->config = $config;
184 * Get the config
186 * @since 1.37
187 * @return Config
189 protected function getConfig() {
190 return $this->config;
194 * Set the session manager
196 * @deprecated since 1.37. For extension-defined session providers
197 * that were using this method to trigger other work, please override
198 * SessionProvider::postInitSetup instead. If your extension
199 * was using this to explicitly change the SessionManager of an existing
200 * SessionProvider object, please file a report on phabricator
201 * - there is no non-deprecated way to do this anymore.
202 * @param SessionManager $manager
204 public function setManager( SessionManager $manager ) {
205 wfDeprecated( __METHOD__, '1.37' );
206 $this->manager = $manager;
210 * Get the session manager
211 * @return SessionManager
213 public function getManager() {
214 return $this->manager;
218 * @internal
219 * @deprecated since 1.37. For extension-defined session providers
220 * that were using this method to trigger other work, please override
221 * SessionProvider::postInitSetup instead. If your extension
222 * was using this to explicitly change the HookContainer of an existing
223 * SessionProvider object, please file a report on phabricator
224 * - there is no non-deprecated way to do this anymore.
225 * @param HookContainer $hookContainer
227 public function setHookContainer( $hookContainer ) {
228 wfDeprecated( __METHOD__, '1.37' );
229 $this->hookContainer = $hookContainer;
230 $this->hookRunner = new HookRunner( $hookContainer );
233 protected function getHookContainer(): HookContainer {
234 return $this->hookContainer;
238 * Get the HookRunner
240 * @internal This is for use by core only. Hook interfaces may be removed
241 * without notice.
242 * @since 1.35
243 * @return HookRunner
245 protected function getHookRunner(): HookRunner {
246 return $this->hookRunner;
250 * Provide session info for a request
252 * If no session exists for the request, return null. Otherwise return an
253 * SessionInfo object identifying the session.
255 * If multiple SessionProviders provide sessions, the one with highest
256 * priority wins. In case of a tie, an exception is thrown.
257 * SessionProviders are encouraged to make priorities user-configurable
258 * unless only max-priority makes sense.
260 * @warning This will be called early in the MediaWiki setup process,
261 * before $wgUser, $wgLang, $wgOut, $wgTitle, the global parser, and
262 * corresponding pieces of the main RequestContext are set up! If you try
263 * to use these, things *will* break.
264 * @note The SessionProvider must not attempt to auto-create users.
265 * MediaWiki will do this later (when it's safe) if the chosen session has
266 * a user with a valid name but no ID.
267 * @note For use by \MediaWiki\Session\SessionManager only
268 * @param WebRequest $request
269 * @return SessionInfo|null
271 abstract public function provideSessionInfo( WebRequest $request );
274 * Provide session info for a new, empty session
276 * Return null if such a session cannot be created. This base
277 * implementation assumes that it only makes sense if a session ID can be
278 * persisted and changing users is allowed.
279 * @stable to override
281 * @note For use by \MediaWiki\Session\SessionManager only
282 * @param string|null $id ID to force for the new session
283 * @return SessionInfo|null
284 * If non-null, must return true for $info->isIdSafe(); pass true for
285 * $data['idIsSafe'] to ensure this.
287 public function newSessionInfo( $id = null ) {
288 if ( $this->canChangeUser() && $this->persistsSessionId() ) {
289 return new SessionInfo( $this->priority, [
290 'id' => $id,
291 'provider' => $this,
292 'persisted' => false,
293 'idIsSafe' => true,
294 ] );
296 return null;
300 * Merge saved session provider metadata
302 * This method will be used to compare the metadata returned by
303 * provideSessionInfo() with the saved metadata (which has been returned by
304 * provideSessionInfo() the last time the session was saved), and merge the two
305 * into the new saved metadata, or abort if the current request is not a valid
306 * continuation of the session.
308 * The default implementation checks that anything in both arrays is
309 * identical, then returns $providedMetadata.
310 * @stable to override
312 * @note For use by \MediaWiki\Session\SessionManager only
313 * @param array $savedMetadata Saved provider metadata
314 * @param array $providedMetadata Provided provider metadata (from the SessionInfo)
315 * @return array Resulting metadata
316 * @throws MetadataMergeException If the metadata cannot be merged.
317 * Such exceptions will be handled by SessionManager and are a safe way of rejecting
318 * a suspicious or incompatible session. The provider is expected to write an
319 * appropriate message to its logger.
321 public function mergeMetadata( array $savedMetadata, array $providedMetadata ) {
322 foreach ( $providedMetadata as $k => $v ) {
323 if ( array_key_exists( $k, $savedMetadata ) && $savedMetadata[$k] !== $v ) {
324 $e = new MetadataMergeException( "Key \"$k\" changed" );
325 $e->setContext( [
326 'old_value' => $savedMetadata[$k],
327 'new_value' => $v,
328 ] );
329 throw $e;
332 return $providedMetadata;
336 * Validate a loaded SessionInfo and refresh provider metadata
338 * This is similar in purpose to the 'SessionCheckInfo' hook, and also
339 * allows for updating the provider metadata. On failure, the provider is
340 * expected to write an appropriate message to its logger.
341 * @stable to override
343 * @note For use by \MediaWiki\Session\SessionManager only
344 * @param SessionInfo $info Any changes by mergeMetadata() will already be reflected here.
345 * @param WebRequest $request
346 * @param array|null &$metadata Provider metadata, may be altered.
347 * @return bool Return false to reject the SessionInfo after all.
349 public function refreshSessionInfo( SessionInfo $info, WebRequest $request, &$metadata ) {
350 return true;
354 * Indicate whether self::persistSession() can save arbitrary session IDs
356 * If false, any session passed to self::persistSession() will have an ID
357 * that was originally provided by self::provideSessionInfo().
359 * If true, the provider may be passed sessions with arbitrary session IDs,
360 * and will be expected to manipulate the request in such a way that future
361 * requests will cause self::provideSessionInfo() to provide a SessionInfo
362 * with that ID.
364 * For example, a session provider for OAuth would function by matching the
365 * OAuth headers to a particular user, and then would use self::hashToSessionId()
366 * to turn the user and OAuth client ID (and maybe also the user token and
367 * client secret) into a session ID, and therefore can't easily assign that
368 * user+client a different ID. Similarly, a session provider for SSL client
369 * certificates would function by matching the certificate to a particular
370 * user, and then would use self::hashToSessionId() to turn the user and
371 * certificate fingerprint into a session ID, and therefore can't easily
372 * assign a different ID either. On the other hand, a provider that saves
373 * the session ID into a cookie can easily just set the cookie to a
374 * different value.
376 * @note For use by \MediaWiki\Session\SessionBackend only
377 * @return bool
379 abstract public function persistsSessionId();
382 * Indicate whether the user associated with the request can be changed
384 * If false, any session passed to self::persistSession() will have a user
385 * that was originally provided by self::provideSessionInfo(). Further,
386 * self::provideSessionInfo() may only provide sessions that have a user
387 * already set.
389 * If true, the provider may be passed sessions with arbitrary users, and
390 * will be expected to manipulate the request in such a way that future
391 * requests will cause self::provideSessionInfo() to provide a SessionInfo
392 * with that ID. This can be as simple as not passing any 'userInfo' into
393 * SessionInfo's constructor, in which case SessionInfo will load the user
394 * from the saved session's metadata.
396 * For example, a session provider for OAuth or SSL client certificates
397 * would function by matching the OAuth headers or certificate to a
398 * particular user, and thus would return false here since it can't
399 * arbitrarily assign those OAuth credentials or that certificate to a
400 * different user. A session provider that shoves information into cookies,
401 * on the other hand, could easily do so.
403 * @note For use by \MediaWiki\Session\SessionBackend only
404 * @return bool
406 abstract public function canChangeUser();
409 * Returns the duration (in seconds) for which users will be remembered when
410 * Session::setRememberUser() is set. Null means setting the remember flag will
411 * have no effect (and endpoints should not offer that option).
412 * @stable to override
413 * @return int|null
415 public function getRememberUserDuration() {
416 return null;
420 * Notification that the session ID was reset
422 * No need to persist here, persistSession() will be called if appropriate.
423 * @stable to override
425 * @note For use by \MediaWiki\Session\SessionBackend only
426 * @param SessionBackend $session Session to persist
427 * @param string $oldId Old session ID
428 * @codeCoverageIgnore
430 public function sessionIdWasReset( SessionBackend $session, $oldId ) {
434 * Persist a session into a request/response
436 * For example, you might set cookies for the session's ID, user ID, user
437 * name, and user token on the passed request.
439 * To correctly persist a user independently of the session ID, the
440 * provider should persist both the user ID (or name, but preferably the
441 * ID) and the user token. When reading the data from the request, it
442 * should construct a User object from the ID/name and then verify that the
443 * User object's token matches the token included in the request. Should
444 * the tokens not match, an anonymous user *must* be passed to
445 * SessionInfo::__construct().
447 * When persisting a user independently of the session ID,
448 * $session->shouldRememberUser() should be checked first. If this returns
449 * false, the user token *must not* be saved to cookies. The user name
450 * and/or ID may be persisted, and should be used to construct an
451 * unverified UserInfo to pass to SessionInfo::__construct().
453 * A backend that cannot persist session ID or user info should implement
454 * this as a no-op.
456 * @note For use by \MediaWiki\Session\SessionBackend only
457 * @param SessionBackend $session Session to persist
458 * @param WebRequest $request Request into which to persist the session
460 abstract public function persistSession( SessionBackend $session, WebRequest $request );
463 * Remove any persisted session from a request/response
465 * For example, blank and expire any cookies set by self::persistSession().
467 * A backend that cannot persist session ID or user info should implement
468 * this as a no-op.
470 * @note For use by \MediaWiki\Session\SessionManager only
471 * @param WebRequest $request Request from which to remove any session data
473 abstract public function unpersistSession( WebRequest $request );
476 * Prevent future sessions for the user
478 * If the provider is capable of returning a SessionInfo with a verified
479 * UserInfo for the named user in some manner other than by validating
480 * against $user->getToken(), steps must be taken to prevent that from
481 * occurring in the future. This might add the username to a list, or
482 * it might just delete whatever authentication credentials would allow
483 * such a session in the first place (e.g. remove all OAuth grants or
484 * delete record of the SSL client certificate).
486 * The intention is that the named account will never again be usable for
487 * normal login (i.e. there is no way to undo the prevention of access).
489 * Note that the passed user name might not exist locally (i.e.
490 * UserIdentity::getId() === 0); the name should still be
491 * prevented, if applicable.
493 * @stable to override
494 * @note For use by \MediaWiki\Session\SessionManager only
495 * @param string $username
497 public function preventSessionsForUser( $username ) {
498 if ( !$this->canChangeUser() ) {
499 throw new \BadMethodCallException(
500 __METHOD__ . ' must be implemented when canChangeUser() is false'
506 * Invalidate existing sessions for a user
508 * If the provider has its own equivalent of CookieSessionProvider's Token
509 * cookie (and doesn't use User::getToken() to implement it), it should
510 * reset whatever token it does use here.
512 * @stable to override
513 * @note For use by \MediaWiki\Session\SessionManager only
514 * @param User $user
516 public function invalidateSessionsForUser( User $user ) {
520 * Return the HTTP headers that need varying on.
522 * The return value is such that someone could theoretically do this:
523 * @code
524 * foreach ( $provider->getVaryHeaders() as $header => $_ ) {
525 * $outputPage->addVaryHeader( $header );
527 * @endcode
529 * @stable to override
530 * @note For use by \MediaWiki\Session\SessionManager only
531 * @return array<string,null>
533 public function getVaryHeaders() {
534 return [];
538 * Return the list of cookies that need varying on.
539 * @stable to override
540 * @note For use by \MediaWiki\Session\SessionManager only
541 * @return string[]
543 public function getVaryCookies() {
544 return [];
548 * Get a suggested username for the login form
549 * @stable to override
550 * @note For use by \MediaWiki\Session\SessionBackend only
551 * @param WebRequest $request
552 * @return string|null
554 public function suggestLoginUsername( WebRequest $request ) {
555 return null;
559 * Fetch the rights allowed the user when the specified session is active.
561 * This is mainly meant for allowing the user to restrict access to the account
562 * by certain methods; you probably want to use this with GrantsInfo. The returned
563 * rights will be intersected with the user's actual rights.
565 * @stable to override
566 * @param SessionBackend $backend
567 * @return null|string[] Allowed user rights, or null to allow all.
569 public function getAllowedUserRights( SessionBackend $backend ) {
570 if ( $backend->getProvider() !== $this ) {
571 // Not that this should ever happen...
572 throw new InvalidArgumentException( 'Backend\'s provider isn\'t $this' );
575 return null;
579 * Fetch any restrictions imposed on logins or actions when this
580 * session is active.
582 * @since 1.42
583 * @stable to override
584 * @return MWRestrictions|null
586 public function getRestrictions( ?array $providerMetadata ): ?MWRestrictions {
587 return null;
591 * @note Only override this if it makes sense to instantiate multiple
592 * instances of the provider. Value returned must be unique across
593 * configured providers. If you override this, you'll likely need to
594 * override self::describeMessage() as well.
595 * @return string
597 public function __toString() {
598 return static::class;
602 * Return a Message identifying this session type
604 * This default implementation takes the class name, lowercases it,
605 * replaces backslashes with dashes, and prefixes 'sessionprovider-' to
606 * determine the message key. For example, MediaWiki\Session\CookieSessionProvider
607 * produces 'sessionprovider-mediawiki-session-cookiesessionprovider'.
609 * @stable to override
610 * @note If self::__toString() is overridden, this will likely need to be
611 * overridden as well.
612 * @warning This will be called early during MediaWiki startup. Do not
613 * use $wgUser, $wgLang, $wgOut, the global Parser, or their equivalents via
614 * RequestContext from this method!
615 * @return Message
617 protected function describeMessage() {
618 return wfMessage(
619 'sessionprovider-' . str_replace( '\\', '-', strtolower( static::class ) )
624 * @inheritDoc
625 * @stable to override
627 public function describe( Language $lang ) {
628 $msg = $this->describeMessage();
629 $msg->inLanguage( $lang );
630 if ( $msg->isDisabled() ) {
631 $msg = wfMessage( 'sessionprovider-generic', (string)$this )->inLanguage( $lang );
633 return $msg->plain();
637 * @inheritDoc
638 * @stable to override
640 public function whyNoSession() {
641 return null;
645 * Most session providers require protection against CSRF attacks (usually via CSRF tokens)
647 * @stable to override
648 * @return bool false
650 public function safeAgainstCsrf() {
651 return false;
655 * Returns true if this provider is exempt from autocreate user permissions check
657 * By default returns false, meaning this provider respects the normal rights
658 * of anonymous user creation. When true the permission checks will be bypassed
659 * and the user will always be created (subject to other limitations, like read
660 * only db and such).
662 * @stable to override
663 * @since 1.42
665 public function canAlwaysAutocreate(): bool {
666 return false;
670 * Hash data as a session ID
672 * Generally this will only be used when self::persistsSessionId() is false and
673 * the provider has to base the session ID on the verified user's identity
674 * or other static data. The SessionInfo should then typically have the
675 * 'forceUse' flag set to avoid persistent session failure if validation of
676 * the stored data fails.
678 * @param string $data
679 * @param string|null $key Defaults to $this->getConfig()->get( MainConfigNames::SecretKey )
680 * @return string
682 final protected function hashToSessionId( $data, $key = null ) {
683 if ( !is_string( $data ) ) {
684 throw new InvalidArgumentException(
685 '$data must be a string, ' . get_debug_type( $data ) . ' was passed'
688 if ( $key !== null && !is_string( $key ) ) {
689 throw new InvalidArgumentException(
690 '$key must be a string or null, ' . get_debug_type( $key ) . ' was passed'
694 $hash = \MWCryptHash::hmac( "$this\n$data",
695 $key ?: $this->getConfig()->get( MainConfigNames::SecretKey ), false );
696 if ( strlen( $hash ) < 32 ) {
697 // Should never happen, even md5 is 128 bits
698 // @codeCoverageIgnoreStart
699 throw new \UnexpectedValueException( 'Hash function returned less than 128 bits' );
700 // @codeCoverageIgnoreEnd
702 if ( strlen( $hash ) >= 40 ) {
703 $hash = \Wikimedia\base_convert( $hash, 16, 32, 32 );
705 return substr( $hash, -32 );
709 * Throw an exception, later. Needed because during session initialization the framework
710 * isn't quite ready to handle an exception.
712 * This should be called from provideSessionInfo() to fail in
713 * a user-friendly way when a session mechanism is used in a way it's not supposed to be used
714 * (e.g. invalid credentials or a non-API request when the session provider only supports
715 * API requests), and the returned SessionInfo should be returned by provideSessionInfo().
717 * @param string $key Key for the error message
718 * @phpcs:ignore Generic.Files.LineLength
719 * @param MessageParam|MessageSpecifier|string|int|float|list<MessageParam|MessageSpecifier|string|int|float> ...$params
720 * See Message::params()
721 * @return SessionInfo An anonymous session info with maximum priority, to force an
722 * anonymous session in case throwing the exception doesn't happen.
724 protected function makeException( $key, ...$params ): SessionInfo {
725 $msg = wfMessage( $key, $params );
727 if ( defined( 'MW_API' ) ) {
728 $this->hookContainer->register(
729 'ApiBeforeMain',
730 // @phan-suppress-next-line PhanPluginNeverReturnFunction Closures should not get doc
731 static function () use ( $msg ) {
732 throw ApiUsageException::newWithMessage( null, $msg );
735 } elseif ( defined( 'MW_REST_API' ) ) {
736 $this->hookContainer->register(
737 'RestCheckCanExecute',
738 static function ( Module $module, Handler $handler, string $path,
739 RequestInterface $request, ?HttpException &$error ) use ( $key, $params )
741 $msg = new MessageValue( $key, $params );
742 $error = new LocalizedHttpException( $msg, 403 );
743 return false;
746 } else {
747 $this->hookContainer->register(
748 'BeforeInitialize',
749 // @phan-suppress-next-line PhanPluginNeverReturnFunction Closures should not get doc
750 static function () use ( $msg ) {
751 RequestContext::getMain()->getOutput()->setStatusCode( 400 );
752 throw new ErrorPageError( 'errorpagetitle', $msg );
755 // Disable file cache, which would be looked up before the BeforeInitialize hook call.
756 $this->hookContainer->register(
757 'HTMLFileCache__useFileCache',
758 static function () {
759 return false;
764 $id = $this->hashToSessionId( 'bogus' );
765 return new SessionInfo( SessionInfo::MAX_PRIORITY, [
766 'provider' => $this,
767 'id' => $id,
768 'userInfo' => UserInfo::newAnonymous(),
769 'persisted' => false,
770 ] );