Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
7.54% covered (danger)
7.54%
15 / 199
3.85% covered (danger)
3.85%
1 / 26
CRAP
0.00% covered (danger)
0.00%
0 / 1
CentralAuthHooks
7.54% covered (danger)
7.54%
15 / 199
3.85% covered (danger)
3.85%
1 / 26
5528.66
0.00% covered (danger)
0.00%
0 / 1
 onRegistration
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
30
 onGetPreferences
0.00% covered (danger)
0.00%
0 / 41
0.00% covered (danger)
0.00%
0 / 1
72
 onSpecialPasswordResetOnSubmit
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
20
 getAuthIconHtml
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
42
 getAutoLoginWikis
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 isMobileDomain
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 onUserArrayFromResult
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 onUserGetEmail
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
 onUserGetEmailAuthenticationTimestamp
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 onInvalidateEmailComplete
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 onUserSetEmail
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 onUserSaveSettings
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
 onUserSetEmailAuthenticationTimestamp
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 onUserGetRights
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
4
 onUserIsLocked
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
30
 onUserIsBot
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
56
 onMakeGlobalVariablesScript
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
20
 getCentralautologinJsData
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
30
 getEdgeLoginHTML
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
20
 isUIReloadRecommended
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
12
 onTestCanonicalRedirect
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 onUserGetReservedNames
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 onApiQueryTokensRegisterTypes
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 onResourceLoaderForeignApiModules
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 onSessionCheckInfo
42.86% covered (danger)
42.86%
3 / 7
0.00% covered (danger)
0.00%
0 / 1
4.68
 onGetLogTypesOnUser
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 * http://www.gnu.org/copyleft/gpl.html
17 *
18 * @file
19 */
20
21namespace MediaWiki\Extension\CentralAuth;
22
23use CentralAuthSessionProvider;
24use ExtensionRegistry;
25use MediaWiki\Api\Hook\ApiQueryTokensRegisterTypesHook;
26use MediaWiki\Extension\CentralAuth\Hooks\CentralAuthHookRunner;
27use MediaWiki\Extension\CentralAuth\Hooks\Handlers\PageDisplayHookHandler;
28use MediaWiki\Extension\CentralAuth\Special\SpecialCentralAutoLogin;
29use MediaWiki\Extension\CentralAuth\User\CentralAuthUser;
30use MediaWiki\Extension\CentralAuth\User\CentralAuthUserArrayFromResult;
31use MediaWiki\Hook\GetLogTypesOnUserHook;
32use MediaWiki\Hook\MakeGlobalVariablesScriptHook;
33use MediaWiki\Hook\TestCanonicalRedirectHook;
34use MediaWiki\Html\Html;
35use MediaWiki\MediaWikiServices;
36use MediaWiki\Output\OutputPage;
37use MediaWiki\Permissions\Hook\UserGetRightsHook;
38use MediaWiki\Preferences\Hook\GetPreferencesHook;
39use MediaWiki\Request\ContentSecurityPolicy;
40use MediaWiki\Request\WebRequest;
41use MediaWiki\ResourceLoader as RL;
42use MediaWiki\ResourceLoader\Hook\ResourceLoaderForeignApiModulesHook;
43use MediaWiki\Session\CookieSessionProvider;
44use MediaWiki\Session\Hook\SessionCheckInfoHook;
45use MediaWiki\Session\SessionInfo;
46use MediaWiki\SpecialPage\SpecialPage;
47use MediaWiki\Title\Title;
48use MediaWiki\User\Hook\InvalidateEmailCompleteHook;
49use MediaWiki\User\Hook\SpecialPasswordResetOnSubmitHook;
50use MediaWiki\User\Hook\UserArrayFromResultHook;
51use MediaWiki\User\Hook\UserGetEmailAuthenticationTimestampHook;
52use MediaWiki\User\Hook\UserGetEmailHook;
53use MediaWiki\User\Hook\UserGetReservedNamesHook;
54use MediaWiki\User\Hook\UserIsBotHook;
55use MediaWiki\User\Hook\UserIsLockedHook;
56use MediaWiki\User\Hook\UserSaveSettingsHook;
57use MediaWiki\User\Hook\UserSetEmailAuthenticationTimestampHook;
58use MediaWiki\User\Hook\UserSetEmailHook;
59use MediaWiki\User\User;
60use MediaWiki\User\UserArrayFromResult;
61use MediaWiki\User\UserIdentity;
62use MediaWiki\WikiMap\WikiMap;
63use MobileContext;
64use OOUI\ButtonWidget;
65use OOUI\HorizontalLayout;
66use OOUI\IconWidget;
67use Wikimedia\Rdbms\IResultWrapper;
68
69class CentralAuthHooks implements
70    ApiQueryTokensRegisterTypesHook,
71    MakeGlobalVariablesScriptHook,
72    TestCanonicalRedirectHook,
73    UserGetRightsHook,
74    GetPreferencesHook,
75    ResourceLoaderForeignApiModulesHook,
76    SessionCheckInfoHook,
77    GetLogTypesOnUserHook,
78    InvalidateEmailCompleteHook,
79    SpecialPasswordResetOnSubmitHook,
80    UserArrayFromResultHook,
81    UserGetEmailAuthenticationTimestampHook,
82    UserGetEmailHook,
83    UserGetReservedNamesHook,
84    UserIsBotHook,
85    UserIsLockedHook,
86    UserSaveSettingsHook,
87    UserSetEmailAuthenticationTimestampHook,
88    UserSetEmailHook
89{
90
91    /**
92     * Called right after configuration variables have been set.
93     */
94    public static function onRegistration() {
95        global $wgCentralAuthDatabase, $wgDBname, $wgSessionProviders,
96            $wgCentralIdLookupProvider;
97
98        // Override $wgCentralAuthDatabase for Wikimedia Jenkins.
99        if ( defined( 'MW_QUIBBLE_CI' ) ) {
100            $wgCentralAuthDatabase = $wgDBname;
101        }
102
103        // CentralAuthSessionProvider is supposed to replace core
104        // CookieSessionProvider, so remove the latter if both are configured
105        if ( isset( $wgSessionProviders[CookieSessionProvider::class] ) &&
106            isset( $wgSessionProviders[CentralAuthSessionProvider::class] )
107        ) {
108            unset( $wgSessionProviders[CookieSessionProvider::class] );
109        }
110
111        // Assume they want CentralAuth as the default central ID provider, unless
112        // already configured otherwise.
113        if ( $wgCentralIdLookupProvider === 'local' ) {
114            $wgCentralIdLookupProvider = 'CentralAuth';
115        }
116    }
117
118    /**
119     * Add a little pretty to the preferences user info section
120     *
121     * @param User $user
122     * @param array &$preferences
123     * @return bool
124     */
125    public function onGetPreferences( $user, &$preferences ) {
126        // Possible states:
127        // - account not merged at all
128        // - global accounts exists, but this local account is unattached
129        // - this local account is attached, but migration incomplete
130        // - all local accounts are attached (no $message shown)
131
132        $global = CentralAuthUser::getInstance( $user );
133        $unattached = count( $global->listUnattached() );
134        if ( $global->exists() ) {
135            if ( $global->isAttached() && $unattached ) {
136                // Migration incomplete - unattached accounts at other wikis
137                $attached = count( $global->listAttached() );
138                $message = wfMessage( 'centralauth-prefs-unattached' )->parse() .
139                    '<br />' .
140                    wfMessage( 'centralauth-prefs-count-attached' )
141                        ->numParams( $attached )->parse() .
142                    '<br />' .
143                    wfMessage( 'centralauth-prefs-count-unattached' )
144                        ->numParams( $unattached )->parse();
145            } elseif ( !$global->isAttached() ) {
146                // Global account exists but the local account is not attached
147                $message = wfMessage( 'centralauth-prefs-detail-unattached' )->parse();
148            }
149        } else {
150            // No global account
151            $message = wfMessage( 'centralauth-prefs-not-managed' )->parse();
152        }
153
154        $manageButtons = [];
155
156        if ( $unattached && $user->isAllowed( 'centralauth-merge' ) ) {
157            // Add "Manage your global account" button
158            $manageButtons[] = new ButtonWidget( [
159                'href' => SpecialPage::getTitleFor( 'MergeAccount' )->getLinkURL(),
160                'label' => wfMessage( 'centralauth-prefs-manage' )->text(),
161            ] );
162        }
163
164        // Add "View your global account info" button
165        $manageButtons[] = new ButtonWidget( [
166            'href' => SpecialPage::getTitleFor( 'CentralAuth', $user->getName() )->getLinkURL(),
167            'label' => wfMessage( 'centralauth-prefs-view' )->text(),
168        ] );
169
170        $manageLinkList = (string)( new HorizontalLayout( [ 'items' => $manageButtons ] ) );
171
172        $preferences['globalaccountstatus'] = [
173            'section' => 'personal/info',
174            'label-message' => 'centralauth-prefs-status',
175            'type' => 'info',
176            'raw' => true,
177            'default' => $manageLinkList
178        ];
179
180        // Display a notice about the user account status with an alert icon
181        if ( isset( $message ) ) {
182            $messageIconWidget = (string)new IconWidget( [
183                'icon' => 'alert',
184                'flags' => [ 'destructive' ]
185            ] );
186            $preferences['globalaccountstatus']['default'] = $messageIconWidget
187                . "$message<br>$manageLinkList";
188        }
189
190        return true;
191    }
192
193    /**
194     * Show a nicer error when the user account does not exist on the local wiki, but
195     * does exist globally
196     * @param User[] &$users
197     * @param array $data
198     * @param string &$error
199     * @return bool
200     */
201    public function onSpecialPasswordResetOnSubmit( &$users, $data, &$error ) {
202        $firstUser = reset( $users );
203        if ( !( $firstUser instanceof UserIdentity ) ) {
204            // We can't handle this
205            return true;
206        }
207
208        if ( !$firstUser->getId() ) {
209            $centralUser = CentralAuthUser::getInstance( $firstUser );
210            if ( $centralUser->exists() ) {
211                $error = [ 'centralauth-account-exists-reset', $centralUser->getName() ];
212                return false;
213            }
214        }
215
216        return true;
217    }
218
219    /**
220     * Get the HTML for an <img> element used to perform edge login, autologin (no-JS), or central logout.
221     *
222     * @param string $wikiID Target wiki
223     * @param string $page Target page, should be a Special:CentralAutoLogin subpage
224     * @param array $params URL query parameters. Some also affect the generated HTML:
225     *   - 'type': when set to '1x1', generate an invisible pixel image, instead of a visible icon
226     *   - 'mobile': when set, use target wiki's mobile domain URL instead of canonical URL
227     * @param ContentSecurityPolicy|null $csp If provided, it will be modified to allow requests to
228     *   the target wiki. Otherwise, that must be done in 'ContentSecurityPolicyDefaultSource' hook.
229     * @return string HTML
230     */
231    public static function getAuthIconHtml(
232        string $wikiID, string $page, array $params, ?ContentSecurityPolicy $csp
233    ): string {
234        // Use WikiMap to avoid localization of the 'Special' namespace, see T56195.
235        $wiki = WikiMap::getWiki( $wikiID );
236        $url = wfAppendQuery(
237            $wiki->getCanonicalUrl( $page ),
238            $params
239        );
240        if ( isset( $params['mobile'] ) ) {
241            // Do autologin on the mobile domain for each wiki
242            $url = MobileContext::singleton()->getMobileUrl( $url );
243        }
244        if ( $csp ) {
245            $csp->addDefaultSrc( wfParseUrl( $url )['host'] );
246        }
247
248        $type = $params['type'];
249        return Html::element( 'img', [
250            'src' => $url,
251            'alt' => '',
252            'width' => $type === '1x1' ? 1 : 20,
253            'height' => $type === '1x1' ? 1 : 20,
254            'style' => $type === '1x1' ? 'border: none; position: absolute;' : 'border: 1px solid #ccc;',
255        ] );
256    }
257
258    /**
259     * Get autologin wikis, in the same format as $wgCentralAuthAutoLoginWikis, but with the
260     * current domain removed.
261     * @return string[]
262     */
263    public static function getAutoLoginWikis(): array {
264        global $wgServer, $wgCentralAuthAutoLoginWikis, $wgCentralAuthCookieDomain;
265        $autoLoginWikis = $wgCentralAuthAutoLoginWikis;
266        if ( $wgCentralAuthCookieDomain ) {
267            unset( $autoLoginWikis[$wgCentralAuthCookieDomain] );
268        } else {
269            $serverParts = MediaWikiServices::getInstance()->getUrlUtils()->parse( $wgServer );
270            // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
271            unset( $autoLoginWikis[ $serverParts['host'] ] );
272        }
273        return $autoLoginWikis;
274    }
275
276    /**
277     * @return bool
278     */
279    public static function isMobileDomain() {
280        return ExtensionRegistry::getInstance()->isLoaded( 'MobileFrontend' )
281            && MobileContext::singleton()->usingMobileDomain();
282    }
283
284    /**
285     * @param UserArrayFromResult|null &$userArray
286     * @param IResultWrapper $res
287     * @return bool
288     */
289    public function onUserArrayFromResult( &$userArray, $res ) {
290        $userArray = new CentralAuthUserArrayFromResult( $res );
291        return true;
292    }
293
294    /**
295     * @param User $user
296     * @param string &$email
297     * @return bool
298     */
299    public function onUserGetEmail( $user, &$email ) {
300        $ca = CentralAuthUser::getInstance( $user );
301        if ( $ca->isAttached() ) {
302            $email = $ca->getEmail();
303        }
304        return true;
305    }
306
307    /**
308     * @param User $user
309     * @param string|null &$timestamp
310     * @return bool
311     */
312    public function onUserGetEmailAuthenticationTimestamp( $user, &$timestamp ) {
313        $ca = CentralAuthUser::getInstance( $user );
314        if ( $ca->isAttached() ) {
315            if ( $ca->isLocked() ) {
316                // Locked users shouldn't be receiving email (T87559)
317                $timestamp = null;
318            } else {
319                $timestamp = $ca->getEmailAuthenticationTimestamp();
320            }
321        }
322        return true;
323    }
324
325    /**
326     * @param User $user
327     * @return bool
328     */
329    public function onInvalidateEmailComplete( $user ) {
330        $ca = CentralAuthUser::getPrimaryInstance( $user );
331        if ( $ca->isAttached() ) {
332            $ca->setEmail( '' );
333            $ca->setEmailAuthenticationTimestamp( null );
334            $ca->saveSettings();
335        }
336        return true;
337    }
338
339    /**
340     * @param User $user
341     * @param string &$email
342     * @return bool
343     */
344    public function onUserSetEmail( $user, &$email ) {
345        $ca = CentralAuthUser::getPrimaryInstance( $user );
346        if ( $ca->isAttached() ) {
347            $ca->setEmail( $email );
348            $ca->saveSettings();
349        }
350        return true;
351    }
352
353    /**
354     * @param User $user
355     * @return bool
356     */
357    public function onUserSaveSettings( $user ) {
358        $ca = CentralAuthUser::getPrimaryInstance( $user );
359        if ( $ca->isAttached() ) {
360            $ca->saveSettings();
361        }
362
363        return true;
364    }
365
366    /**
367     * @param User $user
368     * @param ?string &$timestamp
369     * @return bool
370     */
371    public function onUserSetEmailAuthenticationTimestamp( $user, &$timestamp ) {
372        $ca = CentralAuthUser::getInstance( $user );
373        if ( $ca->isAttached() ) {
374            $latestCa = CentralAuthUser::newPrimaryInstanceFromId( $ca->getId() );
375            if ( $latestCa->isAttached() ) {
376                $latestCa->setEmailAuthenticationTimestamp( $timestamp );
377                $latestCa->saveSettings();
378            }
379        }
380
381        return true;
382    }
383
384    /**
385     * @param User $user
386     * @param string[] &$rights
387     * @return bool
388     */
389    public function onUserGetRights( $user, &$rights ) {
390        if ( $user->isRegistered() ) {
391            $centralUser = CentralAuthUser::getInstance( $user );
392
393            if ( $centralUser->exists() && $centralUser->isAttached() ) {
394                $extraRights = $centralUser->getGlobalRights();
395
396                $rights = array_merge( $extraRights, $rights );
397            }
398        }
399
400        return true;
401    }
402
403    /**
404     * @param User $user
405     * @param bool &$isLocked
406     * @return bool
407     */
408    public function onUserIsLocked( $user, &$isLocked ) {
409        $centralUser = CentralAuthUser::getInstance( $user );
410        if ( $centralUser->exists()
411            && ( $centralUser->isAttached() || !$user->isRegistered() )
412            && $centralUser->isLocked()
413        ) {
414            $isLocked = true;
415            return false;
416        }
417
418        return true;
419    }
420
421    /**
422     * @param User $user
423     * @param bool &$isBot
424     * @return bool
425     */
426    public function onUserIsBot( $user, &$isBot ) {
427        // No need to check global groups if the user is already marked as a bot,
428        // and no global groups for unregistered user
429        if ( !$isBot && $user->isRegistered() ) {
430            $centralUser = CentralAuthUser::getInstance( $user );
431            if ( $centralUser->exists()
432                && $centralUser->isAttached()
433                && array_intersect( [ 'bot', 'global-bot' ], $centralUser->getGlobalGroups() )
434                && in_array( 'bot', $centralUser->getGlobalRights() )
435            ) {
436                $isBot = true;
437            }
438        }
439
440        return true;
441    }
442
443    /**
444     * @param array &$vars
445     * @param OutputPage $out
446     */
447    public function onMakeGlobalVariablesScript( &$vars, $out ): void {
448        $user = $out->getUser();
449        if ( $user->isRegistered() ) {
450            $centralUser = CentralAuthUser::getInstance( $user );
451            $vars['wgGlobalGroups'] = ( $centralUser->exists() && $centralUser->isAttached() )
452                ? $centralUser->getActiveGlobalGroups()
453                : [];
454        }
455    }
456
457    /**
458     * Data to be serialised as JSON for the 'ext.centralauth.centralautologin' module.
459     * @return array
460     */
461    public static function getCentralautologinJsData() {
462        global $wgCentralAuthLoginWiki;
463        $data = [];
464        if ( $wgCentralAuthLoginWiki && $wgCentralAuthLoginWiki !== WikiMap::getCurrentWikiId() ) {
465            $url = WikiMap::getForeignURL(
466                $wgCentralAuthLoginWiki, 'Special:CentralAutoLogin/checkLoggedIn'
467            );
468            if ( $url !== false ) {
469                $params = [
470                    'type' => 'script',
471                    'wikiid' => WikiMap::getCurrentWikiId(),
472                ];
473                if ( self::isMobileDomain() ) {
474                    $params['mobile'] = 1;
475                }
476                $data['checkLoggedInURL'] = wfAppendQuery( $url, $params );
477            }
478        }
479        return $data;
480    }
481
482    /**
483     * Get a HTML fragment that will trigger central autologin, i.e. try to log in the user on
484     * each of $wgCentralAuthAutoLoginWikis in the background by embedding invisible pixel images
485     * which point to Special:CentralAutoLogin on each of those wikis.
486     *
487     * It also calls Special:CentralAutoLogin/refreshCookies on the central wiki, to refresh
488     * central session cookies if needed (e.g. because the "remember me" setting changed).
489     *
490     * This is typically used on the next page view after a successful login (by setting the
491     * CentralAuthDoEdgeLogin session flag).
492     *
493     * @return string
494     *
495     * @see SpecialCentralAutoLogin
496     * @see PageDisplayHookHandler::onBeforePageDisplay()
497     */
498    public static function getEdgeLoginHTML() {
499        global $wgCentralAuthLoginWiki;
500
501        $html = '';
502
503        foreach ( self::getAutoLoginWikis() as $domain => $wikiID ) {
504            $params = [
505                'type' => '1x1',
506                'from' => WikiMap::getCurrentWikiId(),
507            ];
508            if ( self::isMobileDomain() ) {
509                $params['mobile'] = 1;
510            }
511            $html .= self::getAuthIconHtml( $wikiID, 'Special:CentralAutoLogin/start', $params, null );
512        }
513
514        if ( $wgCentralAuthLoginWiki ) {
515            $html .= self::getAuthIconHtml( $wgCentralAuthLoginWiki, 'Special:CentralAutoLogin/refreshCookies', [
516                'type' => '1x1',
517                'wikiid' => WikiMap::getCurrentWikiId(),
518            ], null );
519        }
520
521        return $html;
522    }
523
524    /**
525     * Check whether the user's preferences are such that a UI reload is
526     * recommended.
527     * @param User $user
528     * @return bool
529     */
530    public static function isUIReloadRecommended( User $user ) {
531        global $wgCentralAuthPrefsForUIReload;
532        $userFactory = MediaWikiServices::getInstance()->getUserFactory();
533        $userOptionsLookup = MediaWikiServices::getInstance()->getUserOptionsLookup();
534
535        foreach ( $wgCentralAuthPrefsForUIReload as $pref ) {
536            if (
537                $userOptionsLookup->getOption( $user, $pref ) !==
538                $userOptionsLookup->getDefaultOption( $pref, $userFactory->newAnonymous() )
539            ) {
540                return true;
541            }
542        }
543
544        $hookRunner = new CentralAuthHookRunner( MediaWikiServices::getInstance()->getHookContainer() );
545
546        $recommendReload = false;
547        $hookRunner->onCentralAuthIsUIReloadRecommended( $user, $recommendReload );
548        return $recommendReload;
549    }
550
551    /**
552     * Prevent "canonicalization" of Special:CentralAutoLogin to a localized
553     * Special namespace name. See T56195.
554     * @param WebRequest $request
555     * @param Title $title
556     * @param OutputPage $output
557     * @return bool
558     */
559    public function onTestCanonicalRedirect( $request, $title, $output ) {
560        return $title->getNamespace() !== NS_SPECIAL ||
561            !str_starts_with( $request->getVal( 'title', '' ), 'Special:CentralAutoLogin/' );
562    }
563
564    /**
565     * Handler for UserGetReservedNames
566     * @param array &$reservedUsernames
567     */
568    public function onUserGetReservedNames( &$reservedUsernames ) {
569        $reservedUsernames[] = 'Global rename script';
570    }
571
572    /**
573     * @param array &$salts
574     * @return bool
575     */
576    public function onApiQueryTokensRegisterTypes( &$salts ) {
577        $salts += [
578            'setglobalaccountstatus' => 'setglobalaccountstatus',
579            'deleteglobalaccount' => 'deleteglobalaccount',
580        ];
581        return true;
582    }
583
584    /**
585     * @param string[] &$dependencies
586     * @param RL\Context|null $context
587     * @return void
588     */
589    public function onResourceLoaderForeignApiModules(
590        &$dependencies,
591        $context = null
592    ): void {
593        $dependencies[] = 'ext.centralauth.ForeignApi';
594    }
595
596    /**
597     * Hook function to prevent logged-in sessions when a user is being
598     * renamed.
599     * @param string &$reason Failure reason to log
600     * @param SessionInfo $info
601     * @param WebRequest $request
602     * @param array|bool $metadata
603     * @param array|bool $data
604     * @return bool
605     */
606    public function onSessionCheckInfo(
607        &$reason,
608        $info,
609        $request,
610        $metadata,
611        $data
612    ) {
613        $name = $info->getUserInfo()->getName();
614        if ( $name !== null ) {
615            $centralUser = CentralAuthUser::getInstanceByName( $name );
616            if ( $centralUser->renameInProgress() ) {
617                $reason = 'CentralAuth rename in progress';
618                return false;
619            }
620        }
621        return true;
622    }
623
624    /**
625     * @param array &$types
626     */
627    public function onGetLogTypesOnUser( &$types ) {
628        $types[] = 'gblrights';
629    }
630}