48 private const STATS_LABEL_ANON =
'anonymous';
49 private const STATS_LABEL_AUTH =
'authenticated';
52 private BagOStuff $authenticatedSessionStore;
54 private bool $sameBackend;
61 private LoggerInterface $logger;
66 LoggerInterface $logger,
71 $this->logger->debug(
'SessionManager using anon store ' . get_class( $anonSessionStore ) );
72 $this->logger->debug(
'SessionManager using auth store ' . get_class( $authenticatedSessionStore ) );
74 $this->sameBackend = $anonSessionStore === $authenticatedSessionStore;
75 $this->anonSessionStore = $this->wrapWithCachedBagOStuff( $anonSessionStore );
76 $this->authenticatedSessionStore = $this->wrapWithCachedBagOStuff( $authenticatedSessionStore );
77 $this->statsFactory = $statsFactory;
81 public function setLogger( LoggerInterface $logger ): void {
82 $this->logger = $logger;
90 return new CachedBagOStuff( $store );
101 private function getActiveStore( SessionInfo $sessionInfo ): array {
102 $userInfo = $sessionInfo->getUserInfo();
105 if ( $userInfo ===
null ) {
106 $this->logger->debug(
'No user info found for this session', [
107 'sessionInfo' => (
string)$sessionInfo,
108 'exception' =>
new RuntimeException(),
113 $anonKey = $this->anonSessionStore->makeKey(
'MWSession', $sessionInfo->getId() );
114 $authKey = $this->authenticatedSessionStore->makeKey(
'MWSession', $sessionInfo->getId() );
116 $authData = $this->authenticatedSessionStore->get( $authKey );
117 if ( !$this->authenticatedSessionStore->wasLastGetCached() ) {
118 $this->statsFactory->getCounter(
'sessionstore_nouserinfo_get_total' )
119 ->setLabel(
'type',
'authenticated' )
120 ->setLabel(
'status', $authData ?
'hit' :
'miss' )
124 $anonData = $this->anonSessionStore->get( $anonKey );
125 if ( !$this->anonSessionStore->wasLastGetCached() ) {
126 $this->statsFactory->getCounter(
'sessionstore_nouserinfo_get_total' )
127 ->setLabel(
'type',
'anonymous' )
128 ->setLabel(
'status', $anonData ?
'hit' :
'miss' )
133 $anonUserName = $anonData[
'metadata'][
'userName'] ??
null;
134 $authUserName = $authData[
'metadata'][
'userName'] ??
null;
136 if ( $anonData && $anonUserName !==
null ) {
140 if ( !$this->sameBackend ) {
141 $this->logger->warning(
'No userInfo: authenticated data should not be in the anonymous store', [
142 'sessionInfo' => (
string)$sessionInfo,
143 'exception' =>
new RuntimeException(),
148 $anonUserName =
null;
151 if ( $authData && $authUserName ===
null ) {
152 if ( !$this->sameBackend ) {
153 $this->logger->warning(
'No userInfo: anonymous data should not be in the authenticated store', [
154 'sessionInfo' => (
string)$sessionInfo,
155 'exception' =>
new RuntimeException(),
161 if ( $anonData && $authData && !$this->sameBackend ) {
162 $this->logger->warning(
'Both stores should not have the same session data', [
163 'sessionInfo' => (
string)$sessionInfo,
164 'exception' =>
new RuntimeException(),
169 return [ $this->authenticatedSessionStore, true ];
172 return [ $this->anonSessionStore, false ];
175 if ( !$userInfo->isAnon() ) {
178 return [ $this->authenticatedSessionStore, true ];
181 return [ $this->anonSessionStore, false ];
193 $this->logger->debug( __METHOD__ .
" was called for $info" );
195 [ $store, $isAuthenticated ] = $this->getActiveStore( $info );
197 $key = $store->makeKey(
'MWSession', $info->getId() );
198 $data = $store->get( $key );
199 $userName = $data[
'metadata'][
'userName'] ??
null;
201 if ( $isAuthenticated && $data && $userName ===
null ) {
202 $this->logger->warning(
'Store is authenticated, but the user associated to the data is anonymous', [
203 'sessionInfo' => (
string)$info,
204 'user' => (
string)$info->getUserInfo(),
205 'exception' =>
new RuntimeException(),
209 if ( !$isAuthenticated && $userName !==
null ) {
210 $this->logger->warning(
'Store is anonymous, but the user associated to the data is authenticated', [
211 'sessionInfo' => (
string)$info,
212 'user' => (
string)$info->getUserInfo(),
213 'exception' =>
new RuntimeException(),
219 if ( !$store->wasLastGetCached() ) {
220 $this->statsFactory->getCounter(
'sessionstore_get_total' )
221 ->setLabel(
'type', $isAuthenticated ? self::STATS_LABEL_AUTH : self::STATS_LABEL_ANON )
237 public function set(
SessionInfo $info, $value, $exptime = 0, $flags = 0 ): void {
238 $this->logger->debug( __METHOD__ .
" was called for $info" );
240 if ( $flags === BagOStuff::WRITE_CACHE_ONLY && $value ===
false ) {
247 $anonKey = $this->anonSessionStore->makeKey(
'MWSession', $info->
getId() );
248 $authKey = $this->authenticatedSessionStore->makeKey(
'MWSession', $info->
getId() );
250 $this->anonSessionStore->set( $anonKey,
false, 0, BagOStuff::WRITE_CACHE_ONLY );
251 $this->authenticatedSessionStore->set( $authKey,
false, 0, BagOStuff::WRITE_CACHE_ONLY );
255 [ $store, $isAuthenticated ] = $this->getActiveStore( $info );
257 $key = $store->makeKey(
'MWSession', $info->
getId() );
258 $userName = $value[
'metadata'][
'userName'] ??
null;
264 if ( $isAuthenticated && $userName ===
null ) {
265 $this->logger->warning(
'Store is authenticated, but the user associated to the data is anonymous', [
266 'sessionInfo' => (
string)$info,
267 'user' => (
string)$info->getUserInfo(),
268 'exception' =>
new RuntimeException(),
269 'flags' => (
string)$flags
273 if ( !$isAuthenticated && $userName !==
null ) {
274 $this->logger->warning(
'Store is anonymous, but the user associated to the data is authenticated', [
275 'sessionInfo' => (
string)$info,
276 'user' => (
string)$info->getUserInfo(),
277 'exception' =>
new RuntimeException(),
278 'flags' => (
string)$flags
283 $store->set( $key, $value, $exptime, $flags );
285 if ( ( $flags & BagOStuff::WRITE_CACHE_ONLY ) === 0 ) {
286 $this->statsFactory->getCounter(
'sessionstore_set_total' )
287 ->setLabel(
'type', $isAuthenticated ? self::STATS_LABEL_AUTH : self::STATS_LABEL_ANON )
298 $this->logger->debug( __METHOD__ .
" was called for $info" );
300 [ $store, $isAuthenticated ] = $this->getActiveStore( $info );
301 $key = $store->makeKey(
'MWSession', $info->getId() );
303 $store->delete( $key );
305 $this->statsFactory->getCounter(
'sessionstore_delete_total' )
306 ->setLabel(
'type', $isAuthenticated ? self::STATS_LABEL_AUTH : self::STATS_LABEL_ANON )
314 if ( random_int( 1, 100 ) === 1 ) {
315 $this->logger->debug(
'Cleaning session store expired entries' );
317 $this->authenticatedSessionStore->deleteObjectsExpiringBefore( $timeNow );
318 $this->anonSessionStore->deleteObjectsExpiringBefore( $timeNow );