Page Menu
Home
WickedGov Phorge
Search
Configure Global Search
Log In
Files
F1426490
SessionBackend.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
24 KB
Referenced Files
None
Subscribers
None
SessionBackend.php
View Options
<?php
/**
* MediaWiki session backend
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
* @ingroup Session
*/
namespace
MediaWiki\Session
;
use
InvalidArgumentException
;
use
MediaWiki\Deferred\DeferredUpdates
;
use
MediaWiki\HookContainer\HookContainer
;
use
MediaWiki\HookContainer\HookRunner
;
use
MediaWiki\MainConfigNames
;
use
MediaWiki\MediaWikiServices
;
use
MediaWiki\Request\WebRequest
;
use
MediaWiki\User\User
;
use
MWRestrictions
;
use
Psr\Log\LoggerInterface
;
use
Wikimedia\AtEase\AtEase
;
use
Wikimedia\ObjectCache\CachedBagOStuff
;
/**
* This is the actual workhorse for Session.
*
* Most code does not need to use this class, you want \MediaWiki\Session\Session.
* The exceptions are SessionProviders and SessionMetadata hook functions,
* which get an instance of this class rather than Session.
*
* The reasons for this split are:
* 1. A session can be attached to multiple requests, but we want the Session
* object to have some features that correspond to just one of those
* requests.
* 2. We want reasonable garbage collection behavior, but we also want the
* SessionManager to hold a reference to every active session so it can be
* saved when the request ends.
*
* @ingroup Session
* @since 1.27
*/
final
class
SessionBackend
{
/** @var SessionId */
private
$id
;
/** @var bool */
private
$persist
=
false
;
/** @var bool */
private
$remember
=
false
;
/** @var bool */
private
$forceHTTPS
=
false
;
/** @var array|null */
private
$data
=
null
;
/** @var bool */
private
$forcePersist
=
false
;
/**
* The reason for the next persistSession/unpersistSession call. Only used for logging. Can be:
* - 'renew': triggered by a renew() call)
* - 'no-store': the session was not found in the session store
* - 'no-expiry': there was no expiry * in the session store data; this probably shouldn't happen
* - null otherwise.
* @var string|null
*/
private
$persistenceChangeType
;
/**
* The data from the previous logPersistenceChange() log event. Used for deduplication.
* @var array
*/
private
$persistenceChangeData
=
[];
/** @var bool */
private
$metaDirty
=
false
;
/** @var bool */
private
$dataDirty
=
false
;
/** @var string Used to detect subarray modifications */
private
$dataHash
=
null
;
/** @var CachedBagOStuff */
private
$store
;
/** @var LoggerInterface */
private
$logger
;
/** @var HookRunner */
private
$hookRunner
;
/** @var int */
private
$lifetime
;
/** @var User */
private
$user
;
/** @var int */
private
$curIndex
=
0
;
/** @var WebRequest[] Session requests */
private
$requests
=
[];
/** @var SessionProvider provider */
private
$provider
;
/** @var array|null provider-specified metadata */
private
$providerMetadata
=
null
;
/** @var int */
private
$expires
=
0
;
/** @var int */
private
$loggedOut
=
0
;
/** @var int */
private
$delaySave
=
0
;
/** @var bool */
private
$usePhpSessionHandling
;
/** @var bool */
private
$checkPHPSessionRecursionGuard
=
false
;
/** @var bool */
private
$shutdown
=
false
;
/**
* @param SessionId $id
* @param SessionInfo $info Session info to populate from
* @param CachedBagOStuff $store Backend data store
* @param LoggerInterface $logger
* @param HookContainer $hookContainer
* @param int $lifetime Session data lifetime in seconds
*/
public
function
__construct
(
SessionId
$id
,
SessionInfo
$info
,
CachedBagOStuff
$store
,
LoggerInterface
$logger
,
HookContainer
$hookContainer
,
$lifetime
)
{
$phpSessionHandling
=
MediaWikiServices
::
getInstance
()->
getMainConfig
()
->
get
(
MainConfigNames
::
PHPSessionHandling
);
$this
->
usePhpSessionHandling
=
$phpSessionHandling
!==
'disable'
;
if
(
$info
->
getUserInfo
()
&&
!
$info
->
getUserInfo
()->
isVerified
()
)
{
throw
new
InvalidArgumentException
(
"Refusing to create session for unverified user {$info->getUserInfo()}"
);
}
if
(
$info
->
getProvider
()
===
null
)
{
throw
new
InvalidArgumentException
(
'Cannot create session without a provider'
);
}
if
(
$info
->
getId
()
!==
$id
->
getId
()
)
{
throw
new
InvalidArgumentException
(
'SessionId and SessionInfo don
\'
t match'
);
}
$this
->
id
=
$id
;
$this
->
user
=
$info
->
getUserInfo
()
?
$info
->
getUserInfo
()->
getUser
()
:
MediaWikiServices
::
getInstance
()->
getUserFactory
()->
newAnonymous
();
$this
->
store
=
$store
;
$this
->
logger
=
$logger
;
$this
->
hookRunner
=
new
HookRunner
(
$hookContainer
);
$this
->
lifetime
=
$lifetime
;
$this
->
provider
=
$info
->
getProvider
();
$this
->
persist
=
$info
->
wasPersisted
();
$this
->
remember
=
$info
->
wasRemembered
();
$this
->
forceHTTPS
=
$info
->
forceHTTPS
();
$this
->
providerMetadata
=
$info
->
getProviderMetadata
();
$blob
=
$store
->
get
(
$store
->
makeKey
(
'MWSession'
,
(
string
)
$this
->
id
)
);
if
(
!
is_array
(
$blob
)
||
!
isset
(
$blob
[
'metadata'
]
)
||
!
is_array
(
$blob
[
'metadata'
]
)
||
!
isset
(
$blob
[
'data'
]
)
||
!
is_array
(
$blob
[
'data'
]
)
)
{
$this
->
data
=
[];
$this
->
dataDirty
=
true
;
$this
->
metaDirty
=
true
;
$this
->
persistenceChangeType
=
'no-store'
;
$this
->
logger
->
debug
(
'SessionBackend "{session}" is unsaved, marking dirty in constructor'
,
[
'session'
=>
$this
->
id
->
__toString
(),
]
);
}
else
{
$this
->
data
=
$blob
[
'data'
];
if
(
isset
(
$blob
[
'metadata'
][
'loggedOut'
]
)
)
{
$this
->
loggedOut
=
(
int
)
$blob
[
'metadata'
][
'loggedOut'
];
}
if
(
isset
(
$blob
[
'metadata'
][
'expires'
]
)
)
{
$this
->
expires
=
(
int
)
$blob
[
'metadata'
][
'expires'
];
}
else
{
$this
->
metaDirty
=
true
;
$this
->
persistenceChangeType
=
'no-expiry'
;
$this
->
logger
->
debug
(
'SessionBackend "{session}" metadata dirty due to missing expiration timestamp'
,
[
'session'
=>
$this
->
id
->
__toString
(),
]
);
}
}
$this
->
dataHash
=
md5
(
serialize
(
$this
->
data
)
);
}
/**
* Return a new Session for this backend
* @param WebRequest $request
* @return Session
*/
public
function
getSession
(
WebRequest
$request
)
{
$index
=
++
$this
->
curIndex
;
$this
->
requests
[
$index
]
=
$request
;
$session
=
new
Session
(
$this
,
$index
,
$this
->
logger
);
return
$session
;
}
/**
* Deregister a Session
* @internal For use by \MediaWiki\Session\Session::__destruct() only
* @param int $index
*/
public
function
deregisterSession
(
$index
)
{
unset
(
$this
->
requests
[
$index
]
);
if
(
!
$this
->
shutdown
&&
!
count
(
$this
->
requests
)
)
{
$this
->
save
(
true
);
$this
->
provider
->
getManager
()->
deregisterSessionBackend
(
$this
);
}
}
/**
* Shut down a session
* @internal For use by \MediaWiki\Session\SessionManager::shutdown() only
*/
public
function
shutdown
()
{
$this
->
save
(
true
);
$this
->
shutdown
=
true
;
}
/**
* Returns the session ID.
* @return string
*/
public
function
getId
()
{
return
(
string
)
$this
->
id
;
}
/**
* Fetch the SessionId object
* @internal For internal use by WebRequest
* @return SessionId
*/
public
function
getSessionId
()
{
return
$this
->
id
;
}
/**
* Changes the session ID
* @return string New ID (might be the same as the old)
*/
public
function
resetId
()
{
if
(
$this
->
provider
->
persistsSessionId
()
)
{
$oldId
=
(
string
)
$this
->
id
;
$restart
=
$this
->
usePhpSessionHandling
&&
$oldId
===
session_id
()
&&
PHPSessionHandler
::
isEnabled
();
if
(
$restart
)
{
// If this session is the one behind PHP's $_SESSION, we need
// to close then reopen it.
session_write_close
();
}
$this
->
provider
->
getManager
()->
changeBackendId
(
$this
);
$this
->
provider
->
sessionIdWasReset
(
$this
,
$oldId
);
$this
->
metaDirty
=
true
;
$this
->
logger
->
debug
(
'SessionBackend "{session}" metadata dirty due to ID reset (formerly "{oldId}")'
,
[
'session'
=>
$this
->
id
->
__toString
(),
'oldId'
=>
$oldId
,
]
);
if
(
$restart
)
{
session_id
(
(
string
)
$this
->
id
);
AtEase
::
quietCall
(
'session_start'
);
}
$this
->
autosave
();
// Delete the data for the old session ID now
$this
->
store
->
delete
(
$this
->
store
->
makeKey
(
'MWSession'
,
$oldId
)
);
}
return
(
string
)
$this
->
id
;
}
/**
* Fetch the SessionProvider for this session
* @return SessionProviderInterface
*/
public
function
getProvider
()
{
return
$this
->
provider
;
}
/**
* Indicate whether this session is persisted across requests
*
* For example, if cookies are set.
*
* @return bool
*/
public
function
isPersistent
()
{
return
$this
->
persist
;
}
/**
* Make this session persisted across requests
*
* If the session is already persistent, equivalent to calling
* $this->renew().
*/
public
function
persist
()
{
if
(
!
$this
->
persist
)
{
$this
->
persist
=
true
;
$this
->
forcePersist
=
true
;
$this
->
metaDirty
=
true
;
$this
->
logger
->
debug
(
'SessionBackend "{session}" force-persist due to persist()'
,
[
'session'
=>
$this
->
id
->
__toString
(),
]
);
$this
->
autosave
();
}
else
{
$this
->
renew
();
}
}
/**
* Make this session not persisted across requests
*/
public
function
unpersist
()
{
if
(
$this
->
persist
)
{
// Close the PHP session, if we're the one that's open
if
(
$this
->
usePhpSessionHandling
&&
PHPSessionHandler
::
isEnabled
()
&&
session_id
()
===
(
string
)
$this
->
id
)
{
$this
->
logger
->
debug
(
'SessionBackend "{session}" Closing PHP session for unpersist'
,
[
'session'
=>
$this
->
id
->
__toString
()
]
);
session_write_close
();
session_id
(
''
);
}
$this
->
persist
=
false
;
$this
->
forcePersist
=
true
;
$this
->
metaDirty
=
true
;
// Delete the session data, so the local cache-only write in
// self::save() doesn't get things out of sync with the backend.
$this
->
store
->
delete
(
$this
->
store
->
makeKey
(
'MWSession'
,
(
string
)
$this
->
id
)
);
$this
->
autosave
();
}
}
/**
* Indicate whether the user should be remembered independently of the
* session ID.
* @return bool
*/
public
function
shouldRememberUser
()
{
return
$this
->
remember
;
}
/**
* Set whether the user should be remembered independently of the session
* ID.
* @param bool $remember
*/
public
function
setRememberUser
(
$remember
)
{
if
(
$this
->
remember
!==
(
bool
)
$remember
)
{
$this
->
remember
=
(
bool
)
$remember
;
$this
->
metaDirty
=
true
;
$this
->
logger
->
debug
(
'SessionBackend "{session}" metadata dirty due to remember-user change'
,
[
'session'
=>
$this
->
id
->
__toString
(),
]
);
$this
->
autosave
();
}
}
/**
* Returns the request associated with a Session
* @param int $index Session index
* @return WebRequest
*/
public
function
getRequest
(
$index
)
{
if
(
!
isset
(
$this
->
requests
[
$index
]
)
)
{
throw
new
InvalidArgumentException
(
'Invalid session index'
);
}
return
$this
->
requests
[
$index
];
}
/**
* Returns the authenticated user for this session
* @return User
*/
public
function
getUser
():
User
{
return
$this
->
user
;
}
/**
* Fetch the rights allowed the user when this session is active.
* @return null|string[] Allowed user rights, or null to allow all.
*/
public
function
getAllowedUserRights
()
{
return
$this
->
provider
->
getAllowedUserRights
(
$this
);
}
/**
* Fetch any restrictions imposed on logins or actions when this
* session is active.
* @return MWRestrictions|null
*/
public
function
getRestrictions
():
?
MWRestrictions
{
return
$this
->
provider
->
getRestrictions
(
$this
->
providerMetadata
);
}
/**
* Indicate whether the session user info can be changed
* @return bool
*/
public
function
canSetUser
()
{
return
$this
->
provider
->
canChangeUser
();
}
/**
* Set a new user for this session
* @note This should only be called when the user has been authenticated via a login process
* @param User $user User to set on the session.
* This may become a "UserValue" in the future, or User may be refactored
* into such.
*/
public
function
setUser
(
$user
)
{
if
(
!
$this
->
canSetUser
()
)
{
throw
new
\BadMethodCallException
(
'Cannot set user on this session; check $session->canSetUser() first'
);
}
$this
->
user
=
$user
;
$this
->
metaDirty
=
true
;
$this
->
logger
->
debug
(
'SessionBackend "{session}" metadata dirty due to user change'
,
[
'session'
=>
$this
->
id
->
__toString
(),
]
);
$this
->
autosave
();
}
/**
* Get a suggested username for the login form
* @param int $index Session index
* @return string|null
*/
public
function
suggestLoginUsername
(
$index
)
{
if
(
!
isset
(
$this
->
requests
[
$index
]
)
)
{
throw
new
InvalidArgumentException
(
'Invalid session index'
);
}
return
$this
->
provider
->
suggestLoginUsername
(
$this
->
requests
[
$index
]
);
}
/**
* Whether HTTPS should be forced
* @return bool
*/
public
function
shouldForceHTTPS
()
{
return
$this
->
forceHTTPS
;
}
/**
* Set whether HTTPS should be forced
* @param bool $force
*/
public
function
setForceHTTPS
(
$force
)
{
if
(
$this
->
forceHTTPS
!==
(
bool
)
$force
)
{
$this
->
forceHTTPS
=
(
bool
)
$force
;
$this
->
metaDirty
=
true
;
$this
->
logger
->
debug
(
'SessionBackend "{session}" metadata dirty due to force-HTTPS change'
,
[
'session'
=>
$this
->
id
->
__toString
(),
]
);
$this
->
autosave
();
}
}
/**
* Fetch the "logged out" timestamp
* @return int
*/
public
function
getLoggedOutTimestamp
()
{
return
$this
->
loggedOut
;
}
/**
* @param int|null $ts
*/
public
function
setLoggedOutTimestamp
(
$ts
=
null
)
{
$ts
=
(
int
)
$ts
;
if
(
$this
->
loggedOut
!==
$ts
)
{
$this
->
loggedOut
=
$ts
;
$this
->
metaDirty
=
true
;
$this
->
logger
->
debug
(
'SessionBackend "{session}" metadata dirty due to logged-out-timestamp change'
,
[
'session'
=>
$this
->
id
->
__toString
(),
]
);
$this
->
autosave
();
}
}
/**
* Fetch provider metadata
* @note For use by SessionProvider subclasses only
* @return array|null
*/
public
function
getProviderMetadata
()
{
return
$this
->
providerMetadata
;
}
/**
* @note For use by SessionProvider subclasses only
* @param array|null $metadata
*/
public
function
setProviderMetadata
(
$metadata
)
{
if
(
$metadata
!==
null
&&
!
is_array
(
$metadata
)
)
{
throw
new
InvalidArgumentException
(
'$metadata must be an array or null'
);
}
if
(
$this
->
providerMetadata
!==
$metadata
)
{
$this
->
providerMetadata
=
$metadata
;
$this
->
metaDirty
=
true
;
$this
->
logger
->
debug
(
'SessionBackend "{session}" metadata dirty due to provider metadata change'
,
[
'session'
=>
$this
->
id
->
__toString
(),
]
);
$this
->
autosave
();
}
}
/**
* Fetch the session data array
*
* Note the caller is responsible for calling $this->dirty() if anything in
* the array is changed.
*
* @internal For use by \MediaWiki\Session\Session only.
* @return array
*/
public
function
&
getData
()
{
return
$this
->
data
;
}
/**
* Add data to the session.
*
* Overwrites any existing data under the same keys.
*
* @param array $newData Key-value pairs to add to the session
*/
public
function
addData
(
array
$newData
)
{
$data
=
&
$this
->
getData
();
foreach
(
$newData
as
$key
=>
$value
)
{
if
(
!
array_key_exists
(
$key
,
$data
)
||
$data
[
$key
]
!==
$value
)
{
$data
[
$key
]
=
$value
;
$this
->
dataDirty
=
true
;
$this
->
logger
->
debug
(
'SessionBackend "{session}" data dirty due to addData(): {callers}'
,
[
'session'
=>
$this
->
id
->
__toString
(),
'callers'
=>
wfGetAllCallers
(
5
),
]
);
}
}
}
/**
* Mark data as dirty
* @internal For use by \MediaWiki\Session\Session only.
*/
public
function
dirty
()
{
$this
->
dataDirty
=
true
;
$this
->
logger
->
debug
(
'SessionBackend "{session}" data dirty due to dirty(): {callers}'
,
[
'session'
=>
$this
->
id
->
__toString
(),
'callers'
=>
wfGetAllCallers
(
5
),
]
);
}
/**
* Renew the session by resaving everything
*
* Resets the TTL in the backend store if the session is near expiring, and
* re-persists the session to any active WebRequests if persistent.
*/
public
function
renew
()
{
if
(
time
()
+
$this
->
lifetime
/
2
>
$this
->
expires
)
{
$this
->
metaDirty
=
true
;
$this
->
logger
->
debug
(
'SessionBackend "{callers}" metadata dirty for renew(): {callers}'
,
[
'session'
=>
$this
->
id
->
__toString
(),
'callers'
=>
wfGetAllCallers
(
5
),
]
);
if
(
$this
->
persist
)
{
$this
->
persistenceChangeType
=
'renew'
;
$this
->
forcePersist
=
true
;
$this
->
logger
->
debug
(
'SessionBackend "{session}" force-persist for renew(): {callers}'
,
[
'session'
=>
$this
->
id
->
__toString
(),
'callers'
=>
wfGetAllCallers
(
5
),
]
);
}
}
$this
->
autosave
();
}
/**
* Delay automatic saving while multiple updates are being made
*
* Calls to save() will not be delayed.
*
* @return \Wikimedia\ScopedCallback When this goes out of scope, a save will be triggered
*/
public
function
delaySave
()
{
$this
->
delaySave
++;
return
new
\Wikimedia\ScopedCallback
(
function
()
{
if
(
--
$this
->
delaySave
<=
0
)
{
$this
->
delaySave
=
0
;
$this
->
save
();
}
}
);
}
/**
* Save the session, unless delayed
* @see SessionBackend::save()
*/
private
function
autosave
()
{
if
(
$this
->
delaySave
<=
0
)
{
$this
->
save
();
}
}
/**
* Save the session
*
* Update both the backend data and the associated WebRequest(s) to
* reflect the state of the SessionBackend. This might include
* persisting or unpersisting the session.
*
* @param bool $closing Whether the session is being closed
*/
public
function
save
(
$closing
=
false
)
{
$anon
=
$this
->
user
->
isAnon
();
if
(
!
$anon
&&
$this
->
provider
->
getManager
()->
isUserSessionPrevented
(
$this
->
user
->
getName
()
)
)
{
$this
->
logger
->
debug
(
'SessionBackend "{session}" not saving, user {user} was '
.
'passed to SessionManager::preventSessionsForUser'
,
[
'session'
=>
$this
->
id
->
__toString
(),
'user'
=>
$this
->
user
->
__toString
(),
]
);
return
;
}
// Ensure the user has a token
// @codeCoverageIgnoreStart
if
(
!
$anon
&&
defined
(
'MW_PHPUNIT_TEST'
)
&&
MediaWikiServices
::
getInstance
()->
isStorageDisabled
()
)
{
// Avoid making DB queries in non-database tests. We don't need to save the token when using
// fake users, and it would probably be ignored anyway.
return
;
}
if
(
!
$anon
&&
!
$this
->
user
->
getToken
(
false
)
)
{
$this
->
logger
->
debug
(
'SessionBackend "{session}" creating token for user {user} on save'
,
[
'session'
=>
$this
->
id
->
__toString
(),
'user'
=>
$this
->
user
->
__toString
(),
]
);
$this
->
user
->
setToken
();
if
(
!
MediaWikiServices
::
getInstance
()->
getReadOnlyMode
()->
isReadOnly
()
)
{
// Promise that the token set here will be valid; save it at end of request
$user
=
$this
->
user
;
DeferredUpdates
::
addCallableUpdate
(
static
function
()
use
(
$user
)
{
$user
->
saveSettings
();
}
);
}
$this
->
metaDirty
=
true
;
}
// @codeCoverageIgnoreEnd
if
(
!
$this
->
metaDirty
&&
!
$this
->
dataDirty
&&
$this
->
dataHash
!==
md5
(
serialize
(
$this
->
data
)
)
)
{
$this
->
logger
->
debug
(
'SessionBackend "{session}" data dirty due to hash mismatch, {expected} !== {got}'
,
[
'session'
=>
$this
->
id
->
__toString
(),
'expected'
=>
$this
->
dataHash
,
'got'
=>
md5
(
serialize
(
$this
->
data
)
),
]
);
$this
->
dataDirty
=
true
;
}
if
(
!
$this
->
metaDirty
&&
!
$this
->
dataDirty
&&
!
$this
->
forcePersist
)
{
return
;
}
$this
->
logger
->
debug
(
'SessionBackend "{session}" save: dataDirty={dataDirty} '
.
'metaDirty={metaDirty} forcePersist={forcePersist}'
,
[
'session'
=>
$this
->
id
->
__toString
(),
'dataDirty'
=>
(
int
)
$this
->
dataDirty
,
'metaDirty'
=>
(
int
)
$this
->
metaDirty
,
'forcePersist'
=>
(
int
)
$this
->
forcePersist
,
]
);
// Persist or unpersist to the provider, if necessary
if
(
$this
->
metaDirty
||
$this
->
forcePersist
)
{
if
(
$this
->
persist
)
{
foreach
(
$this
->
requests
as
$request
)
{
$request
->
setSessionId
(
$this
->
getSessionId
()
);
$this
->
logPersistenceChange
(
$request
,
true
);
$this
->
provider
->
persistSession
(
$this
,
$request
);
}
if
(
!
$closing
)
{
$this
->
checkPHPSession
();
}
}
else
{
foreach
(
$this
->
requests
as
$request
)
{
if
(
$request
->
getSessionId
()
===
$this
->
id
)
{
$this
->
logPersistenceChange
(
$request
,
false
);
$this
->
provider
->
unpersistSession
(
$request
);
}
}
}
}
$this
->
forcePersist
=
false
;
$this
->
persistenceChangeType
=
null
;
if
(
!
$this
->
metaDirty
&&
!
$this
->
dataDirty
)
{
return
;
}
// Save session data to store, if necessary
$metadata
=
$origMetadata
=
[
'provider'
=>
(
string
)
$this
->
provider
,
'providerMetadata'
=>
$this
->
providerMetadata
,
'userId'
=>
$anon
?
0
:
$this
->
user
->
getId
(),
'userName'
=>
MediaWikiServices
::
getInstance
()->
getUserNameUtils
()
->
isValid
(
$this
->
user
->
getName
()
)
?
$this
->
user
->
getName
()
:
null
,
'userToken'
=>
$anon
?
null
:
$this
->
user
->
getToken
(),
'remember'
=>
!
$anon
&&
$this
->
remember
,
'forceHTTPS'
=>
$this
->
forceHTTPS
,
'expires'
=>
time
()
+
$this
->
lifetime
,
'loggedOut'
=>
$this
->
loggedOut
,
'persisted'
=>
$this
->
persist
,
];
$this
->
hookRunner
->
onSessionMetadata
(
$this
,
$metadata
,
$this
->
requests
);
foreach
(
$origMetadata
as
$k
=>
$v
)
{
if
(
$metadata
[
$k
]
!==
$v
)
{
throw
new
\UnexpectedValueException
(
"SessionMetadata hook changed metadata key
\"
$k
\"
"
);
}
}
$flags
=
$this
->
persist
?
0
:
CachedBagOStuff
::
WRITE_CACHE_ONLY
;
$this
->
store
->
set
(
$this
->
store
->
makeKey
(
'MWSession'
,
(
string
)
$this
->
id
),
[
'data'
=>
$this
->
data
,
'metadata'
=>
$metadata
,
],
$metadata
[
'expires'
],
$flags
);
$this
->
metaDirty
=
false
;
$this
->
dataDirty
=
false
;
$this
->
dataHash
=
md5
(
serialize
(
$this
->
data
)
);
$this
->
expires
=
$metadata
[
'expires'
];
}
/**
* For backwards compatibility, open the PHP session when the global
* session is persisted
*/
private
function
checkPHPSession
()
{
if
(
!
$this
->
checkPHPSessionRecursionGuard
)
{
$this
->
checkPHPSessionRecursionGuard
=
true
;
$reset
=
new
\Wikimedia\ScopedCallback
(
function
()
{
$this
->
checkPHPSessionRecursionGuard
=
false
;
}
);
if
(
$this
->
usePhpSessionHandling
&&
session_id
()
===
''
&&
PHPSessionHandler
::
isEnabled
()
&&
SessionManager
::
getGlobalSession
()->
getId
()
===
(
string
)
$this
->
id
)
{
$this
->
logger
->
debug
(
'SessionBackend "{session}" Taking over PHP session'
,
[
'session'
=>
$this
->
id
->
__toString
(),
]
);
session_id
(
(
string
)
$this
->
id
);
AtEase
::
quietCall
(
'session_start'
);
}
}
}
/**
* Helper method for logging persistSession/unpersistSession calls.
* @param WebRequest $request
* @param bool $persist True when persisting, false when unpersisting
*/
private
function
logPersistenceChange
(
WebRequest
$request
,
bool
$persist
)
{
if
(
!
$this
->
isPersistent
()
&&
!
$persist
)
{
// FIXME SessionManager calls unpersistSession() on anonymous requests (and the cookie
// filtering in WebResponse makes it a noop). Skip those.
return
;
}
$verb
=
$persist
?
'Persisting'
:
'Unpersisting'
;
if
(
$this
->
persistenceChangeType
===
'renew'
)
{
$message
=
"$verb session for renewal"
;
}
elseif
(
$this
->
persistenceChangeType
===
'no-store'
)
{
$message
=
"$verb session due to no pre-existing stored session"
;
}
elseif
(
$this
->
persistenceChangeType
===
'no-expiry'
)
{
$message
=
"$verb session due to lack of stored expiry"
;
}
elseif
(
$this
->
persistenceChangeType
===
null
)
{
$message
=
"$verb session for unknown reason"
;
}
// Because SessionManager repeats session loading several times in the same request,
// it will try to persist or unpersist several times. WebResponse deduplicates, but
// we want to deduplicate logging as well since the volume is already fairly large.
$id
=
$this
->
getId
();
$user
=
$this
->
getUser
()->
isAnon
()
?
'<anon>'
:
$this
->
getUser
()->
getName
();
if
(
$this
->
persistenceChangeData
&&
$this
->
persistenceChangeData
[
'id'
]
===
$id
&&
$this
->
persistenceChangeData
[
'user'
]
===
$user
// @phan-suppress-next-line PhanPossiblyUndeclaredVariable message always set
&&
$this
->
persistenceChangeData
[
'message'
]
===
$message
)
{
return
;
}
// @phan-suppress-next-line PhanPossiblyUndeclaredVariable message always set
$this
->
persistenceChangeData
=
[
'id'
=>
$id
,
'user'
=>
$user
,
'message'
=>
$message
];
// @phan-suppress-next-line PhanTypeMismatchArgumentNullable,PhanPossiblyUndeclaredVariable message always set
$this
->
logger
->
info
(
$message
,
[
'id'
=>
$id
,
'provider'
=>
get_class
(
$this
->
getProvider
()
),
'user'
=>
$user
,
'clientip'
=>
$request
->
getIP
(),
'userAgent'
=>
$request
->
getHeader
(
'user-agent'
),
]
);
}
}
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Sat, May 16, 13:18 (1 d, 17 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
86/51/dd9cae78481a6a2e6812aefe1ffc
Default Alt Text
SessionBackend.php (24 KB)
Attached To
Mode
rMWPROD MediaWiki Production
Attached
Detach File
Event Timeline
Log In to Comment