Page Menu
Home
WickedGov Phorge
Search
Configure Global Search
Log In
Files
F4103468
CentralAuthSessionProvider.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
19 KB
Referenced Files
None
Subscribers
None
CentralAuthSessionProvider.php
View Options
<?php
use
MediaWiki\Context\RequestContext
;
use
MediaWiki\Extension\CentralAuth\CentralAuthSessionManager
;
use
MediaWiki\Extension\CentralAuth\User\CentralAuthUser
;
use
MediaWiki\MainConfigNames
;
use
MediaWiki\Password\InvalidPassword
;
use
MediaWiki\Password\PasswordError
;
use
MediaWiki\Password\PasswordFactory
;
use
MediaWiki\Request\FauxRequest
;
use
MediaWiki\Request\WebRequest
;
use
MediaWiki\Session\CookieSessionProvider
;
use
MediaWiki\Session\SessionBackend
;
use
MediaWiki\Session\SessionInfo
;
use
MediaWiki\Session\SessionManager
;
use
MediaWiki\Session\UserInfo
;
use
MediaWiki\User\TempUser\TempUserConfig
;
use
MediaWiki\User\User
;
use
MediaWiki\User\UserIdentityLookup
;
use
MediaWiki\User\UserRigorOptions
;
use
Wikimedia\Rdbms\IDBAccessObject
;
/**
* CentralAuth cookie-based sessions.
*
* This is intended to completely replace the core CookieSessionProvider.
*
* @warning Due to the complicated way CentralAuth has historically interacted with core
* sessions, this is somewhat complicated and is probably not a good example to
* copy if you're writing your own SessionProvider.
*/
class
CentralAuthSessionProvider
extends
CookieSessionProvider
{
/** @var bool */
protected
$enable
=
false
;
/** @var array */
protected
$centralCookieOptions
=
[];
private
UserIdentityLookup
$userIdentityLookup
;
private
CentralAuthSessionManager
$sessionManager
;
private
TempUserConfig
$tempUserConfig
;
/**
* @param TempUserConfig $tempUserConfig
* @param UserIdentityLookup $userIdentityLookup
* @param CentralAuthSessionManager $sessionManager
* @param array $params In addition to the parameters for
* CookieSessionProvider, the following are
* recognized:
* - enable: Whether to set CentralAuth-specific features. Defaults to
* $wgCentralAuthCookies.
* - centralSessionName: Central session cookie name. Defaults to
* centralCookieOptions['prefix'] . 'Session'. Note this does not
* replace the parent class's 'sessionName', it's a different cookie.
* - centralCookieOptions: Settings for central cookies
* - prefix: Cookie prefix, defaults to $wgCentralAuthCookiePrefix
* - path: Cookie path, defaults to $wgCentralAuthCookiePath
* - domain: Cookie domain, defaults to $wgCentralAuthCookieDomain
* - secure: Cookie secure flag, defaults to $wgCookieSecure
* - httpOnly: Cookie httpOnly flag, defaults to $wgCookieHttpOnly
* - sameSite: Cookie SameSite attribute, defaults to $wgCookieSameSite
*/
public
function
__construct
(
TempUserConfig
$tempUserConfig
,
UserIdentityLookup
$userIdentityLookup
,
CentralAuthSessionManager
$sessionManager
,
$params
=
[]
)
{
$this
->
userIdentityLookup
=
$userIdentityLookup
;
$this
->
sessionManager
=
$sessionManager
;
$this
->
tempUserConfig
=
$tempUserConfig
;
$params
+=
[
'centralCookieOptions'
=>
[],
];
if
(
!
is_array
(
$params
[
'centralCookieOptions'
]
)
)
{
throw
new
\InvalidArgumentException
(
__METHOD__
.
': centralCookieOptions must be an array'
);
}
$this
->
centralCookieOptions
=
$params
[
'centralCookieOptions'
];
unset
(
$params
[
'centralCookieOptions'
]
);
parent
::
__construct
(
$params
);
}
protected
function
postInitSetup
()
{
parent
::
postInitSetup
();
$this
->
centralCookieOptions
+=
[
'prefix'
=>
$this
->
getConfig
()->
get
(
'CentralAuthCookiePrefix'
),
'path'
=>
$this
->
getConfig
()->
get
(
'CentralAuthCookiePath'
),
'domain'
=>
$this
->
getConfig
()->
get
(
'CentralAuthCookieDomain'
),
'secure'
=>
$this
->
getConfig
()->
get
(
'CookieSecure'
)
||
$this
->
getConfig
()->
get
(
'ForceHTTPS'
),
'httpOnly'
=>
$this
->
getConfig
()->
get
(
'CookieHttpOnly'
),
'sameSite'
=>
$this
->
getConfig
()->
get
(
'CookieSameSite'
)
];
$params
=
[
'enable'
=>
$this
->
getConfig
()->
get
(
'CentralAuthCookies'
),
'centralSessionName'
=>
$this
->
centralCookieOptions
[
'prefix'
]
.
'Session'
,
];
$this
->
params
+=
$params
;
$this
->
enable
=
(
bool
)
$params
[
'enable'
];
}
/**
* Get the local session info, with CentralAuthSource metadata.
*
* @param WebRequest $request
* @param bool $forceEmptyPersist If this is true and there is no local
* session, return a persistent empty local session to override the
* non-functional CentralAuth one. This prevents the deletion of global
* cookies, allowing the user to retain their central login elsewhere
* in the same cookie domain. It makes sense to do this for problems with
* the central session that are specific to the local wiki. (T342475)
* @return SessionInfo|null
*/
private
function
returnParentSessionInfo
(
WebRequest
$request
,
$forceEmptyPersist
=
false
)
{
$info
=
parent
::
provideSessionInfo
(
$request
);
if
(
$info
)
{
return
new
SessionInfo
(
$info
->
getPriority
(),
[
'copyFrom'
=>
$info
,
'metadata'
=>
[
'CentralAuthSource'
=>
'Local'
,
],
]
);
}
if
(
$forceEmptyPersist
)
{
return
new
SessionInfo
(
$this
->
priority
,
[
'id'
=>
null
,
'provider'
=>
$this
,
'idIsSafe'
=>
true
,
'persisted'
=>
true
,
'metadata'
=>
[
'CentralAuthSource'
=>
'Local'
,
],
]
);
}
return
null
;
}
/**
* Determine whether $request has a valid CentralAuth session.
*
* The following requests are considered to have a valid CentralAuth session:
* - A request with a centralauth_User and centralauth_Token cookie (prefix is configurable)
* that match the data in the globaluser table.
* - A request with a centralauth_Session cookie where the referenced data exists in the
* central session store, and the username and token in the central session object match
* the data in the globaluser table.
*
* The following requests are considered to have a valid local session (marked with
* CentralAuthSource => Local in the session metadata):
* - A request with a centralauth_Session cookie where the referenced data exists in the
* central session store, but is a stub (has pending_name or pending_guid fields).
* - Anything that CookieSessionProvider considers valid, if the session is anonymous or for
* a local user that's not attached to the global CentralAuth user.
* - When $wgCentralAuthCookies is disabled, falls back to CookieSessionProvider entirely.
*
* @param WebRequest $request
* @return SessionInfo|null
*/
public
function
provideSessionInfo
(
WebRequest
$request
)
{
if
(
!
$this
->
enable
)
{
$this
->
logger
->
debug
(
__METHOD__
.
': Not enabled, falling back to core sessions'
);
return
$this
->
returnParentSessionInfo
(
$request
);
}
$info
=
[
'id'
=>
$this
->
getCookie
(
$request
,
$this
->
params
[
'sessionName'
],
''
)
];
if
(
!
SessionManager
::
validateSessionId
(
$info
[
'id'
]
)
)
{
unset
(
$info
[
'id'
]
);
}
$userName
=
null
;
$token
=
null
;
$from
=
null
;
$prefix
=
$this
->
centralCookieOptions
[
'prefix'
];
$userCookie
=
$this
->
getCookie
(
$request
,
'User'
,
$prefix
);
$tokenCookie
=
$this
->
getCookie
(
$request
,
'Token'
,
$prefix
);
if
(
$userCookie
!==
null
&&
$tokenCookie
!==
null
)
{
$userName
=
$userCookie
;
$token
=
$tokenCookie
;
$from
=
'cookies'
;
}
else
{
$id
=
$this
->
getCookie
(
$request
,
$this
->
params
[
'centralSessionName'
],
''
);
if
(
$id
!==
null
)
{
$data
=
$this
->
sessionManager
->
getCentralSessionById
(
$id
);
if
(
isset
(
$data
[
'pending_name'
]
)
||
isset
(
$data
[
'pending_guid'
]
)
)
{
$this
->
logger
->
debug
(
__METHOD__
.
': uninitialized session'
);
}
elseif
(
isset
(
$data
[
'token'
]
)
&&
isset
(
$data
[
'user'
]
)
)
{
$token
=
$data
[
'token'
];
$userName
=
$data
[
'user'
];
$from
=
'session'
;
}
else
{
$this
->
logger
->
debug
(
__METHOD__
.
': uninitialized session'
);
}
}
}
if
(
$userName
===
null
||
$token
===
null
)
{
return
$this
->
returnParentSessionInfo
(
$request
);
}
// Check to avoid session ID collisions, as reported on T21158
if
(
$userCookie
===
null
)
{
$this
->
logger
->
debug
(
__METHOD__
.
': no User cookie, so unable to check for session mismatch'
);
return
$this
->
returnParentSessionInfo
(
$request
);
}
if
(
$userCookie
!=
$userName
)
{
$this
->
logger
->
debug
(
__METHOD__
.
': Session ID/User mismatch. Possible session collision. '
.
"Expected: $userName; actual: $userCookie"
);
return
$this
->
returnParentSessionInfo
(
$request
);
}
// Clean up username
$userName
=
$this
->
userNameUtils
->
getCanonical
(
$userName
);
if
(
!
$userName
)
{
$this
->
logger
->
debug
(
__METHOD__
.
': invalid username'
);
return
$this
->
returnParentSessionInfo
(
$request
);
}
if
(
!
$this
->
userNameUtils
->
isUsable
(
$userName
)
)
{
// Log a warning if the username is not usable, except if the name is reserved by the temporary account
// system to avoid spamming the logs (T373827).
if
(
!
$this
->
tempUserConfig
->
isReservedName
(
$userName
)
)
{
$this
->
logger
->
warning
(
__METHOD__
.
': username {username} is not usable on this wiki'
,
[
'username'
=>
$userName
,
]
);
}
return
$this
->
returnParentSessionInfo
(
$request
,
true
);
}
// Try the central user
$centralUser
=
CentralAuthUser
::
getInstanceByName
(
$userName
);
// Skip if they're being renamed
if
(
$centralUser
->
renameInProgress
()
)
{
$this
->
logger
->
debug
(
__METHOD__
.
': rename in progress'
);
// No fallback here, just fail it because our SessionCheckMetadata
// hook will do so anyway.
return
null
;
}
if
(
!
$centralUser
->
exists
()
)
{
$this
->
logger
->
debug
(
__METHOD__
.
': global account doesn
\'
t exist'
);
return
$this
->
returnParentSessionInfo
(
$request
);
}
if
(
!
$centralUser
->
isAttached
()
)
{
$userIdentity
=
$this
->
userIdentityLookup
->
getUserIdentityByName
(
$userName
);
if
(
$userIdentity
&&
$userIdentity
->
isRegistered
()
)
{
$this
->
logger
->
debug
(
__METHOD__
.
': not attached and local account exists'
);
return
$this
->
returnParentSessionInfo
(
$request
,
true
);
}
}
if
(
$centralUser
->
authenticateWithToken
(
$token
)
!=
'ok'
)
{
$this
->
logger
->
debug
(
__METHOD__
.
': token mismatch'
);
// At this point, don't log in with a local session anymore
return
null
;
}
$this
->
logger
->
debug
(
__METHOD__
.
": logged in from $from"
);
$info
+=
[
'userInfo'
=>
UserInfo
::
newFromName
(
$userName
,
true
),
'provider'
=>
$this
,
// CA sessions are always persistent
'persisted'
=>
true
,
'remembered'
=>
$tokenCookie
!==
null
,
'metadata'
=>
[
'CentralAuthSource'
=>
'CentralAuth'
,
],
];
return
new
SessionInfo
(
$this
->
priority
,
$info
);
}
/** @inheritDoc */
public
function
refreshSessionInfo
(
SessionInfo
$info
,
WebRequest
$request
,
&
$metadata
)
{
// Check on the metadata, to avoid T124409
if
(
isset
(
$metadata
[
'CentralAuthSource'
]
)
)
{
$name
=
$info
->
getUserInfo
()->
getName
();
if
(
$name
===
null
)
{
return
true
;
}
$source
=
'Local'
;
if
(
$this
->
enable
)
{
$centralUser
=
CentralAuthUser
::
getInstanceByName
(
$name
);
$centralUserExists
=
$centralUser
->
exists
();
if
(
$centralUserExists
&&
$centralUser
->
isAttached
()
)
{
$source
=
'CentralAuth'
;
}
elseif
(
$centralUserExists
)
{
$userIdentity
=
$this
->
userIdentityLookup
->
getUserIdentityByName
(
$name
,
IDBAccessObject
::
READ_LATEST
);
if
(
!
$userIdentity
||
!
$userIdentity
->
isRegistered
()
)
{
$source
=
'CentralAuth'
;
}
}
}
if
(
$metadata
[
'CentralAuthSource'
]
!==
$source
)
{
$this
->
logger
->
warning
(
'Session "{session}": CentralAuth saved source {saved} != expected source {expected}'
,
[
'session'
=>
$info
->
__toString
(),
'saved'
=>
$metadata
[
'CentralAuthSource'
],
'expected'
=>
$source
,
]
);
return
false
;
}
}
return
true
;
}
/** @inheritDoc */
public
function
sessionIdWasReset
(
SessionBackend
$session
,
$oldId
)
{
if
(
!
$this
->
enable
)
{
return
;
}
// We need a Session to pass to CentralAuthSessionManager::setCentralSession()
// to reset the session ID, so create one on a new FauxRequest.
$s
=
$session
->
getSession
(
new
FauxRequest
()
);
// We also need to fetch the current central data to pass to
// CentralAuthSessionManager::setCentralSession() when resetting the ID.
$data
=
$this
->
sessionManager
->
getCentralSession
(
$s
);
$this
->
sessionManager
->
setCentralSession
(
$data
,
true
,
$s
);
}
/** @inheritDoc */
protected
function
sessionDataToExport
(
$user
)
{
$data
=
parent
::
sessionDataToExport
(
$user
);
// CentralAuth needs to prevent core login-from-session to
// avoid bugs like T124409
$centralUser
=
CentralAuthUser
::
getInstance
(
$user
);
if
(
$centralUser
->
isAttached
()
)
{
unset
(
$data
[
'wsToken'
]
);
}
return
$data
;
}
/** @inheritDoc */
protected
function
cookieDataToExport
(
$user
,
$remember
)
{
// If we're going to set CA cookies, don't remember in core cookies.
if
(
$this
->
enable
&&
$remember
)
{
$centralUser
=
CentralAuthUser
::
getInstance
(
$user
);
$remember
=
!
$centralUser
->
isAttached
();
}
return
parent
::
cookieDataToExport
(
$user
,
$remember
);
}
/**
* Sets CentralAuth and core authentication cookies.
*
* - For authenticated sessions where the local account is owned by the corresponding central
* account, the core <wiki>UserToken cookie is not set, the centralauth_Session,
* centralauth_User, and (if the session is remembered) centralauth_Token cookies are set,
* and a new central session object is created in the central session store if there wasn't
* already a centralauth_Session cookie pointing at a valid object.
* - Otherwise (including stub central sessions), behavior is identical to CookieSessionProvider.
*
* @param SessionBackend $session
* @param WebRequest $request
*/
public
function
persistSession
(
SessionBackend
$session
,
WebRequest
$request
)
{
parent
::
persistSession
(
$session
,
$request
);
if
(
!
$this
->
enable
)
{
return
;
}
$response
=
$request
->
response
();
if
(
$response
->
headersSent
()
)
{
// Can't do anything now
return
;
}
$s
=
$session
->
getSession
(
$request
);
$user
=
$session
->
getUser
();
$centralUser
=
CentralAuthUser
::
getInstance
(
$user
);
if
(
$centralUser
->
exists
()
&&
(
$centralUser
->
isAttached
()
||
!
$user
->
getId
()
)
)
{
// CentralAuth needs to prevent core login-from-session to
// avoid bugs like T124409
$data
=
&
$session
->
getData
();
if
(
array_key_exists
(
'wsToken'
,
$data
)
)
{
unset
(
$data
[
'wsToken'
]
);
$session
->
dirty
();
}
unset
(
$data
);
$metadata
=
$session
->
getProviderMetadata
();
$metadata
[
'CentralAuthSource'
]
=
'CentralAuth'
;
$session
->
setProviderMetadata
(
$metadata
);
$remember
=
$session
->
shouldRememberUser
();
$options
=
$this
->
centralCookieOptions
;
// We only save the user into the central session if it's not a
// "pending" session, but we still need the ID to set the cookie.
$data
=
$this
->
sessionManager
->
getCentralSession
(
$s
);
if
(
isset
(
$data
[
'pending_name'
]
)
)
{
$remember
=
false
;
}
else
{
$data
[
'user'
]
=
$centralUser
->
getName
();
$data
[
'token'
]
=
$centralUser
->
getAuthToken
();
$data
[
'remember'
]
=
$remember
;
}
$centralSessionId
=
$this
->
sessionManager
->
setCentralSession
(
$data
,
false
,
$s
);
$cookies
=
[
'User'
=>
(
string
)
$centralUser
->
getName
(),
'Token'
=>
$remember
?
(
string
)
$centralUser
->
getAuthToken
()
:
false
,
];
foreach
(
$cookies
as
$name
=>
$value
)
{
if
(
$value
===
false
)
{
$response
->
clearCookie
(
$name
,
$options
);
}
else
{
$expirationDuration
=
$this
->
getLoginCookieExpiration
(
$name
,
$remember
);
$expiration
=
$expirationDuration
?
$expirationDuration
+
time
()
:
null
;
$response
->
setCookie
(
$name
,
(
string
)
$value
,
$expiration
,
$options
);
}
}
$response
->
setCookie
(
$this
->
params
[
'centralSessionName'
],
$centralSessionId
,
null
,
[
'prefix'
=>
''
]
+
$options
);
}
else
{
$metadata
=
$session
->
getProviderMetadata
();
$metadata
[
'CentralAuthSource'
]
=
'Local'
;
$session
->
setProviderMetadata
(
$metadata
);
}
}
/**
* Deletes CentralAuth and core authentication cookies.
*
* @param WebRequest $request
*/
public
function
unpersistSession
(
WebRequest
$request
)
{
parent
::
unpersistSession
(
$request
);
if
(
!
$this
->
enable
)
{
return
;
}
$response
=
$request
->
response
();
if
(
$response
->
headersSent
()
)
{
// Can't do anything now
$this
->
logger
->
debug
(
__METHOD__
.
': Headers already sent'
);
return
;
}
$expiry
=
time
()
-
86400
;
$response
->
clearCookie
(
'User'
,
$this
->
centralCookieOptions
);
$response
->
clearCookie
(
'Token'
,
$this
->
centralCookieOptions
);
$response
->
clearCookie
(
$this
->
params
[
'centralSessionName'
],
[
'prefix'
=>
''
]
+
$this
->
centralCookieOptions
);
}
/** @inheritDoc */
public
function
invalidateSessionsForUser
(
User
$user
)
{
$centralUser
=
CentralAuthUser
::
getPrimaryInstance
(
$user
);
if
(
$centralUser
->
exists
()
&&
(
$centralUser
->
isAttached
()
||
!
$user
->
isRegistered
()
)
)
{
$centralUser
->
resetAuthToken
();
}
}
/** @inheritDoc */
public
function
preventSessionsForUser
(
$username
)
{
$username
=
$this
->
userNameUtils
->
getCanonical
(
$username
);
if
(
!
$username
)
{
return
;
}
$centralUser
=
CentralAuthUser
::
getPrimaryInstanceByName
(
$username
);
if
(
!
$centralUser
->
exists
()
)
{
return
;
}
// Reset the user's password to something invalid and reset the token
// if it's not already invalid.
$config
=
RequestContext
::
getMain
()->
getConfig
();
$passwordFactory
=
new
PasswordFactory
(
$config
->
get
(
MainConfigNames
::
PasswordConfig
),
$config
->
get
(
MainConfigNames
::
PasswordDefault
)
);
try
{
$password
=
$passwordFactory
->
newFromCiphertext
(
$centralUser
->
getPassword
()
);
}
catch
(
PasswordError
$e
)
{
return
;
}
if
(
!
$password
instanceof
InvalidPassword
)
{
$centralUser
->
setPassword
(
null
);
}
}
/**
* @inheritDoc
*/
protected
function
setForceHTTPSCookie
(
$set
,
?
SessionBackend
$backend
,
WebRequest
$request
)
{
// Do nothing. We don't support mixed-protocol HTTP/HTTPS wikis in CentralAuth,
// so this cookie is not needed.
}
/** @inheritDoc */
protected
function
setLoggedOutCookie
(
$loggedOut
,
WebRequest
$request
)
{
if
(
$loggedOut
+
86400
>
time
()
&&
$loggedOut
!==
(
int
)
$this
->
getCookie
(
$request
,
'LoggedOut'
,
$this
->
centralCookieOptions
[
'prefix'
]
)
)
{
$request
->
response
()->
setCookie
(
'LoggedOut'
,
(
string
)
$loggedOut
,
$loggedOut
+
86400
,
$this
->
centralCookieOptions
);
}
}
/**
* @return string[]
*/
public
function
getVaryCookies
()
{
$cookies
=
parent
::
getVaryCookies
();
if
(
$this
->
enable
)
{
$prefix
=
$this
->
centralCookieOptions
[
'prefix'
];
$cookies
[]
=
$prefix
.
'Token'
;
$cookies
[]
=
$prefix
.
'LoggedOut'
;
$cookies
[]
=
$this
->
params
[
'centralSessionName'
];
}
return
$cookies
;
}
/** @inheritDoc */
public
function
suggestLoginUsername
(
WebRequest
$request
)
{
$name
=
$this
->
getCookie
(
$request
,
'User'
,
$this
->
centralCookieOptions
[
'prefix'
]
);
if
(
$name
!==
null
)
{
if
(
$this
->
userNameUtils
->
isTemp
(
$name
)
)
{
$name
=
false
;
}
else
{
$name
=
$this
->
userNameUtils
->
getCanonical
(
$name
,
UserRigorOptions
::
RIGOR_USABLE
);
}
}
return
(
$name
===
false
||
$name
===
null
)
?
parent
::
suggestLoginUsername
(
$request
)
:
$name
;
}
/**
* Fetch the central cookie domain
* @return string
*/
public
function
getCentralCookieDomain
()
{
return
$this
->
centralCookieOptions
[
'domain'
];
}
/** @inheritDoc */
protected
function
getExtendedLoginCookies
()
{
$cookies
=
parent
::
getExtendedLoginCookies
();
$cookies
[]
=
'User'
;
return
$cookies
;
}
/** @inheritDoc */
public
function
getRememberUserDuration
()
{
// CentralAuth needs User and Token cookies to remember the user. The fallback to
// sessions needs UserID as well, so if that one has shorter expiration, the remember
// duration will depend on whether the account is attached; let's return the shorter
// duration in that case.
return
min
(
$this
->
getLoginCookieExpiration
(
'User'
,
true
),
$this
->
getLoginCookieExpiration
(
'Token'
,
true
),
$this
->
getLoginCookieExpiration
(
'UserID'
,
true
)
)
?:
null
;
}
}
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Tue, Aug 18, 18:00 (3 w, 3 d ago)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
9f/42/f2360cc32c54484d3183e092110b
Default Alt Text
CentralAuthSessionProvider.php (19 KB)
Attached To
Mode
rMWPROD MediaWiki Production
Attached
Detach File
Event Timeline
Log In to Comment