Page MenuHomeWickedGov Phorge

SharedDomainUtils.php
No OneTemporary

Size
11 KB
Referenced Files
None
Subscribers
None

SharedDomainUtils.php

<?php
namespace MediaWiki\Extension\CentralAuth;
use MediaWiki\Config\Config;
use MediaWiki\Extension\CentralAuth\Config\CAMainConfigNames;
use MediaWiki\Extension\CentralAuth\Hooks\Handlers\SharedDomainHookHandler;
use MediaWiki\HookContainer\HookRunner;
use MediaWiki\MainConfigNames;
use MediaWiki\Request\WebRequest;
use MediaWiki\SpecialPage\SpecialPageFactory;
use MediaWiki\WikiMap\WikiMap;
use MobileContext;
use RuntimeException;
use Wikimedia\Assert\Assert;
/**
* Utilities for handling the shared domain name used for SUL3 login.
* This class is kept lightweight, so it can be used in early hooks.
*/
class SharedDomainUtils {
private Config $config;
private SpecialPageFactory $specialPageFactory;
private HookRunner $hookRunner;
private ?bool $isSharedDomain = null;
private ?MobileContext $mobileContext;
private bool $isApiRequest;
public function __construct(
Config $config,
SpecialPageFactory $specialPageFactory,
HookRunner $hookRunner,
?MobileContext $mobileContext,
bool $isApiRequest
) {
$this->config = $config;
$this->specialPageFactory = $specialPageFactory;
$this->hookRunner = $hookRunner;
$this->mobileContext = $mobileContext;
$this->isApiRequest = $isApiRequest;
}
/**
* Return the URL prefix for using the shared authentication domain. The server will
* handle a request to this URL mostly the same as a request to the specified wiki.
* The returned prefix is equivalent to $wgCanonicalServer in how it should be used to
* construct URLs.
*
* @param ?string $wikiId The ID of the wiki that should be used for configuration when the
* shared domain URL is processed. Defaults to the current wiki ID.
* @return string|null URL prefix for the shared authentication domain, without a trailing
* slash; or null if the shared domain is not configured.
*/
public function getSharedDomainPrefix( ?string $wikiId = null ): ?string {
$wikiId ??= WikiMap::getCurrentWikiId();
$sharedDomainCallback = $this->config->get( CAMainConfigNames::CentralAuthSharedDomainCallback );
return $sharedDomainCallback ? $sharedDomainCallback( $wikiId ) : null;
}
/**
* Whether the current request is to the shared domain used for SUL3 login.
*
* This assumes:
* - $wgCentralAuthSharedDomainCallback contains the shared domain.
* - $wgCanonicalServer is set in site configuration to the current domain
* (instead of the actual canonical domain) for requests to the shared domain.
*/
public function isSharedDomain(): bool {
if ( $this->isSharedDomain === null ) {
$sharedDomainPrefix = $this->getSharedDomainPrefix();
if ( !$sharedDomainPrefix ) {
$this->isSharedDomain = false;
} else {
$sharedDomain = parse_url( $sharedDomainPrefix, PHP_URL_HOST );
$currentDomain = parse_url(
$this->config->get( MainConfigNames::CanonicalServer ), PHP_URL_HOST
);
$this->isSharedDomain = $sharedDomain && $currentDomain === $sharedDomain;
}
}
return $this->isSharedDomain;
}
/**
* Whether the current request must deny non-auth actions.
*
* If $wgCentralAuthRestrictSharedDomain is enabled, then requests to the "fake"
* shared domain within $wgCentralAuthSharedDomainCallback must only be for authentication
* purposes. All non-authentication-related actions should be prevented.
*
* SUL3 login supports both using a dedicated login wiki for the domain where the central
* session cookies are stored, and a shared domain which serve any wiki (from a virtual
* subdirectory). In the latter case, we want to prevent non-authentication actions
* to prevent complications like cache splits. This flag differentiates between the two
* setups.
* @see SharedDomainHookHandler
*/
public function shouldRestrictCurrentDomain(): bool {
return $this->isSharedDomain() && $this->config->get( CAMainConfigNames::CentralAuthRestrictSharedDomain );
}
/**
* Whether the wiki supports SUL3 at all. This does not necessarily mean SUL3 is enabled on
* the wiki, but at a minimum, when this function returns true, it can be enabled for a given
* request by using the usesul3=1 URL parameter.
*/
public function canSul3BeEnabled(): bool {
return (bool)$this->config->get( CAMainConfigNames::CentralAuthSharedDomainCallback );
}
/**
* Whether SUL3 mode is enabled on this wiki and/or this request.
*
* In order to facilitate testing and rollout of SUL3 migration,
* this method provides mechanisms for testing the SUL3 feature.
*
* SUL3 mode is available if $wgCentralAuthSharedDomainCallback is configured.
* It's enabled or disabled by default using $wgCentralAuthEnableSul3,
* which can be overridden by the user by setting the 'usesul3'
* URL query parameter to "1" or "0" (or other fuzzy bool values).
*
* @param WebRequest $request
* @param bool $excludeApiRequest Whether to exclude this API request from the
* SUL3 mode.
*/
public function isSul3Enabled( WebRequest $request, bool $excludeApiRequest = true ): bool {
if ( !$this->canSul3BeEnabled() ) {
return false;
}
// T379816: The `clientlogin` API should still work in SUL3 mode as if
// we're in SUL2 mode regardless of whether SUL3 is enabled or not.
// There are some edge-cases handled below like:
// - edits coming from VisualEditor that will trigger CentralLogin
// via the action API. Shouldn't really happen because we don't
// have VE enabled for anon users in production today but let's
// handle these;
// - a user trying to authenticate (login/signup) with their permanent
// account with a temporary account session active.
// T384523, T383812: Users sometimes will try to authenticate (login/signup)
// with an existing temporary session active. When this happens, we want
// to still assume SUL2 mode rather than try to trigger SUL3 login flow.
// This can happen for mobile apps (iOS for example) users.
// T390751: There is a special case where we want to create an account on-behalf
// of another user and mail them the temp password. In this case, the user
// that is creating the account is authenticated (API or regular web request).
// So in all cases, where we're using the API, regardless of the user (temp,
// anon or named), we want to just disable SUL3 and fallback to SUL2.
if ( $this->isApiRequest && $excludeApiRequest && !$this->isSharedDomain() ) {
return false;
}
if ( $request->getCheck( 'usesul3' ) ) {
return $request->getFuzzyBool( 'usesul3' );
}
return (bool)$this->config->get( CAMainConfigNames::CentralAuthEnableSul3 );
}
/**
* Assert that the SUL3 mode is allowed.
*
* @return void
*/
public function assertSul3Enabled( WebRequest $request ) {
Assert::precondition(
$this->isSul3Enabled( $request ),
'SUL3 is not enabled. Set $wgCentralAuthEnableSul3 to boolean true.'
);
}
/**
* Assert that we're on the shared login domain.
*
* @return void
*/
public function assertIsSharedDomain() {
Assert::precondition(
$this->isSharedDomain(),
'This action is not allowed because the domain is not the shared login domain.'
);
}
/**
* Assert that we're not on the shared login domain.
*
* @return void
*/
public function assertIsNotSharedDomain() {
Assert::precondition(
!( $this->isSharedDomain() ),
'This action is not allowed because the domain is not the shared login domain.'
);
}
/**
* Get the login/signup URL on the shared domain in SUL3 mode. Should only be called during
* authentication.
*
* @note This utility method supports URLs for SUL3 mode only. It
* doesn't have any concept of SUL2 mode. For shared domain
* URLs that support SUL2 and SUL3, see get the generic method
* CentralDomainUtils::getCentralDomainURL().
*
* @param string $action 'login' or 'signup' action
* @param WebRequest $request There could be more to look at
* in the request like if we're coming from a campaign link.
*/
public function getUrlForSharedDomainAction( string $action, WebRequest $request ): string {
switch ( $action ) {
case 'login':
$localUrl = $this->specialPageFactory->getTitleForAlias( 'Userlogin' )->getLocalURL();
break;
case 'signup':
$localUrl = $this->specialPageFactory->getTitleForAlias( 'CreateAccount' )->getLocalURL();
break;
default:
throw new RuntimeException( 'Unknown action: ' . $action );
}
$sharedDomainPrefix = $this->getSharedDomainPrefix();
if ( !$sharedDomainPrefix ) {
throw new RuntimeException( 'SUL3 action used but $wgCentralAuthSharedDomainCallback not configured' );
}
$url = $this->makeUrlDeviceCompliant( $sharedDomainPrefix . $localUrl );
$params = [];
$this->hookRunner->onAuthPreserveQueryParams( $params, [ 'request' => $request ] );
// replicate the non-hook part of LoginSignupSpecialPage::getPreservedParams()
$params += [
'display' => $request->getRawVal( 'display' ),
'uselang' => $request->getRawVal( 'uselang' ),
'variant' => $request->getRawVal( 'variant' ),
'returnto' => $request->getRawVal( 'sul3-returnto' ) ?? $request->getRawVal( 'returnto' ),
'returntoquery' => $request->getRawVal( 'sul3-returntoquery' ) ?? $request->getRawVal( 'returntoquery' ),
'returntoanchor' => $request->getRawVal( 'sul3-returntoanchor' ) ?? $request->getRawVal( 'returntoanchor' ),
'redoLocalAuthentication' => $request->getRawVal( 'redoLocalAuthentication' ),
];
// already handled in makeUrlDeviceCompliant()
unset( $params['useformat'] );
// Make sure SUL3 opt-in state is preserved, e.g. in case of the user changing networks
$params['usesul3'] = '1';
// Skip the login form if the user is logged out on the local domain, but already logged in on the central
// domain. See LoginSignupSpecialPage: "In the case where the user is already logged in"...
if ( ( $params['returnto'] ?? '' ) === '' && ( $params['returntoquery'] ?? '' ) === ''
&& !$request->getSession()->getUser()->isNamed() && $action === 'login'
) {
// This is enough to pass the check in LoginSignupSpecialPage and make sure
// PostLoginRedirect is called even when the user is already logged in.
$params['returntoquery'] = '?';
// In the future maybe we'll want to use a more robust redirection mechanism instead of
// relying on PostLoginRedirect (see also T369467).
}
return wfAppendQuery( $url, $params );
}
/**
* @return bool True if on mobile device
*/
public function shouldUseMobile(): bool {
return $this->mobileContext && $this->mobileContext->shouldDisplayMobileView();
}
/**
* Check the URL and apply transformation based on the device
* that is currently looking at it. If mobile, apply the mobile
* transformation to the URL so we view the correct rendering.
*
* Get the mobile domain (m.) version of the URL if available
* configured (in that WMF is currently configured to have separate
* domain for mobile and desktop versions of sites) and we want that
* instead of just appending a `useformat` query parameter, if the
* domain is a mobile domain, just return it but if it's not, we
* detect that and append a `useformat` query param..
*/
public function makeUrlDeviceCompliant( string $url ): string {
// Assume either all or none of the wikis in the farm have MobileFrontend
if ( !$this->mobileContext ) {
return $url;
}
$mobileUrl = $this->mobileContext->getMobileUrl( $url );
// Some wikis don't have separate mobile and desktop versions at different URLs,
// in which case getMobileUrl() is a no-op.
$hasMobileUrl = ( $mobileUrl !== $url );
if ( $this->mobileContext->shouldDisplayMobileView() ) {
return $hasMobileUrl ? $mobileUrl : wfAppendQuery( $url, [ 'useformat' => 'mobile' ] );
} else {
// useformat=desktop is the default, and so we don't really need to set it,
// but we want to consider the possibility that the user has previously used
// the central domain and set it to mobile mode via a cookie. In that case,
// we want to prioritize the consistency of the current mode over that setting.
return $hasMobileUrl ? $url : wfAppendQuery( $url, [ 'useformat' => 'desktop' ] );
}
}
}

File Metadata

Mime Type
text/x-php
Expires
Aug 17 2026, 15:00 (10 w, 3 d ago)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
20/33/0cce32bd1f281178350964fc81f5
Default Alt Text
SharedDomainUtils.php (11 KB)

Event Timeline