Page Menu
Home
WickedGov Phorge
Search
Configure Global Search
Log In
Files
F4084617
SsoHookHandler.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
9 KB
Referenced Files
None
Subscribers
None
SsoHookHandler.php
View Options
<?php
namespace
MediaWiki\Extension\CentralAuth\Hooks\Handlers
;
use
LogicException
;
use
MediaWiki\Api\Hook\ApiCheckCanExecuteHook
;
use
MediaWiki\Auth\AuthenticationResponse
;
use
MediaWiki\Auth\AuthManager
;
use
MediaWiki\Auth\Hook\AuthManagerFilterProvidersHook
;
use
MediaWiki\Auth\Hook\AuthManagerVerifyAuthenticationHook
;
use
MediaWiki\Auth\LocalPasswordPrimaryAuthenticationProvider
;
use
MediaWiki\Auth\TemporaryPasswordPrimaryAuthenticationProvider
;
use
MediaWiki\Context\RequestContext
;
use
MediaWiki\Extension\CentralAuth\CentralAuthRedirectingPrimaryAuthenticationProvider
;
use
MediaWiki\Extension\CentralAuth\FilteredRequestTracker
;
use
MediaWiki\Extension\CentralAuth\SharedDomainUtils
;
use
MediaWiki\Hook\GetLocalURLHook
;
use
MediaWiki\Hook\SetupAfterCacheHook
;
use
MediaWiki\Permissions\Hook\GetUserPermissionsErrorsHook
;
use
MediaWiki\ResourceLoader\Hook\ResourceLoaderModifyEmbeddedSourceUrlsHook
;
use
MediaWiki\User\UserIdentity
;
use
MediaWiki\Utils\UrlUtils
;
use
MediaWiki\WikiMap\WikiMap
;
use
MobileContext
;
use
MWExceptionHandler
;
use
Wikimedia\NormalizedException\NormalizedException
;
/**
* Ensure that the SSO domain cannot be used for anything that is unrelated to its purpose.
*/
class
SsoHookHandler
implements
GetLocalURLHook
,
SetupAfterCacheHook
,
GetUserPermissionsErrorsHook
,
ApiCheckCanExecuteHook
,
ResourceLoaderModifyEmbeddedSourceUrlsHook
,
AuthManagerFilterProvidersHook
,
AuthManagerVerifyAuthenticationHook
{
// Allowlists of things a user can do on the SSO domain.
// FIXME these should be configurable and/or come from extension attributes
// 'static' is WMF's custom static.php entry point, serving some files on the SSO domain (T374286)
private
const
ALLOWED_ENTRY_POINTS
=
[
'index'
,
'api'
,
'static'
];
private
const
ALLOWED_SPECIAL_PAGES
=
[
'Userlogin'
,
'Userlogout'
,
'CreateAccount'
,
'PasswordReset'
,
'Captcha'
];
private
const
ALLOWED_API_MODULES
=
[
// needed for allowing any query API, even if we only want meta modules; it can be
// used to check page existence (which is unwanted functionality on the SSO domain),
// which is unfortunate but permissions will still be checked, so it's not a risk.
'query'
,
// allow login/signup directly via the API + help for those APIs
'clientlogin'
,
'createaccount'
,
'authmanagerinfo'
,
'paraminfo'
,
'help'
,
// APIs used during web login
'validatepassword'
,
'userinfo'
,
'webauthn'
,
'fancycaptchareload'
,
// generic meta APIs, there's a good chance something somewhere will use them
'siteinfo'
,
'globaluserinfo'
,
'tokens'
,
];
// List of authentication providers which should be skipped on the local login page in
// SUL3 mode, because they will be done on the shared domain instead.
// This is somewhat fragile, e.g. in case of class renamespacing. We inherit that from
// AuthManager and can't do much about it. It fails in the safe direction, though - on
// provider key mismatch there will be unnecessary extra checks.
private
const
DISALLOWED_LOCAL_PROVIDERS
=
[
// FIXME what about preauth providers like AbuseFilter which don't generate a form
// but might prevent login and then the user ends up on a confusing login page?
// FIXME what about providers (if any) which generate a form but also do something else?
'preauth'
=>
[
'CaptchaPreAuthenticationProvider'
,
],
'primaryauth'
=>
[
TemporaryPasswordPrimaryAuthenticationProvider
::
class
,
LocalPasswordPrimaryAuthenticationProvider
::
class
,
],
'secondaryauth'
=>
[
'OATHSecondaryAuthenticationProvider'
,
],
];
private
UrlUtils
$urlUtils
;
private
FilteredRequestTracker
$filteredRequestTracker
;
private
SharedDomainUtils
$sharedDomainUtils
;
private
?
MobileContext
$mobileContext
;
public
function
__construct
(
UrlUtils
$urlUtils
,
FilteredRequestTracker
$filteredRequestTracker
,
SharedDomainUtils
$sharedDomainUtils
,
?
MobileContext
$mobileContext
=
null
)
{
$this
->
urlUtils
=
$urlUtils
;
$this
->
filteredRequestTracker
=
$filteredRequestTracker
;
$this
->
sharedDomainUtils
=
$sharedDomainUtils
;
$this
->
mobileContext
=
$mobileContext
;
}
/** @inheritDoc */
public
function
onSetupAfterCache
()
{
if
(
$this
->
sharedDomainUtils
->
shouldRestrictCurrentDomain
()
)
{
// FIXME The REST API does not provide a hook for disabling APIs. No rest APIs
// should be needed for login and signup so we can just throw unconditionally,
// but this should be improved in the future.
// FIXME should not log a production error
if
(
!
in_array
(
MW_ENTRY_POINT
,
self
::
ALLOWED_ENTRY_POINTS
,
true
)
)
{
throw
new
\RuntimeException
(
MW_ENTRY_POINT
.
' endpoint is not allowed on the SSO domain'
);
}
}
}
/** @inheritDoc */
public
function
onGetUserPermissionsErrors
(
$title
,
$user
,
$action
,
&
$result
)
{
if
(
$this
->
sharedDomainUtils
->
shouldRestrictCurrentDomain
()
)
{
if
(
!
$title
->
isSpecialPage
()
)
{
$result
=
wfMessage
(
'badaccess-group0'
);
return
false
;
}
foreach
(
self
::
ALLOWED_SPECIAL_PAGES
as
$name
)
{
if
(
$title
->
isSpecial
(
$name
)
)
{
return
true
;
}
}
$result
=
wfMessage
(
'badaccess-group0'
);
return
false
;
}
}
/** @inheritDoc */
public
function
onApiCheckCanExecute
(
$module
,
$user
,
&
$message
)
{
if
(
$this
->
sharedDomainUtils
->
shouldRestrictCurrentDomain
()
)
{
if
(
!
in_array
(
$module
->
getModuleName
(),
self
::
ALLOWED_API_MODULES
)
)
{
$message
=
[
'apierror-moduledisabled'
,
$module
->
getModuleName
()
];
return
false
;
}
}
}
/**
* @inheritDoc
* @phan-param array{local:string} $urls
*/
public
function
onResourceLoaderModifyEmbeddedSourceUrls
(
array
&
$urls
):
void
{
$local
=
$urls
[
'local'
];
$local
=
$this
->
urlUtils
->
expand
(
$local
,
PROTO_CURRENT
);
// reassure Phan that expand() won't return null
'@phan-var string $local'
;
if
(
$this
->
mobileContext
&&
$this
->
mobileContext
->
usingMobileDomain
()
)
{
$local
=
$this
->
mobileContext
->
getMobileUrl
(
$local
);
}
$urls
[
'local'
]
=
$local
;
}
/**
* If we are not on the shared domain and SUL3 is enabled, remove some authentication
* providers. They will run after the redirect on shared domain, so it's not necessary to
* run them locally, and on the local domain they would generate a login form, and we
* don't want that.
* @inheritDoc
* @note
*/
public
function
onAuthManagerFilterProviders
(
array
&
$providers
):
void
{
$request
=
RequestContext
::
getMain
()->
getRequest
();
if
(
$this
->
sharedDomainUtils
->
isSul3Enabled
(
$request
)
&&
!
$this
->
sharedDomainUtils
->
isSharedDomain
()
)
{
// We'll rely on CentralAuthSsoPreAuthenticationProvider to make sure filtering does not
// happen at the wrong time so make sure it's in place.
if
(
!
isset
(
$providers
[
'preauth'
][
'CentralAuthSsoPreAuthenticationProvider'
]
)
)
{
throw
new
LogicException
(
'CentralAuthSsoPreAuthenticationProvider not found during SUL3 login'
);
}
foreach
(
self
::
DISALLOWED_LOCAL_PROVIDERS
as
$stage
=>
$disallowedProviders
)
{
foreach
(
$disallowedProviders
as
$disallowedProvider
)
{
unset
(
$providers
[
$stage
][
$disallowedProvider
]
);
}
}
// This is security-critical code. If these providers are removed but some
// non-redirect-based login is still possible, or the providers are erroneously
// removed on the shared domain as well, that would circumvent important security
// checks. To prevent mistakes, we sync with the behavior of the
// AuthManagerVerifyAuthentication hook.
$this
->
filteredRequestTracker
->
markRequestAsFiltered
(
$request
);
}
}
/** @inheritDoc */
public
function
onAuthManagerVerifyAuthentication
(
?
UserIdentity
$user
,
AuthenticationResponse
&
$response
,
AuthManager
$authManager
,
array
$info
)
{
if
(
$this
->
filteredRequestTracker
->
isCurrentAuthenticationFlowFiltered
(
$authManager
)
&&
$info
[
'primaryId'
]
!==
CentralAuthRedirectingPrimaryAuthenticationProvider
::
class
)
{
// If providers were filtered, but then authentication wasn't handled by redirecting,
// report and interrupt.
MWExceptionHandler
::
logException
(
new
NormalizedException
(
'Providers were filtered but redirecting provider was not the primary'
,
[
'user'
=>
$user
->
getName
(),
'result'
=>
$response
->
status
,
]
+
$info
)
);
$response
=
AuthenticationResponse
::
newFail
(
wfMessage
(
'internalerror'
)
);
return
false
;
}
}
/** @inheritDoc */
public
function
onGetLocalURL
(
$title
,
&
$url
,
$query
)
{
if
(
$this
->
sharedDomainUtils
->
shouldRestrictCurrentDomain
()
)
{
// Only allow links to auth-related special pages on the SSO domain.
// Point all other links to the normal wiki domain.
foreach
(
self
::
ALLOWED_SPECIAL_PAGES
as
$name
)
{
if
(
$title
->
isSpecial
(
$name
)
)
{
return
;
}
}
if
(
$title
->
getInterwiki
()
!==
''
)
{
return
;
}
// WikiMap entry for the current wiki points to the normal wiki domain,
// even when $wgServer etc. were overridden for the SSO domain.
$currentWiki
=
WikiMap
::
getWiki
(
WikiMap
::
getCurrentWikiId
()
);
$url
=
wfAppendQuery
(
$currentWiki
->
getCanonicalUrl
(
$title
->
getPrefixedText
()
),
$query
);
if
(
$this
->
mobileContext
&&
$this
->
mobileContext
->
shouldDisplayMobileView
()
)
{
$url
=
$this
->
mobileContext
->
getMobileUrl
(
$url
);
}
}
}
}
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Aug 18 2026, 07:47 (7 w, 4 h ago)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
bc/76/92060a23a60bd88bdf2217e09106
Default Alt Text
SsoHookHandler.php (9 KB)
Attached To
Mode
rMWPROD MediaWiki Production
Attached
Detach File
Event Timeline
Log In to Comment