jenkins-bot has submitted this change and it was merged.
Change subject: Update for AuthManager
......................................................................
Update for AuthManager
Creates a PrimaryAuthenticationProvider for actual authentication, and a
SecondaryAuthenticationProvider for checking renames and central locks
on all logins.
The loginwiki handshake is still done post-login via the existing hooks,
in no small part because trying to stick it into the login flow would
break every existing API client.
Bug: T110283
Change-Id: Ia60d92f9e08740e1f76a3744147b93a7636ae09e
---
M AntiSpoof/CentralAuthAntiSpoofHooks.php
M CentralAuth.php
M extension.json
M i18n/en.json
M i18n/qqq.json
M includes/CentralAuthHooks.php
M includes/CentralAuthPlugin.php
A includes/CentralAuthPreAuthManagerHooks.php
A includes/CentralAuthPrimaryAuthenticationProvider.php
A includes/CentralAuthSecondaryAuthenticationProvider.php
M includes/CentralAuthUser.php
M includes/CentralAuthUtils.php
M includes/LocalRenameJob/LocalUserMergeJob.php
M includes/specials/SpecialCentralLogin.php
M maintenance/createLocalAccount.php
M tests/phpunit/CentralAuthHooksUsingDatabaseTest.php
A tests/phpunit/CentralAuthPreAuthManagerHooksUsingDatabaseTest.php
17 files changed, 1,022 insertions(+), 340 deletions(-)
Approvals:
Gergő Tisza: Looks good to me, approved
jenkins-bot: Verified
diff --git a/AntiSpoof/CentralAuthAntiSpoofHooks.php
b/AntiSpoof/CentralAuthAntiSpoofHooks.php
index d4fa369..1a54f29 100644
--- a/AntiSpoof/CentralAuthAntiSpoofHooks.php
+++ b/AntiSpoof/CentralAuthAntiSpoofHooks.php
@@ -12,11 +12,26 @@
public static function asAbortNewAccountHook( $user, &$message ) {
global $wgAntiSpoofAccounts, $wgUser, $wgRequest;
- if ( !$wgAntiSpoofAccounts ) {
+ $status = self::testNewAccount( $user, $wgUser,
$wgAntiSpoofAccounts, $wgRequest->getCheck( 'wpIgnoreAntiSpoof' ) );
+ if ( !$status->isGood() ) {
+ $message = Status::wrap( $status
)->getMessage()->escaped();
+ }
+ return $status->isGood();
+ }
+
+ /**
+ * Test if an account is acceptable
+ * @param User $user
+ * @param User $creator
+ * @param bool $enable
+ * @param bool $override
+ * @return StatusValue
+ */
+ public static function testNewAccount( $user, $creator, $enable,
$override ) {
+ if ( !$enable ) {
$mode = 'LOGGING ';
$active = false;
- } elseif ( $wgRequest->getCheck( 'wpIgnoreAntiSpoof' ) &&
- $wgUser->isAllowed( 'override-antispoof' ) ) {
+ } elseif ( $override && $creator->isAllowed(
'override-antispoof' ) ) {
$mode = 'OVERRIDE ';
$active = false;
} else {
@@ -35,24 +50,25 @@
wfDebugLog( 'antispoof', "{$mode}CONFLICT new
account '$name' [$normalized] spoofs " . implode( ',', $conflicts ) );
if ( $active ) {
$numConflicts = count( $conflicts );
+
+ // This message pasting-together sucks.
$message = wfMessage(
'antispoof-conflict-top', $name )->numParams( $numConflicts )->escaped();
$message .= '<ul>';
foreach ( $conflicts as $simUser ) {
$message .= '<li>' . wfMessage(
'antispoof-conflict-item', $simUser )->escaped() . '</li>';
}
$message .= '</ul>' . wfMessage(
'antispoof-conflict-bottom' )->escaped();
- return false;
+ return StatusValue::newFatal( new
RawMessage( '$1', Message::rawParam( $message ) ) );
}
}
} else {
$error = $spoof->getError();
wfDebugLog( 'antispoof', "{$mode}ILLEGAL new account
'$name' $error" );
if ( $active ) {
- $message = wfMessage( 'antispoof-name-illegal',
$name, $error )->escaped();
- return false;
+ return StatusValue::newFatal(
'antispoof-name-illegal', $name, $error );
}
}
- return true;
+ return StatusValue::newGood();
}
/**
diff --git a/CentralAuth.php b/CentralAuth.php
index 6e3ffe6..8b6dde6 100644
--- a/CentralAuth.php
+++ b/CentralAuth.php
@@ -33,12 +33,6 @@
$wgCentralAuthDatabase = 'centralauth';
/**
- * If true, new account registrations will be registered globally if
- * the username hasn't been used elsewhere.
- */
-$wgCentralAuthAutoNew = false;
-
-/**
* If true, existing unattached accounts will be automatically migrated
* if possible at first login.
*
diff --git a/extension.json b/extension.json
index e8b2e73..c059f78 100644
--- a/extension.json
+++ b/extension.json
@@ -6,7 +6,7 @@
"license-name": "GPL-2.0",
"type": "specialpage",
"requires": {
- "MediaWiki": ">= 1.28.0"
+ "MediaWiki": ">= 1.27.0"
},
"GroupPermissions": {
"steward": {
@@ -170,8 +170,11 @@
"SpecialSulRenameWarning":
"includes/specials/SpecialSulRenameWarning.php",
"CentralAuthUser": "includes/CentralAuthUser.php",
"CentralAuthUtils": "includes/CentralAuthUtils.php",
+ "CentralAuthPrimaryAuthenticationProvider":
"includes/CentralAuthPrimaryAuthenticationProvider.php",
+ "CentralAuthSecondaryAuthenticationProvider":
"includes/CentralAuthSecondaryAuthenticationProvider.php",
"CentralAuthPlugin": "includes/CentralAuthPlugin.php",
"CentralAuthHooks": "includes/CentralAuthHooks.php",
+ "CentralAuthPreAuthManagerHooks":
"includes/CentralAuthPreAuthManagerHooks.php",
"CentralAuthSuppressUserJob": "includes/SuppressUserJob.php",
"CentralAuthCreateLocalAccountJob":
"includes/CreateLocalAccountJob.php",
"WikiSet": "includes/WikiSet.php",
@@ -319,12 +322,8 @@
},
"callback": "CentralAuthHooks::onRegistration",
"Hooks": {
- "AuthPluginSetup": "CentralAuthHooks::onAuthPluginSetup",
- "AddNewAccount": "CentralAuthHooks::onAddNewAccount",
+ "LocalUserCreated": "CentralAuthHooks::onLocalUserCreated",
"GetPreferences": "CentralAuthHooks::onGetPreferences",
- "AbortLogin": "CentralAuthHooks::onAbortLogin",
- "AbortNewAccount": "CentralAuthHooks::onAbortNewAccount",
- "AbortAutoAccount": "CentralAuthHooks::onAbortAutoAccount",
"UserLoginComplete": "CentralAuthHooks::onUserLoginComplete",
"UserLogout": "CentralAuthHooks::onUserLogout",
"UserLogoutComplete": "CentralAuthHooks::onUserLogoutComplete",
@@ -373,7 +372,6 @@
"config": {
"@doc": "see CentralAuth.php",
"CentralAuthDatabase": "centralauth",
- "CentralAuthAutoNew": false,
"CentralAuthAutoMigrate": false,
"CentralAuthAutoMigrateNonGlobalAccounts": false,
"CentralAuthStrict": false,
diff --git a/i18n/en.json b/i18n/en.json
index b760439..02904bd 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -495,6 +495,7 @@
"sulrenamewarning-title" : "{{GENDER:$1|Your}} account has been
renamed",
"sulrenamewarning-renamed" : "{{GENDER:$2|Your}} account '''$1''' has
been renamed to '''$2''' during automatic migration to the unified login system
of {{int:Centralauth-groupname}}.",
"sulrenamewarning-usenew" : "Please use {{GENDER:$1|your}} new username
'''$1''' when logging in in the future.",
+ "sulrenamewarning-authmanager-ok-help": "Continue after displaying the
rename notification.",
"globalrenamequeue-request-reason-label": "Public log entry comment (if
accepting)",
"apihelp-centralauthtoken-description": "Fetch a centralauthtoken for
making an authenticated request to an attached wiki.",
"apihelp-centralauthtoken-example-1": "Fetch a centralauthtoken",
diff --git a/i18n/qqq.json b/i18n/qqq.json
index 2d2476b..ce3ec17 100644
--- a/i18n/qqq.json
+++ b/i18n/qqq.json
@@ -511,6 +511,7 @@
"sulrenamewarning-title": "[[Special:SulRenameWarning]] title\n\n* $1
is the user's new name for GENDER",
"sulrenamewarning-renamed": "Notice to user that their account has been
renamed.\n\n* $1 is the user's old name\n* $2 is the user's new name",
"sulrenamewarning-usenew": "Instructions to user to login with new name
in the future\n\n* $1 is the user's new name",
+ "sulrenamewarning-authmanager-ok-help": "Description of the \"ok\"
field when displaying the rename notification during AuthManager
authentication.",
"globalrenamequeue-request-reason-label": "Label for field that will be
used as the log comment in the public rename log",
"apihelp-centralauthtoken-description":
"{{doc-apihelp-description|centralauthtoken}}",
"apihelp-centralauthtoken-example-1":
"{{doc-apihelp-example|centralauthtoken}}",
diff --git a/includes/CentralAuthHooks.php b/includes/CentralAuthHooks.php
index 76e3352..27f5708 100644
--- a/includes/CentralAuthHooks.php
+++ b/includes/CentralAuthHooks.php
@@ -9,7 +9,8 @@
global $wgWikimediaJenkinsCI, $wgCentralAuthDatabase, $wgDBname,
$wgHooks, $wgSpecialPages, $wgSessionProviders,
$wgCentralIdLookupProvider,
$wgOverrideCentralIdLookupProvider,
- $wgCentralAuthCheckSULMigration;
+ $wgCentralAuthCheckSULMigration, $wgDisableAuthManager,
+ $wgAuthManagerAutoConfig;
// Override $wgCentralAuthDatabase for Wikimedia Jenkins.
if( isset( $wgWikimediaJenkinsCI ) && $wgWikimediaJenkinsCI ) {
@@ -30,11 +31,35 @@
$wgCentralIdLookupProvider = 'CentralAuth';
}
- if ( $wgCentralAuthCheckSULMigration ) {
- // Install hidden special page for renamed users
- $wgSpecialPages['SulRenameWarning'] =
'SpecialSulRenameWarning';
- $wgHooks['PostLoginRedirect'][] =
'CentralAuthHooks::onPostLoginRedirect';
- $wgHooks['LoginUserMigrated'][] =
'CentralAuthHooks::onLoginUserMigrated';
+ // AuthManager
+ if ( class_exists( MediaWiki\Auth\AuthManager::class ) &&
empty( $wgDisableAuthManager ) ) {
+ $wgAuthManagerAutoConfig['primaryauth'] += [
+ CentralAuthPrimaryAuthenticationProvider::class
=> [
+ 'class' =>
CentralAuthPrimaryAuthenticationProvider::class,
+ 'sort' => 10, // After
TemporaryPassword, before LocalPassword.
+ ]
+ ];
+ $wgAuthManagerAutoConfig['secondaryauth'] += [
+
CentralAuthSecondaryAuthenticationProvider::class => [
+ 'class' =>
CentralAuthSecondaryAuthenticationProvider::class,
+ 'sort' => 0, // non-UI secondaries
should run early
+ ]
+ ];
+ } else {
+ $wgHooks['AuthPluginSetup'][] =
'CentralAuthPreAuthManagerHooks::onAuthPluginSetup';
+ $wgHooks['AbortLogin'][] =
'CentralAuthPreAuthManagerHooks::onAbortLogin';
+ $wgHooks['AbortNewAccount'][] =
'CentralAuthPreAuthManagerHooks::onAbortNewAccount';
+ $wgHooks['AbortAutoAccount'][] =
'CentralAuthPreAuthManagerHooks::onAbortAutoAccount';
+ $wgHooks['UserLoginComplete'][] =
'CentralAuthPreAuthManagerHooks::onUserLoginComplete';
+ $wgHooks['UserLogout'][] =
'CentralAuthPreAuthManagerHooks::onUserLogout';
+ $wgHooks['SpecialPage_initList'][] =
'CentralAuthPreAuthManagerHooks::onSpecialPage_initList';
+
+ if ( $wgCentralAuthCheckSULMigration ) {
+ // Install hidden special page for renamed users
+ $wgSpecialPages['SulRenameWarning'] =
'SpecialSulRenameWarning';
+ $wgHooks['PostLoginRedirect'][] =
'CentralAuthHooks::onPostLoginRedirect';
+ $wgHooks['LoginUserMigrated'][] =
'CentralAuthHooks::onLoginUserMigrated';
+ }
}
}
@@ -48,7 +73,7 @@
global $wgAutoloadClasses, $wgExtensionCredits, $wgHooks;
global $wgSpecialPages, $wgResourceModules;
global $wgCentralAuthEnableGlobalRenameRequest;
- global $wgCentralAuthCheckSULMigration;
+ global $wgDisableAuthManager;
$caBase = __DIR__ . '/..';
if ( class_exists( 'RenameuserSQL' ) ) {
@@ -79,9 +104,11 @@
$wgAutoloadClasses['CentralAuthAntiSpoofHooks'] =
"$caBase/AntiSpoof/CentralAuthAntiSpoofHooks.php";
- $wgHooks['AbortNewAccount'][] =
-
'CentralAuthAntiSpoofHooks::asAbortNewAccountHook';
- $wgHooks['AddNewAccount'][] =
+ if ( !class_exists( MediaWiki\Auth\AuthManager::class )
|| !empty( $wgDisableAuthManager ) ) {
+ $wgHooks['AbortNewAccount'][] =
+
'CentralAuthAntiSpoofHooks::asAbortNewAccountHook';
+ }
+ $wgHooks['LocalUserCreated'][] =
'CentralAuthAntiSpoofHooks::asAddNewAccountHook';
$wgHooks['RenameUserComplete'][] =
'CentralAuthAntiSpoofHooks::asAddRenameUserHook';
@@ -227,25 +254,12 @@
* @return bool
*/
public static function onSpecialPage_initList( &$list ) {
- global $wgCentralAuthEnableGlobalRenameRequest,
$wgCentralAuthCheckSULMigration;
+ global $wgCentralAuthEnableGlobalRenameRequest;
if ( $wgCentralAuthEnableGlobalRenameRequest ) {
$list['GlobalRenameRequest'] =
'SpecialGlobalRenameRequest';
$list['GlobalRenameQueue'] = 'SpecialGlobalRenameQueue';
}
- if ( $wgCentralAuthCheckSULMigration ) {
- $list['SulRenameWarning'] = 'SpecialSulRenameWarning';
- }
-
- return true;
- }
-
- /**
- * @param $auth
- * @return bool
- */
- static function onAuthPluginSetup( &$auth ) {
- $auth = new CentralAuthPlugin;
return true;
}
@@ -253,12 +267,24 @@
* Make sure migration information in localuser table is populated
* on local account creation
* @param $user User
- * @param $byEmail bool
+ * @param $autocreated bool
* @return bool
*/
- static function onAddNewAccount( $user, $byEmail ) {
- $central = CentralAuthUser::getMasterInstance( $user );
- $central->addLocalName( wfWikiID() );
+ static function onLocalUserCreated( $user, $autocreated ) {
+ $centralUser = CentralAuthUser::getMasterInstance( $user );
+
+ // If some other AuthManager PrimaryAuthenticationProvider is
creating
+ // the user, we should still create a central user for them so
+ // CentralAuthIdLookup can have an ID for this new user right
away.
+ if ( !$centralUser->exists() && !$centralUser->listUnattached()
) {
+ if ( $centralUser->register( null, $user->getEmail() )
) {
+ $centralUser->attach( wfWikiID(), 'new' );
+ CentralAuthUtils::scheduleCreationJobs(
$centralUser );
+ }
+ }
+
+ $centralUser->addLocalName( wfWikiID() );
+
return true;
}
@@ -323,144 +349,6 @@
}
/**
- * @param $user User
- * @param $abortError
- * @return bool
- */
- static function onAbortNewAccount( User $user, &$abortError ) {
- global $wgCentralAuthPreventUnattached;
- global $wgCentralAuthEnableGlobalRenameRequest;
-
- $centralUser = CentralAuthUser::getMasterInstance( $user );
- if ( $centralUser->exists() ||
$centralUser->renameInProgressOn( wfWikiID() ) ) {
- $abortError = wfMessage( 'centralauth-account-exists'
)->text();
- return false;
- }
-
- if ( $wgCentralAuthPreventUnattached &&
$centralUser->listUnattached() ) {
- // If no global account exists and there are unattached
accounts,
- // don't let a new unattached account get created.
We'll pretend
- // that the name is already taken, because someone will
eventually
- // get it. See bug 67901.
- $abortError = wfMessage(
'centralauth-account-unattached-exists' )->text();
- return false;
- }
-
- // Block account creation if name is a pending rename request
- if ( $wgCentralAuthEnableGlobalRenameRequest &&
- GlobalRenameRequest::nameHasPendingRequest(
$user->getName() )
- ) {
- $abortError = wfMessage(
'centralauth-account-rename-exists' )->text();
- return false;
- }
-
- return true;
- }
-
- /**
- * Prevent accounts from being autocreated if a rename is in progress
- * @param User $user
- * @param $msg
- * @return bool
- */
- static function onAbortAutoAccount( User $user, &$msg ) {
- $caUser = CentralAuthUser::getInstance( $user );
- if ( $caUser->renameInProgress() ) {
- $msg = 'centralauth-rename-abortlogin';
- return false;
- }
-
- return true;
- }
-
- /**
- * @param User $user
- * @param string $pass
- * @param integer &$retval
- * @param string &$msg
- * @return bool
- * @throws Exception
- */
- static function onAbortLogin( User $user, $pass, &$retval, &$msg ) {
- $centralUser = CentralAuthUser::getMasterInstance( $user );
-
- // Since logins are rare, check the actual DB
- $rename = $centralUser->renameInProgressOn( wfWikiID() );
- if ( $rename ) {
- // This wiki specifically has a rename in progress, so
always abort
- $retval = LoginForm::NOT_EXISTS;
- $msg = 'centralauth-rename-abortlogin';
- return false;
- }
-
- // Now check if the user is the target of a rename anywhere
- $rename = $centralUser->renameInProgress();
- if ( $rename ) {
- // It's possible a user is being renamed but someone
else with
- // an unattached account is trying to login. Since
we've already
- // moved everything over to the new account name, we
only need
- // to check one username.
- $newCAUser = $rename[1] === $user->getName()
- ? $centralUser
- : CentralAuthUser::getInstanceByName(
$rename[0] );
-
- if ( $newCAUser->isAttached() ) {
- // If there is an account using that name that
exists on this wiki
- // reject the login.
- $retval = LoginForm::NOT_EXISTS;
- // This is an icky hack so the message is
->parse()d and
- // gets the username as a parameter
- $msg = 'centralauth-rename-abortlogin';
- return false;
- }
- }
-
- switch ( (string)$centralUser->canAuthenticate() ) {
- case '1': // boolean true
- return true;
-
- case 'no user':
- // If they're local, we still want to let them
log in. And if they
- // don't exist, this hook wouldn't have even
been called.
- return true;
-
- case 'locked':
- $msg = 'centralauth-login-error-locked';
- $retval = LoginForm::USER_BLOCKED;
- return false;
-
- default:
- throw new Exception( "Unexpected result from
CentralAuthUser::canAuthenticate()" );
- }
- }
-
- /**
- * Inform a user that their username was renamed as part of SUL
- * Finalization, if their previous username doesn't exist any more
(winner
- * was renamed).
- * @param User $user
- * @param string &$msg return error key, or return an array with key
and params
- * @return bool
- */
- public static function onLoginUserMigrated( $user, &$msg ) {
- global $wgCentralAuthCheckSULMigration;
- if ( $wgCentralAuthCheckSULMigration ) {
- $centralUser = CentralAuthUser::getInstance( $user );
- if ( $user->getID() === 0 && !$centralUser->exists() ) {
- // If the local and global accounts don't exist,
- // otherwise wgAuth will handle those.
- $testName = $user->getName() . '~' . wfWikiID();
- $test = CentralAuthUser::getInstanceByName(
$testName );
- if ( $test->exists() && $test->isAttached() ) {
- $msg = array(
'centralauth-abortlogin-renamed', $testName );
- return false;
- }
- }
- }
- return true;
- }
-
- /**
* Show a nicer error when the user account does not exist on the local
wiki, but
* does exist globally
* @param $users User[]
@@ -493,17 +381,6 @@
*/
static function onUserLoginComplete( &$user, &$inject_html ) {
global $wgCentralAuthCookies;
- global $wgCentralAuthCheckSULMigration;
-
- if ( $wgCentralAuthCheckSULMigration &&
- isset( $user->sulRenamed ) &&
- $user->sulRenamed === true
- ) {
- // Add a flag in the user's session to track that they
authenticated
- // with a pre-migration username.
- $request = $user->getRequest();
- $request->setSessionData( 'CentralAuthForcedRename',
true );
- }
if ( !$wgCentralAuthCookies ) {
// Use local sessions only.
@@ -713,9 +590,6 @@
$centralUser->resetAuthToken();
}
} );
-
- // Clean up any possible forced rename markers
- $user->getRequest()->setSessionData( 'CentralAuthForcedRename',
null );
return true;
}
@@ -1413,35 +1287,6 @@
'setglobalaccountstatus' => 'setglobalaccountstatus',
'deleteglobalaccount' => 'deleteglobalaccount',
);
- return true;
- }
-
- /**
- * Handler for PostLoginRedirect
- * @param string $returnTo The page to return to
- * @param array $returnToQuery Url parameters
- * @param string $type Type of login redirect
- * @return bool
- */
- public static function onPostLoginRedirect(
- &$returnTo, &$returnToQuery, &$type
- ) {
- global $wgCentralAuthCheckSULMigration, $wgUser;
- if ( $wgCentralAuthCheckSULMigration &&
- $wgUser->getRequest()->getSessionData(
'CentralAuthForcedRename' ) === true &&
- ( $type == 'success' || $type == 'successredirect' )
- ) {
- wfDebugLog( 'SUL', 'Redirecting user to
Special:SulRenameWarning' );
- // Store current redirect target in session so we can
provide a link
- // later.
- $wgUser->getRequest()->setSessionData(
'SulRenameWarning', array(
- 'returnTo' => $returnTo,
- 'returnToQuery' => $returnToQuery,
- ) );
- $returnTo = SpecialPageFactory::getLocalNameFor(
'Special:SulRenameWarning' );
- $returnToQuery = array();
- return false;
- }
return true;
}
diff --git a/includes/CentralAuthPlugin.php b/includes/CentralAuthPlugin.php
index 37dff76..7f9e9e0 100644
--- a/includes/CentralAuthPlugin.php
+++ b/includes/CentralAuthPlugin.php
@@ -7,6 +7,9 @@
* central password or settings.
*/
+/**
+ * @deprecated
+ */
class CentralAuthPlugin extends AuthPlugin {
/**
@@ -297,25 +300,22 @@
* @return bool
*/
public function addUser( $user, $password, $email = '', $realname = ''
) {
- global $wgCentralAuthAutoNew;
- if ( $wgCentralAuthAutoNew ) {
- $central = CentralAuthUser::getMasterInstance( $user );
- if ( !$central->exists() && !$central->listUnattached()
) {
- // Username is unused; set up as a global
account
- // @fixme is this even vaguely reliable? pah
- $ok = $central->register( $password, $email );
- if ( $ok ) {
- $central->attach( wfWikiID(), 'new' );
- $this->autoCreateAccounts( $central );
- } else {
- return false;
- }
+ $central = CentralAuthUser::getMasterInstance( $user );
+ if ( !$central->exists() && !$central->listUnattached() ) {
+ // Username is unused; set up as a global account
+ // @fixme is this even vaguely reliable? pah
+ $ok = $central->register( $password, $email );
+ if ( $ok ) {
+ $central->attach( wfWikiID(), 'new' );
+ CentralAuthUtils::scheduleCreationJobs(
$central );
+ } else {
+ return false;
}
- // Note: If $wgCentralAuthPreventUnattached is enabled,
- // accounts where a global does not exist, but there are
- // unattached accounts will have been denied creation in
- // the AbortNewAccount hook.
}
+ // Note: If $wgCentralAuthPreventUnattached is enabled,
+ // accounts where a global does not exist, but there are
+ // unattached accounts will have been denied creation in
+ // the AbortNewAccount hook.
return true;
}
@@ -367,27 +367,4 @@
return CentralAuthUser::getInstance( $user );
}
- /**
- * Sets up jobs to create and attach a local account for the given user
on every wiki listed in
- * $wgCentralAuthAutoCreateWikis.
- * @param CentralAuthUser $centralUser
- */
- private function autoCreateAccounts( CentralAuthUser $centralUser ) {
- global $wgCentralAuthAutoCreateWikis;
-
- $name = $centralUser->getName();
- $thisWiki = wfWikiID();
- $session = RequestContext::getMain()->exportSession();
- foreach ( $wgCentralAuthAutoCreateWikis as $wiki ) {
- if ( $wiki === $thisWiki ) {
- continue;
- }
- $job = Job::factory(
- 'CentralAuthCreateLocalAccountJob',
- Title::makeTitleSafe( NS_USER, $name ),
- array( 'name' => $name, 'from' => $thisWiki,
'session' => $session )
- );
- JobQueueGroup::singleton( $wiki )->push( $job );
- }
- }
}
diff --git a/includes/CentralAuthPreAuthManagerHooks.php
b/includes/CentralAuthPreAuthManagerHooks.php
new file mode 100644
index 0000000..ccc8de5
--- /dev/null
+++ b/includes/CentralAuthPreAuthManagerHooks.php
@@ -0,0 +1,235 @@
+<?php
+
+/**
+ * This holds hooks needed for pre-AuthManager versions of MediaWiki (or when
+ * $wgDisableAuthManager is set). Once we drop support for those versions, this
+ * may be removed.
+ * @deprecated
+ */
+class CentralAuthPreAuthManagerHooks {
+
+ /**
+ * This hook is used in cases where SpecialPageFactory::getPageList()
is called before
+ * $wgExtensionFunctions are run, which happens when E:ShortUrl is
installed.
+ *
+ * @param array $list
+ * @return bool
+ */
+ public static function onSpecialPage_initList( &$list ) {
+ global $wgCentralAuthCheckSULMigration;
+
+ if ( $wgCentralAuthCheckSULMigration ) {
+ $list['SulRenameWarning'] = 'SpecialSulRenameWarning';
+ }
+
+ return true;
+ }
+
+ /**
+ * @param $auth
+ * @return bool
+ */
+ static function onAuthPluginSetup( &$auth ) {
+ $auth = new CentralAuthPlugin;
+ return true;
+ }
+
+ /**
+ * @param $user User
+ * @param $abortError
+ * @return bool
+ */
+ static function onAbortNewAccount( User $user, &$abortError ) {
+ global $wgCentralAuthPreventUnattached;
+ global $wgCentralAuthEnableGlobalRenameRequest;
+
+ $centralUser = CentralAuthUser::getMasterInstance( $user );
+ if ( $centralUser->exists() ||
$centralUser->renameInProgressOn( wfWikiID() ) ) {
+ $abortError = wfMessage( 'centralauth-account-exists'
)->text();
+ return false;
+ }
+
+ if ( $wgCentralAuthPreventUnattached &&
$centralUser->listUnattached() ) {
+ // If no global account exists and there are unattached
accounts,
+ // don't let a new unattached account get created.
We'll pretend
+ // that the name is already taken, because someone will
eventually
+ // get it. See bug 67901.
+ $abortError = wfMessage(
'centralauth-account-unattached-exists' )->text();
+ return false;
+ }
+
+ // Block account creation if name is a pending rename request
+ if ( $wgCentralAuthEnableGlobalRenameRequest &&
+ GlobalRenameRequest::nameHasPendingRequest(
$user->getName() )
+ ) {
+ $abortError = wfMessage(
'centralauth-account-rename-exists' )->text();
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Prevent accounts from being autocreated if a rename is in progress
+ * @param User $user
+ * @param $msg
+ * @return bool
+ */
+ static function onAbortAutoAccount( User $user, &$msg ) {
+ $caUser = CentralAuthUser::getInstance( $user );
+ if ( $caUser->renameInProgress() ) {
+ $msg = 'centralauth-rename-abortlogin';
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * @param User $user
+ * @param string $pass
+ * @param integer &$retval
+ * @param string &$msg
+ * @return bool
+ * @throws Exception
+ */
+ static function onAbortLogin( User $user, $pass, &$retval, &$msg ) {
+ $centralUser = CentralAuthUser::getMasterInstance( $user );
+
+ // Since logins are rare, check the actual DB
+ $rename = $centralUser->renameInProgressOn( wfWikiID() );
+ if ( $rename ) {
+ // This wiki specifically has a rename in progress, so
always abort
+ $retval = LoginForm::NOT_EXISTS;
+ $msg = 'centralauth-rename-abortlogin';
+ return false;
+ }
+
+ // Now check if the user is the target of a rename anywhere
+ $rename = $centralUser->renameInProgress();
+ if ( $rename ) {
+ // It's possible a user is being renamed but someone
else with
+ // an unattached account is trying to login. Since
we've already
+ // moved everything over to the new account name, we
only need
+ // to check one username.
+ $newCAUser = $rename[1] === $user->getName()
+ ? $centralUser
+ : CentralAuthUser::getInstanceByName(
$rename[0] );
+
+ if ( $newCAUser->isAttached() ) {
+ // If there is an account using that name that
exists on this wiki
+ // reject the login.
+ $retval = LoginForm::NOT_EXISTS;
+ // This is an icky hack so the message is
->parse()d and
+ // gets the username as a parameter
+ $msg = 'centralauth-rename-abortlogin';
+ return false;
+ }
+ }
+
+ switch ( (string)$centralUser->canAuthenticate() ) {
+ case '1': // boolean true
+ return true;
+
+ case 'no user':
+ // If they're local, we still want to let them
log in. And if they
+ // don't exist, this hook wouldn't have even
been called.
+ return true;
+
+ case 'locked':
+ $msg = 'centralauth-login-error-locked';
+ $retval = LoginForm::USER_BLOCKED;
+ return false;
+
+ default:
+ throw new Exception( "Unexpected result from
CentralAuthUser::canAuthenticate()" );
+ }
+ }
+
+ /**
+ * Inform a user that their username was renamed as part of SUL
+ * Finalization, if their previous username doesn't exist any more
(winner
+ * was renamed).
+ * @param User $user
+ * @param string &$msg return error key, or return an array with key
and params
+ * @return bool
+ */
+ public static function onLoginUserMigrated( $user, &$msg ) {
+ global $wgCentralAuthCheckSULMigration;
+ if ( $wgCentralAuthCheckSULMigration ) {
+ $centralUser = CentralAuthUser::getInstance( $user );
+ if ( $user->getID() === 0 && !$centralUser->exists() ) {
+ // If the local and global accounts don't exist,
+ // otherwise wgAuth will handle those.
+ $testName = $user->getName() . '~' . wfWikiID();
+ $test = CentralAuthUser::getInstanceByName(
$testName );
+ if ( $test->exists() && $test->isAttached() ) {
+ $msg = array(
'centralauth-abortlogin-renamed', $testName );
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * @param $user User
+ * @param $inject_html string
+ * @return bool
+ */
+ static function onUserLoginComplete( &$user, &$inject_html ) {
+ global $wgCentralAuthCheckSULMigration;
+
+ if ( $wgCentralAuthCheckSULMigration &&
+ isset( $user->sulRenamed ) &&
+ $user->sulRenamed === true
+ ) {
+ // Add a flag in the user's session to track that they
authenticated
+ // with a pre-migration username.
+ $request = $user->getRequest();
+ $request->setSessionData( 'CentralAuthForcedRename',
true );
+ }
+
+ return true;
+ }
+
+ /**
+ * @param $user User
+ * @return bool
+ */
+ static function onUserLogout( &$user ) {
+ // Clean up any possible forced rename markers
+ $user->getRequest()->setSessionData( 'CentralAuthForcedRename',
null );
+
+ return true;
+ }
+
+ /**
+ * Handler for PostLoginRedirect
+ * @param string $returnTo The page to return to
+ * @param array $returnToQuery Url parameters
+ * @param string $type Type of login redirect
+ * @return bool
+ */
+ public static function onPostLoginRedirect(
+ &$returnTo, &$returnToQuery, &$type
+ ) {
+ global $wgCentralAuthCheckSULMigration, $wgUser;
+ if ( $wgCentralAuthCheckSULMigration &&
+ $wgUser->getRequest()->getSessionData(
'CentralAuthForcedRename' ) === true &&
+ ( $type == 'success' || $type == 'successredirect' )
+ ) {
+ wfDebugLog( 'SUL', 'Redirecting user to
Special:SulRenameWarning' );
+ // Store current redirect target in session so we can
provide a link
+ // later.
+ $wgUser->getRequest()->setSessionData(
'SulRenameWarning', array(
+ 'returnTo' => $returnTo,
+ 'returnToQuery' => $returnToQuery,
+ ) );
+ $returnTo = SpecialPageFactory::getLocalNameFor(
'Special:SulRenameWarning' );
+ $returnToQuery = array();
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/includes/CentralAuthPrimaryAuthenticationProvider.php
b/includes/CentralAuthPrimaryAuthenticationProvider.php
new file mode 100644
index 0000000..4dbaa90
--- /dev/null
+++ b/includes/CentralAuthPrimaryAuthenticationProvider.php
@@ -0,0 +1,463 @@
+<?php
+/**
+ * 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 Auth
+ */
+
+use MediaWiki\Auth\AuthManager;
+use MediaWiki\Auth\AbstractPasswordPrimaryAuthenticationProvider;
+use MediaWiki\Auth\AuthenticationRequest;
+use MediaWiki\Auth\AuthenticationResponse;
+use MediaWiki\Auth\PasswordAuthenticationRequest;
+use MediaWiki\Auth\ButtonAuthenticationRequest;
+
+/**
+ * A primary authentication provider that uses the CentralAuth password.
+ */
+class CentralAuthPrimaryAuthenticationProvider
+ extends AbstractPasswordPrimaryAuthenticationProvider
+{
+ /** @var bool Whether to check for force-renamed users on login */
+ protected $checkSULMigration = null;
+
+ /** @var bool Whether to auto-migrate non-merged accounts on login */
+ protected $autoMigrate = null;
+
+ /** @var bool Whether to auto-migrate non-global accounts on login */
+ protected $autoMigrateNonGlobalAccounts = null;
+
+ /** @var bool Whether to prevent a new account from being created if the
+ * account exists on other wikis in the SUL group. */
+ protected $preventUnattached = null;
+
+ /** @var bool Whether to check for spoofed user names */
+ protected $antiSpoofAccounts = null;
+
+ /**
+ * @param array $params Settings. All are optional, defaulting to the
+ * similarly-named $wgCentralAuth* globals.
+ * - checkSULMigration: If true, check if the user was force-renamed
for
+ * SUL unification on login.
+ * - autoMigrate: If true, attempt to auto-migrate local accounts on
other
+ * wikis when logging in.
+ * - autoMigrateNonGlobalAccounts: If true, attempt to auto-migrate
+ * non-global accounts on login.
+ * - preventUnattached: Whether to prevent new unattached accounts from
+ * being created.
+ * - antiSpoofAccounts: Whether to anti-spoof new accounts. Ignored if
the
+ * AntiSpoof extension isn't installed or the extension is outdated.
+ */
+ public function __construct( $params = [] ) {
+ global $wgCentralAuthCheckSULMigration,
$wgCentralAuthAutoMigrate,
+ $wgCentralAuthAutoMigrateNonGlobalAccounts,
$wgCentralAuthPreventUnattached,
+ $wgCentralAuthStrict, $wgAntiSpoofAccounts;
+
+ $params += [
+ 'checkSULMigration' => $wgCentralAuthCheckSULMigration,
+ 'autoMigrate' => $wgCentralAuthAutoMigrate,
+ 'autoMigrateNonGlobalAccounts' =>
$wgCentralAuthAutoMigrateNonGlobalAccounts,
+ 'preventUnattached' => $wgCentralAuthPreventUnattached,
+ 'antiSpoofAccounts' => $wgAntiSpoofAccounts,
+ 'authoritative' => $wgCentralAuthStrict,
+ ];
+
+ parent::__construct( $params );
+
+ $this->checkSULMigration = (bool)$params['checkSULMigration'];
+ $this->autoMigrate = (bool)$params['autoMigrate'];
+ $this->autoMigrateNonGlobalAccounts =
(bool)$params['autoMigrateNonGlobalAccounts'];
+ $this->preventUnattached = (bool)$params['preventUnattached'];
+ $this->antiSpoofAccounts = (bool)$params['antiSpoofAccounts'];
+ }
+
+ public function getAuthenticationRequests( $action, array $options ) {
+ $ret = parent::getAuthenticationRequests( $action, $options );
+
+ if ( $this->antiSpoofAccounts && $action ===
AuthManager::ACTION_CREATE &&
+ class_exists( 'AntiSpoofAuthenticationRequest' )
+ ) {
+ $user = User::newFromName( $options['username'] ) ?:
new User();
+ if ( $user->isAllowed( 'override-antispoof' ) ) {
+ $ret[] = new AntiSpoofAuthenticationRequest();
+ }
+ }
+
+ return $ret;
+ }
+
+ public function beginPrimaryAuthentication( array $reqs ) {
+ $req = AuthenticationRequest::getRequestByClass( $reqs,
PasswordAuthenticationRequest::class );
+ if ( !$req ) {
+ return AuthenticationResponse::newAbstain();
+ }
+
+ if ( $req->username === null || $req->password === null ) {
+ return AuthenticationResponse::newAbstain();
+ }
+
+ $username = User::getCanonicalName( $req->username, 'usable' );
+ if ( $username === false ) {
+ return AuthenticationResponse::newAbstain();
+ }
+
+ $status = $this->checkPasswordValidity( $username,
$req->password );
+ if ( !$status->isOk() ) {
+ // Fatal, can't log in
+ return AuthenticationResponse::newFail(
$status->getMessage() );
+ }
+
+ $pass = false;
+
+ // First, check normal login
+ $centralUser = CentralAuthUser::getMasterInstanceByName(
$username );
+ $pass = $centralUser->authenticate( $req->password ) === 'ok';
+
+ // See if it's a user affected by a rename, if applicable.
+ if ( !$pass && $this->checkSULMigration ) {
+ $renamedUsername = User::getCanonicalName(
+ $req->username . '~' . str_replace( '_', '-',
wfWikiID() )
+ );
+ if ( $renamedUsername !== false ) {
+ $renamed =
CentralAuthUser::getMasterInstanceByName( $renamedUsername );
+ if ( $renamed->getId() ) {
+ $this->logger->debug(
+ 'CentralAuthMigration: Checking
for migration of "{oldname}" to "{newname}"',
+ [
+ 'oldname' => $username,
+ 'newname' =>
$renamedUsername,
+ ]
+ );
+
RequestContext::getMain()->getStats()->increment( 'centralauth.migration.check'
);
+
+ if ( $renamed->authenticate(
$req->password ) === 'ok' ) {
+ // Don't do any of the checks
below if we checked for a
+ // renamed user. But do notify,
unless this is coming
+ // through the API action=login
where it's better to
+ // preserve BC and just let it
go.
+ if ( defined( 'MW_API' ) &&
+
$this->manager->getRequest()->getVal( 'action' ) === 'login'
+ ) {
+ return
AuthenticationResponse::newPass( $renamedUsername );
+ }
+
$this->manager->setAuthenticationSessionData( 'CA-renamed-from', $username );
+
$this->manager->setAuthenticationSessionData( 'CA-renamed-to', $renamedUsername
);
+ return
AuthenticationResponse::newUI(
+ [
+ new
ButtonAuthenticationRequest(
+
'caRenameOk', wfMessage( 'ok' ), wfMessage(
'sulrenamewarning-authmanager-ok-help' )
+ )
+ ],
+ wfMessage(
'sulrenamewarning-renamed', $username, $renamedUsername )
+ );
+ }
+ }
+ }
+ }
+
+ // If we don't have a central account, see if all local
accounts match
+ // the password and can be globalized. (bug T72392)
+ if ( !$centralUser->exists() ) {
+ $this->logger->debug( 'no global account for
"{username}"', [ 'username' => $username ] );
+ if ( $this->autoMigrateNonGlobalAccounts ) {
+ $ok = $centralUser->storeAndMigrate( [
$req->password ], /* $sendToRC = */ true, /* $safe = */ true, /* $checkHome =
*/ true );
+ if ( $ok ) {
+ $this->logger->debug(
'wgCentralAuthAutoMigrateNonGlobalAccounts successful in creating a global
account for "{username}"', [
+ 'username' => $username
+ ] );
+ return AuthenticationResponse::newPass(
$username );
+ }
+ }
+ return $this->failResponse( $req );
+ }
+
+ if ( $pass && $this->autoMigrate ) {
+ // If the user passed in the global password, we can
identify
+ // any remaining local accounts with a matching password
+ // and migrate them in transparently.
+ //
+ // That may or may not include the current wiki.
+ //
+ $this->logger->debug( 'attempting
wgCentralAuthAutoMigrate for "{username}"', [
+ 'username' => $username,
+ ] );
+ $centralUser->attemptPasswordMigration( $req->password
);
+ }
+
+ if ( !$centralUser->isAttached() ) {
+ $local = User::newFromName( $username );
+ if ( $local && $local->getId() ) {
+ // An unattached local account; central
authentication can't
+ // be used until this account has been
transferred.
+ // $wgCentralAuthStrict will determine if local
login is allowed.
+ $this->logger->debug( 'unattached account for
"{username}"', [
+ 'username' => $username,
+ ] );
+ return $this->failResponse( $req );
+ }
+ }
+
+ if ( $pass ) {
+ return AuthenticationResponse::newPass( $username );
+ } else {
+ // We know the central user is attached at this point,
so never
+ // fall back to other password providers.
+ return AuthenticationResponse::newFail( wfMessage(
'wrongpassword' ) );
+ }
+ }
+
+ public function continuePrimaryAuthentication( array $reqs ) {
+ $username = $this->manager->getAuthenticationSessionData(
'CA-renamed-from' );
+ $renamedUsername =
$this->manager->getAuthenticationSessionData( 'CA-renamed-to' );
+ if ( $username === null || $renamedUsername === null ) {
+ // What?
+ $this->logger->debug( 'Missing "CA-renamed-from" or
"CA-renamed-to" in session data' );
+ return AuthenticationResponse::newFail( wfMessage(
'authmanager-authn-not-in-progress' ) );
+ }
+
+ $req = ButtonAuthenticationRequest::getRequestByName( $reqs,
'caRenameOk' );
+ if ( $req ) {
+ return AuthenticationResponse::newPass(
$renamedUsername );
+ } else {
+ // Try again, client, and please get it right this time.
+ return AuthenticationResponse::newUI(
+ [
+ new ButtonAuthenticationRequest(
+ 'caRenameOk', wfMessage( 'ok'
), wfMessage( 'sulrenamewarning-authmanager-ok-help' )
+ )
+ ],
+ wfMessage( 'sulrenamewarning-renamed',
$username, $renamedUsername )
+ );
+ }
+ }
+
+ public function postAuthentication( $user, AuthenticationResponse
$response ) {
+ if ( $response->status === AuthenticationResponse::PASS ) {
+ $centralUser = CentralAuthUser::getMasterInstance(
$user );
+ if ( $centralUser->exists() &&
$centralUser->isAttached() &&
+ $centralUser->getEmail() != $user->getEmail()
+ ) {
+ $user->setEmail( $centralUser->getEmail() );
+ $user->mEmailAuthenticated =
$centralUser->getEmailAuthenticationTimestamp();
+ $user->saveSettings();
+ }
+ }
+ }
+
+ public function testUserCanAuthenticate( $username ) {
+ $username = User::getCanonicalName( $username, 'usable' );
+ if ( $username === false ) {
+ return false;
+ }
+
+ // Note this omits the case where an unattached local user
exists but
+ // will be globalized on login thanks to $this->autoMigrate or
+ // $this->autoMigrateNonGlobalAccounts. Both are impossible to
really
+ // test here because they both need cleartext passwords to do
their
+ // thing. If you have such accounts on your wiki, you should
have
+ // LocalPasswordPrimaryAuthenticationProvider configured too
which
+ // will return true for such users.
+
+ $centralUser = CentralAuthUser::getInstanceByName( $username );
+ return $centralUser && $centralUser->exists() &&
+ ( $centralUser->isAttached() || !User::idFromName(
$username ) ) &&
+ !$centralUser->getPasswordObject() instanceof
InvalidPassword;
+ }
+
+ public function testUserExists( $username, $flags = User::READ_NORMAL )
{
+ $username = User::getCanonicalName( $username, 'usable' );
+ if ( $username === false ) {
+ return false;
+ }
+
+ $centralUser = CentralAuthUser::getInstanceByName( $username );
+ return $centralUser && $centralUser->exists();
+ }
+
+ public function providerAllowsAuthenticationDataChange(
+ AuthenticationRequest $req, $checkData = true
+ ) {
+ if ( get_class( $req ) === PasswordAuthenticationRequest::class
) {
+ if ( !$checkData ) {
+ return StatusValue::newGood();
+ }
+
+ $username = User::getCanonicalName( $req->username,
'usable' );
+ if ( $username !== false ) {
+ $centralUser =
CentralAuthUser::getInstanceByName( $username );
+ if ( $centralUser && $centralUser->isAttached()
) {
+ $sv = StatusValue::newGood();
+ if ( $req->password !== null ) {
+ if ( $req->password !==
$req->retype ) {
+ $sv->fatal( 'badretype'
);
+ } else {
+ $sv->merge(
$this->checkPasswordValidity( $username, $req->password ) );
+ }
+ }
+ return $sv;
+ }
+ }
+ }
+
+ return StatusValue::newGood( 'ignored' );
+ }
+
+ public function providerChangeAuthenticationData( AuthenticationRequest
$req ) {
+ $username = $req->username !== null ? User::getCanonicalName(
$req->username, 'usable' ) : false;
+ if ( $username === false ) {
+ return;
+ }
+
+ if ( get_class( $req ) === PasswordAuthenticationRequest::class
) {
+ $centralUser =
CentralAuthUser::getMasterInstanceByName( $username );
+ if ( $centralUser && $centralUser->isAttached() ) {
+ $centralUser->setPassword( $req->password );
+ }
+ }
+ }
+
+ public function accountCreationType() {
+ return self::TYPE_CREATE;
+ }
+
+ public function testUserForCreation( $user, $autocreate ) {
+ global $wgCentralAuthEnableGlobalRenameRequest;
+
+ $status = parent::testUserForCreation( $user, $autocreate );
+ if ( !$status->isOk() ) {
+ return $status;
+ }
+
+ $centralUser = CentralAuthUser::getMasterInstance( $user );
+
+ // Rename in progress?
+ if ( $centralUser->renameInProgressOn( wfWikiID() ) ) {
+ $status->fatal( 'centralauth-rename-abortlogin' );
+ return $status;
+ }
+
+ if ( $autocreate !== $this->getUniqueId() ) {
+ // Prevent creation if the user exists centrally
+ if ( $centralUser->exists() ) {
+ $status->fatal( 'centralauth-account-exists' );
+ return $status;
+ }
+
+ // Prevent creation if the user exists anywhere else we
know about,
+ // and we're asked to
+ if ( $this->preventUnattached &&
$centralUser->listUnattached() ) {
+ $status->fatal(
'centralauth-account-unattached-exists' );
+ return $status;
+ }
+
+ // Block account creation if name is a pending rename
request
+ if ( $wgCentralAuthEnableGlobalRenameRequest &&
+ GlobalRenameRequest::nameHasPendingRequest(
$user->getName() )
+ ) {
+ $status->fatal(
'centralauth-account-rename-exists' );
+ return $status;
+ }
+ }
+
+ return $status;
+ }
+
+ public function testForAccountCreation( $user, $creator, array $reqs ) {
+ $req = AuthenticationRequest::getRequestByClass( $reqs,
PasswordAuthenticationRequest::class );
+
+ $ret = StatusValue::newGood();
+ if ( $req && $req->username !== null && $req->password !== null
) {
+ if ( $req->password !== $req->retype ) {
+ $ret->fatal( 'badretype' );
+ } else {
+ $ret->merge(
+ $this->checkPasswordValidity(
$user->getName(), $req->password )
+ );
+ }
+ }
+
+ // Check CentralAuthAntiSpoof, if applicable
+ if ( class_exists( 'AntiSpoofAuthenticationRequest' ) ) {
+ $antiSpoofReq =
AuthenticationRequest::getRequestByClass( $reqs,
AntiSpoofAuthenticationRequest::class );
+ $ret->merge( CentralAuthAntiSpoofHooks::testNewAccount(
+ $user, $creator, $this->antiSpoofAccounts,
$antiSpoofReq && $antiSpoofReq->ignoreAntiSpoof
+ ) );
+ }
+
+ return $ret;
+ }
+
+ public function beginPrimaryAccountCreation( $user, $creator, array
$reqs ) {
+ $req = AuthenticationRequest::getRequestByClass( $reqs,
PasswordAuthenticationRequest::class );
+ if ( $req ) {
+ if ( $req->username !== null && $req->password !== null
) {
+ $centralUser =
CentralAuthUser::getMasterInstance( $user );
+ if ( $centralUser->exists() ) {
+ return AuthenticationResponse::newFail(
wfMessage( 'centralauth-account-exists' ) );
+ }
+ if ( $centralUser->listUnattached() ) {
+ // $this->testUserForCreation() will
already have rejected it if necessary
+ return
AuthenticationResponse::newAbstain();
+ }
+ // Username is unused; set up as a global
account
+ if ( !$centralUser->register( $req->password,
$user->getEmail() ) ) {
+ // Wha?
+ return AuthenticationResponse::newFail(
wfMessage( 'userexists' ) );
+ }
+ $centralUser->attach( wfWikiID(), 'new' );
+ CentralAuthUtils::scheduleCreationJobs(
$centralUser );
+ return AuthenticationResponse::newPass(
$user->getName() );
+ }
+ }
+
+ return AuthenticationResponse::newAbstain();
+ }
+
+ public function autoCreatedAccount( $user, $source ) {
+ $centralUser = CentralAuthUser::getMasterInstance( $user );
+ if ( !$centralUser->exists() ) {
+ $this->logger->debug(
+ 'Not centralizing auto-created user {username},
central account doesn\'t exist',
+ [
+ 'user' => $user->getName(),
+ ]
+ );
+ } elseif ( $source !== $this->getUniqueId() &&
$centralUser->listUnattached() ) {
+ $this->logger->debug(
+ 'Not centralizing auto-created user {username},
unattached accounts exist',
+ [
+ 'user' => $user->getName(),
+ 'source' => $source,
+ ]
+ );
+ } else {
+ $this->logger->debug(
+ 'Centralizing auto-created user {username}',
+ [
+ 'user' => $user->getName(),
+ ]
+ );
+ $centralUser->attach( wfWikiID(), 'login' );
+ $centralUser->addLocalName( wfWikiID() );
+
+ if ( $centralUser->getEmail() != $user->getEmail() ) {
+ $user->setEmail( $centralUser->getEmail() );
+ $user->mEmailAuthenticated =
$centralUser->getEmailAuthenticationTimestamp();
+ }
+ }
+ }
+}
diff --git a/includes/CentralAuthSecondaryAuthenticationProvider.php
b/includes/CentralAuthSecondaryAuthenticationProvider.php
new file mode 100644
index 0000000..f97be5b
--- /dev/null
+++ b/includes/CentralAuthSecondaryAuthenticationProvider.php
@@ -0,0 +1,79 @@
+<?php
+/**
+ * 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 Auth
+ */
+
+use MediaWiki\Auth\AbstractSecondaryAuthenticationProvider;
+use MediaWiki\Auth\AuthenticationResponse;
+
+/**
+ * This implements CentralAuth checks that should be done on all logins, e.g.
+ * being renamed or centrally locked.
+ */
+class CentralAuthSecondaryAuthenticationProvider extends
AbstractSecondaryAuthenticationProvider {
+
+ /**
+ * @param array $params
+ */
+ public function __construct( $params = [] ) {
+ }
+
+ public function getAuthenticationRequests( $action, array $options ) {
+ return [];
+ }
+
+ public function beginSecondaryAuthentication( $user, array $reqs ) {
+ $centralUser = CentralAuthUser::getMasterInstance( $user );
+
+ // Since logins are rare, check the actual DB
+ $rename = $centralUser->renameInProgressOn( wfWikiID() );
+ if ( $rename ) {
+ // This wiki specifically has a rename in progress, so
always abort
+ return AuthenticationResponse::newFail( wfMessage(
'centralauth-rename-abortlogin' ) );
+ }
+
+ // Now check if the user is the target of a rename anywhere
+ $rename = $centralUser->renameInProgress();
+ if ( $rename ) {
+ // It's possible a user is being renamed but someone
else with
+ // an unattached account is trying to login. Since
we've already
+ // moved everything over to the new account name, we
only need
+ // to check one username.
+ $newCAUser = $rename[1] === $user->getName()
+ ? $centralUser
+ : CentralAuthUser::getInstanceByName(
$rename[0] );
+
+ if ( $newCAUser->isAttached() ) {
+ // If there is an account using that name that
exists on this wiki
+ // reject the login.
+ return AuthenticationResponse::newFail(
wfMessage( 'centralauth-rename-abortlogin' ) );
+ }
+ }
+
+ if ( $centralUser->canAuthenticate() === 'locked' ) {
+ return AuthenticationResponse::newFail( wfMessage(
'centralauth-login-error-locked' ) );
+ }
+
+ return AuthenticationResponse::newAbstain();
+ }
+
+ public function beginSecondaryAccountCreation( $user, $creator, array
$reqs ) {
+ return AuthenticationResponse::newAbstain();
+ }
+}
diff --git a/includes/CentralAuthUser.php b/includes/CentralAuthUser.php
index 5b6f3cf..797882d 100644
--- a/includes/CentralAuthUser.php
+++ b/includes/CentralAuthUser.php
@@ -584,7 +584,7 @@
*
* @return Password
*/
- protected function getPasswordObject() {
+ public function getPasswordObject() {
$this->loadState();
return $this->getPasswordFromString( $this->mPassword,
$this->mSalt );
}
diff --git a/includes/CentralAuthUtils.php b/includes/CentralAuthUtils.php
index ad267d2..fab028b 100644
--- a/includes/CentralAuthUtils.php
+++ b/includes/CentralAuthUtils.php
@@ -227,4 +227,28 @@
RequestContext::getMain()->getStats()->timing(
"centralauth.session.delete", $real );
}
}
+
+ /**
+ * Sets up jobs to create and attach a local account for the given user
on every wiki listed in
+ * $wgCentralAuthAutoCreateWikis.
+ * @param CentralAuthUser $centralUser
+ */
+ public static function scheduleCreationJobs( CentralAuthUser
$centralUser ) {
+ global $wgCentralAuthAutoCreateWikis;
+
+ $name = $centralUser->getName();
+ $thisWiki = wfWikiID();
+ $session = RequestContext::getMain()->exportSession();
+ foreach ( $wgCentralAuthAutoCreateWikis as $wiki ) {
+ if ( $wiki === $thisWiki ) {
+ continue;
+ }
+ $job = Job::factory(
+ 'CentralAuthCreateLocalAccountJob',
+ Title::makeTitleSafe( NS_USER, $name ),
+ array( 'name' => $name, 'from' => $thisWiki,
'session' => $session )
+ );
+ JobQueueGroup::singleton( $wiki )->push( $job );
+ }
+ }
}
diff --git a/includes/LocalRenameJob/LocalUserMergeJob.php
b/includes/LocalRenameJob/LocalUserMergeJob.php
index 5111870..c38df35 100755
--- a/includes/LocalRenameJob/LocalUserMergeJob.php
+++ b/includes/LocalRenameJob/LocalUserMergeJob.php
@@ -60,22 +60,24 @@
* @throws Exception
*/
private function maybeCreateNewUser( $newName ) {
- global $wgAuth;
+ global $wgDisableAuthManager;
$user = User::newFromName( $newName );
if ( $user->getId() ) {
// User already exists, nothing to do.
return $user;
}
- // Logic from CentralAuthHooks::attemptAddUser
- $user->loadDefaults( $newName );
- $status = $user->addToDatabase();
- if ( !$status->isOK() ) {
+ if ( class_exists( MediaWiki\Auth\AuthManager::class ) &&
!$wgDisableAuthManager ) {
+ $status = MediaWiki\Auth\AuthManager::autoCreateUser(
$user, CentralAuthPrimaryAuthenticationProvider::class, false );
+ if ( !$status->isGood() ) {
+ $this->updateStatus( 'failed' );
+ throw new Exception(
"AuthManager::autoCreateUser failed for $newName: " . $status->getWikiText(
null, null, 'en' ) );
+ }
+ } elseif ( !MediaWiki\Session\SessionManager::autoCreateUser(
$user ) ) {
$this->updateStatus( 'failed' );
- throw new Exception( "User::addToDatabase failed for
$newName: {$status->getWikiText()}" );
+ throw new Exception( "SessionManager::autoCreateUser
failed for $newName" );
}
- $wgAuth->initUser( $user, true );
- Hooks::run( 'AuthPluginAutoCreate', array( $user ) );
+
return $user;
}
diff --git a/includes/specials/SpecialCentralLogin.php
b/includes/specials/SpecialCentralLogin.php
index d62de05..da1d70a 100644
--- a/includes/specials/SpecialCentralLogin.php
+++ b/includes/specials/SpecialCentralLogin.php
@@ -230,9 +230,8 @@
$this->getContext()->setUser( $user );
// Show the login success page
- $form = new LoginForm( new FauxRequest() );
- $inject_html = '';
+ $inject_html = '';
if ( $attempt['type'] === 'signup' ) {
$msg = $this->msg( 'centralauth-welcomecreation-msg' );
if ( !$msg->isDisabled() ) {
@@ -266,7 +265,8 @@
CentralAuthHooks::getDomainAutoLoginHtml(
$user, $centralUser ) );
}
- $form->showReturnToPage(
+ $helper = new LoginHelper( $this->getContext() );
+ $helper->showReturnToPage(
$action,
$attempt['returnTo'],
$attempt['returnToQuery'],
@@ -292,9 +292,10 @@
$this->showError( 'centralauth-warning-notattached' );
return;
}
+
// Show the login success page
- $form = new LoginForm( new FauxRequest() );
- $form->showReturnToPage( 'success',
+ $helper = new LoginHelper( $this->getContext() );
+ $helper->showReturnToPage( 'success',
$this->getRequest()->getVal( 'returnto', '' ),
$this->getRequest()->getVal( 'returntoquery', '' )
);
diff --git a/maintenance/createLocalAccount.php
b/maintenance/createLocalAccount.php
index b4ca65d..931274a 100644
--- a/maintenance/createLocalAccount.php
+++ b/maintenance/createLocalAccount.php
@@ -14,6 +14,8 @@
}
public function execute() {
+ global $wgDisableAuthManager;
+
if ( !class_exists( 'CentralAuthUser' ) ) {
$this->error( "CentralAuth isn't enabled on this
wiki\n", 1 );
}
@@ -29,21 +31,19 @@
if ( $user->getId() ) {
$this->error( "User '$username' already exists\n", 1 );
} else {
- global $wgAuth;
-
$central = CentralAuthUser::getInstance( $user );
if ( !$central->exists() ) {
$this->error( "No such global user:
'$username'\n", 1 );
}
- $user->loadDefaults( $username );
- $user->addToDatabase();
-
- $wgAuth->initUser( $user, true );
- $wgAuth->updateUser( $user );
-
- # Notify hooks (e.g. Newuserlog)
- Hooks::run( 'AuthPluginAutoCreate', array( $user ) );
+ if ( class_exists( MediaWiki\Auth\AuthManager::class )
&& !$wgDisableAuthManager ) {
+ $status =
MediaWiki\Auth\AuthManager::autoCreateUser( $user,
CentralAuthPrimaryAuthenticationProvider::class, false );
+ if ( !$status->isGood() ) {
+ $this->error(
"AuthManager::autoCreateUser failed for $username: " . $status->getWikiText(
null, null, 'en' ) );
+ }
+ } elseif (
!MediaWiki\Session\SessionManager::autoCreateUser( $user ) ) {
+ $this->error( "SessionManager::autoCreateUser
failed for $username" );
+ }
# Update user count
$ssUpdate = new SiteStatsUpdate( 0, 0, 0, 0, 1 );
diff --git a/tests/phpunit/CentralAuthHooksUsingDatabaseTest.php
b/tests/phpunit/CentralAuthHooksUsingDatabaseTest.php
index 380344f..f0a74f1 100644
--- a/tests/phpunit/CentralAuthHooksUsingDatabaseTest.php
+++ b/tests/phpunit/CentralAuthHooksUsingDatabaseTest.php
@@ -5,26 +5,6 @@
*/
class CentralAuthHooksUsingDatabaseTest extends
CentralAuthTestCaseUsingDatabase {
-
- /**
- * @dataProvider provideAbortNewAccount
- * @covers CentralAuthHooks::onAbortNewAccount
- */
- public function testOnAbortNewAccount( $user, $result) {
- $error = '';
- $hookResult = CentralAuthHooks::onAbortNewAccount( $user,
$error );
- $this->assertSame( $result, $hookResult );
- }
-
- public function provideAbortNewAccount() {
- $userExists = User::newFromName( 'GlobalUser' );
- $userNotExists = User::newFromName( 'UserNotExists' );
- return array(
- array( $userNotExists, true ),
- array( $userExists, false ),
- );
- }
-
/**
* @covers CentralAuthHooks::onUserGetEmailAuthenticationTimestampp
*/
@@ -32,33 +12,6 @@
$user = User::newFromName( 'GlobalLockedUser' );
$this->assertFalse( $user->isEmailConfirmed() );
$this->assertFalse( $user->canReceiveEmail() );
- }
-
- /**
- * @dataProvider provideAbortLogin
- * @covers CentralAuthHooks::onAbortLogin
- */
- public function testOnAbortLogin( $user, $result, $test) {
- $retval = 0;
- $msg = '';
- $hookResult = CentralAuthHooks::onAbortLogin( $user, '',
$retval, $msg );
- $this->assertEquals( $result, $hookResult, $test );
- }
-
- public function provideAbortLogin() {
- $user = User::newFromName( 'GlobalUser' );
- $lockedUser = User::newFromName( 'GlobalLockedUser' );
-
- // Fake out CentralAuthUser::getInstance()
- $noUser = User::newFromName( 'NoUser' );
- TestingAccessWrapper::newFromClass( 'CentralAuthUser'
)->getUserCache()
- ->set( $noUser->getName(),
CentralAuthUser::newUnattached( 'NoUser' ) );
-
- return array(
- array( $user, true, 'Attached user can login' ),
- array( $noUser, true, 'Unattached user can login' ),
- array( $lockedUser, false, 'Locked User cannot login' ),
- );
}
/**
diff --git a/tests/phpunit/CentralAuthPreAuthManagerHooksUsingDatabaseTest.php
b/tests/phpunit/CentralAuthPreAuthManagerHooksUsingDatabaseTest.php
new file mode 100644
index 0000000..a06ebb7
--- /dev/null
+++ b/tests/phpunit/CentralAuthPreAuthManagerHooksUsingDatabaseTest.php
@@ -0,0 +1,93 @@
+<?php
+/**
+ * Basic tests for CentralAuthPreAuthManagerHooks
+ * @group CentralAuthDB
+ */
+class CentralAuthPreAuthManagerHooksUsingDatabaseTest extends
CentralAuthTestCaseUsingDatabase {
+
+
+ /**
+ * @dataProvider provideAbortNewAccount
+ * @covers CentralAuthPreAuthManagerHooks::onAbortNewAccount
+ */
+ public function testOnAbortNewAccount( $user, $result) {
+ $error = '';
+ $hookResult =
CentralAuthPreAuthManagerHooks::onAbortNewAccount( $user, $error );
+ $this->assertSame( $result, $hookResult );
+ }
+
+ public function provideAbortNewAccount() {
+ $userExists = User::newFromName( 'GlobalUser' );
+ $userNotExists = User::newFromName( 'UserNotExists' );
+ return array(
+ array( $userNotExists, true ),
+ array( $userExists, false ),
+ );
+ }
+
+ /**
+ * @dataProvider provideAbortLogin
+ * @covers CentralAuthPreAuthManagerHooks::onAbortLogin
+ */
+ public function testOnAbortLogin( $user, $result, $test) {
+ $retval = 0;
+ $msg = '';
+ $hookResult = CentralAuthPreAuthManagerHooks::onAbortLogin(
$user, '', $retval, $msg );
+ $this->assertEquals( $result, $hookResult, $test );
+ }
+
+ public function provideAbortLogin() {
+ $user = User::newFromName( 'GlobalUser' );
+ $lockedUser = User::newFromName( 'GlobalLockedUser' );
+
+ // Fake out CentralAuthUser::getInstance()
+ $noUser = User::newFromName( 'NoUser' );
+ TestingAccessWrapper::newFromClass( 'CentralAuthUser'
)->getUserCache()
+ ->set( $noUser->getName(),
CentralAuthUser::newUnattached( 'NoUser' ) );
+
+ return array(
+ array( $user, true, 'Attached user can login' ),
+ array( $noUser, true, 'Unattached user can login' ),
+ array( $lockedUser, false, 'Locked User cannot login' ),
+ );
+ }
+
+ /**
+ * Setup a fresh set of global users for each test.
+ * Note: MediaWikiTestCase::resetDB() will delete all tables between
+ * test runs, so no explicite tearDown() is needed.
+ */
+ protected function setUp() {
+ parent::setUp();
+ $u = new CentralAuthTestUser(
+ 'GlobalUser',
+ 'GUP@ssword',
+ array( 'gu_id' => '1001' ),
+ array(
+ array( wfWikiID(), 'primary' ),
+ array( 'enwiki', 'primary' ),
+ array( 'dewiki', 'login' ),
+ array( 'metawiki', 'password' ),
+ )
+ );
+ $u->save( $this->db );
+
+ $u = new CentralAuthTestUser(
+ 'GlobalLockedUser',
+ 'GLUP@ssword',
+ array(
+ 'gu_id' => '1003',
+ 'gu_locked' => 1,
+ 'gu_hidden' => CentralAuthUser::HIDDEN_NONE,
+ 'gu_email' => 'testlocked@localhost',
+ 'gu_home_db' => 'metawiki',
+ ),
+ array(
+ array( 'metawiki', 'primary' ),
+ )
+ );
+ $u->save( $this->db );
+ }
+
+
+}
--
To view, visit https://gerrit.wikimedia.org/r/284499
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: Ia60d92f9e08740e1f76a3744147b93a7636ae09e
Gerrit-PatchSet: 25
Gerrit-Project: mediawiki/extensions/CentralAuth
Gerrit-Branch: master
Gerrit-Owner: Anomie <[email protected]>
Gerrit-Reviewer: Anomie <[email protected]>
Gerrit-Reviewer: BryanDavis <[email protected]>
Gerrit-Reviewer: Gergő Tisza <[email protected]>
Gerrit-Reviewer: Legoktm <[email protected]>
Gerrit-Reviewer: Paladox <[email protected]>
Gerrit-Reviewer: Siebrand <[email protected]>
Gerrit-Reviewer: jenkins-bot <>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits