29 use Psr\Log\LoggerAwareInterface;
30 use Psr\Log\LoggerInterface;
35 use Wikimedia\ObjectFactory;
156 if ( self::$instance ===
null ) {
157 self::$instance =
new self(
196 $this->logger->warning(
"Overriding AuthManager primary authn because $why" );
198 if ( $this->primaryAuthenticationProviders !==
null ) {
199 $this->logger->warning(
200 'PrimaryAuthenticationProviders have already been accessed! I hope nothing breaks.'
203 $this->allAuthenticationProviders = array_diff_key(
204 $this->allAuthenticationProviders,
205 $this->primaryAuthenticationProviders
207 $session = $this->request->getSession();
208 $session->remove(
'AuthManager::authnState' );
209 $session->remove(
'AuthManager::accountCreationState' );
210 $session->remove(
'AuthManager::accountLinkState' );
211 $this->createdAccountAuthenticationRequests = [];
214 $this->primaryAuthenticationProviders = [];
215 foreach ( $providers as $provider ) {
217 throw new \RuntimeException(
218 'Expected instance of MediaWiki\\Auth\\PrimaryAuthenticationProvider, got ' .
219 get_class( $provider )
222 $provider->setLogger( $this->logger );
223 $provider->setManager( $this );
224 $provider->setConfig( $this->config );
225 $id = $provider->getUniqueId();
226 if ( isset( $this->allAuthenticationProviders[$id] ) ) {
227 throw new \RuntimeException(
228 "Duplicate specifications for id $id (classes " .
229 get_class( $provider ) .
' and ' .
230 get_class( $this->allAuthenticationProviders[$id] ) .
')'
233 $this->allAuthenticationProviders[$id] = $provider;
234 $this->primaryAuthenticationProviders[$id] = $provider;
268 return $this->request->getSession()->canSetUser();
290 $session = $this->request->getSession();
291 if ( !$session->canSetUser() ) {
293 $session->remove(
'AuthManager::authnState' );
294 throw new \LogicException(
'Authentication is not possible now' );
297 $guessUserName =
null;
298 foreach ( $reqs as $req ) {
299 $req->returnToUrl = $returnToUrl;
301 if ( $req->username !==
null && $req->username !==
'' ) {
302 if ( $guessUserName ===
null ) {
303 $guessUserName = $req->username;
304 } elseif ( $guessUserName !== $req->username ) {
305 $guessUserName =
null;
314 $reqs, CreatedAccountAuthenticationRequest::class
317 if ( !in_array( $req, $this->createdAccountAuthenticationRequests,
true ) ) {
318 throw new \LogicException(
319 'CreatedAccountAuthenticationRequests are only valid on ' .
320 'the same AuthManager that created the account'
327 throw new \UnexpectedValueException(
328 "CreatedAccountAuthenticationRequest had invalid username \"{$req->username}\""
330 } elseif ( $user->getId() != $req->id ) {
331 throw new \UnexpectedValueException(
332 "ID for \"{$req->username}\" was {$user->getId()}, expected {$req->id}"
337 $this->logger->info(
'Logging in {user} after account creation', [
338 'user' => $user->getName(),
343 $session->remove(
'AuthManager::authnState' );
344 \Hooks::run(
'AuthManagerLoginAuthenticateAudit', [ $ret, $user, $user->getName(), [] ] );
351 $status = $provider->testForAuthentication( $reqs );
353 $this->logger->debug(
'Login failed in pre-authentication by ' . $provider->getUniqueId() );
360 \Hooks::run(
'AuthManagerLoginAuthenticateAudit', [ $ret,
null, $guessUserName, [] ] );
367 'returnToUrl' => $returnToUrl,
368 'guessUserName' => $guessUserName,
370 'primaryResponse' =>
null,
373 'continueRequests' => [],
378 $reqs, CreateFromLoginAuthenticationRequest::class
381 $state[
'maybeLink'] = $req->maybeLink;
384 $session = $this->request->getSession();
385 $session->setSecret(
'AuthManager::authnState', $state );
414 $session = $this->request->getSession();
416 if ( !$session->canSetUser() ) {
419 throw new \LogicException(
'Authentication is not possible now' );
423 $state = $session->getSecret(
'AuthManager::authnState' );
424 if ( !is_array( $state ) ) {
426 wfMessage(
'authmanager-authn-not-in-progress' )
429 $state[
'continueRequests'] = [];
431 $guessUserName = $state[
'guessUserName'];
433 foreach ( $reqs as $req ) {
434 $req->returnToUrl = $state[
'returnToUrl'];
439 if ( $state[
'primary'] ===
null ) {
442 $guessUserName =
null;
443 foreach ( $reqs as $req ) {
444 if ( $req->username !==
null && $req->username !==
'' ) {
445 if ( $guessUserName ===
null ) {
446 $guessUserName = $req->username;
447 } elseif ( $guessUserName !== $req->username ) {
448 $guessUserName =
null;
453 $state[
'guessUserName'] = $guessUserName;
455 $state[
'reqs'] = $reqs;
458 $res = $provider->beginPrimaryAuthentication( $reqs );
459 switch (
$res->status ) {
461 $state[
'primary'] = $id;
462 $state[
'primaryResponse'] =
$res;
463 $this->logger->debug(
"Primary login with $id succeeded" );
466 $this->logger->debug(
"Login failed in primary authentication by $id" );
467 if (
$res->createRequest || $state[
'maybeLink'] ) {
469 $res->createRequest, $state[
'maybeLink']
475 $session->remove(
'AuthManager::authnState' );
476 \Hooks::run(
'AuthManagerLoginAuthenticateAudit', [
$res,
null, $guessUserName, [] ] );
483 $this->logger->debug(
"Primary login with $id returned $res->status" );
484 $this->
fillRequests(
$res->neededRequests, self::ACTION_LOGIN, $guessUserName );
485 $state[
'primary'] = $id;
486 $state[
'continueRequests'] =
$res->neededRequests;
487 $session->setSecret(
'AuthManager::authnState', $state );
492 throw new \DomainException(
493 get_class( $provider ) .
"::beginPrimaryAuthentication() returned $res->status"
498 if ( $state[
'primary'] ===
null ) {
499 $this->logger->debug(
'Login failed in primary authentication because no provider accepted' );
501 wfMessage(
'authmanager-authn-no-primary' )
506 $session->remove(
'AuthManager::authnState' );
509 } elseif ( $state[
'primaryResponse'] ===
null ) {
515 wfMessage(
'authmanager-authn-not-in-progress' )
520 $session->remove(
'AuthManager::authnState' );
524 $id = $provider->getUniqueId();
525 $res = $provider->continuePrimaryAuthentication( $reqs );
526 switch (
$res->status ) {
528 $state[
'primaryResponse'] =
$res;
529 $this->logger->debug(
"Primary login with $id succeeded" );
532 $this->logger->debug(
"Login failed in primary authentication by $id" );
533 if (
$res->createRequest || $state[
'maybeLink'] ) {
535 $res->createRequest, $state[
'maybeLink']
541 $session->remove(
'AuthManager::authnState' );
542 \Hooks::run(
'AuthManagerLoginAuthenticateAudit', [
$res,
null, $guessUserName, [] ] );
546 $this->logger->debug(
"Primary login with $id returned $res->status" );
547 $this->
fillRequests(
$res->neededRequests, self::ACTION_LOGIN, $guessUserName );
548 $state[
'continueRequests'] =
$res->neededRequests;
549 $session->setSecret(
'AuthManager::authnState', $state );
552 throw new \DomainException(
553 get_class( $provider ) .
"::continuePrimaryAuthentication() returned $res->status"
558 $res = $state[
'primaryResponse'];
559 if (
$res->username ===
null ) {
565 wfMessage(
'authmanager-authn-not-in-progress' )
570 $session->remove(
'AuthManager::authnState' );
580 $state[
'maybeLink'][
$res->linkRequest->getUniqueId()] =
$res->linkRequest;
581 $msg =
'authmanager-authn-no-local-user-link';
583 $msg =
'authmanager-authn-no-local-user';
585 $this->logger->debug(
586 "Primary login with {$provider->getUniqueId()} succeeded, but returned no user"
594 if (
$res->createRequest || $state[
'maybeLink'] ) {
596 $res->createRequest, $state[
'maybeLink']
598 $ret->neededRequests[] = $ret->createRequest;
600 $this->
fillRequests( $ret->neededRequests, self::ACTION_LOGIN,
null,
true );
601 $session->setSecret(
'AuthManager::authnState', [
604 'primaryResponse' =>
null,
606 'continueRequests' => $ret->neededRequests,
617 throw new \DomainException(
618 get_class( $provider ) .
" returned an invalid username: {$res->username}"
621 if ( $user->getId() === 0 ) {
623 $this->logger->info(
'Auto-creating {user} on login', [
624 'user' => $user->getName(),
632 $session->remove(
'AuthManager::authnState' );
633 \Hooks::run(
'AuthManagerLoginAuthenticateAudit', [ $ret, $user, $user->getName(), [] ] );
640 $beginReqs = $state[
'reqs'];
643 if ( !isset( $state[
'secondary'][$id] ) ) {
647 $func =
'beginSecondaryAuthentication';
648 $res = $provider->beginSecondaryAuthentication( $user, $beginReqs );
649 } elseif ( !$state[
'secondary'][$id] ) {
650 $func =
'continueSecondaryAuthentication';
651 $res = $provider->continueSecondaryAuthentication( $user, $reqs );
655 switch (
$res->status ) {
657 $this->logger->debug(
"Secondary login with $id succeeded" );
660 $state[
'secondary'][$id] =
true;
663 $this->logger->debug(
"Login failed in secondary authentication by $id" );
665 $session->remove(
'AuthManager::authnState' );
666 \Hooks::run(
'AuthManagerLoginAuthenticateAudit', [
$res, $user, $user->getName(), [] ] );
670 $this->logger->debug(
"Secondary login with $id returned " .
$res->status );
671 $this->
fillRequests( $res->neededRequests, self::ACTION_LOGIN, $user->getName() );
672 $state[
'secondary'][$id] =
false;
673 $state[
'continueRequests'] =
$res->neededRequests;
674 $session->setSecret(
'AuthManager::authnState', $state );
679 throw new \DomainException(
680 get_class( $provider ) .
"::{$func}() returned $res->status"
689 $this->logger->info(
'Login for {user} succeeded from {clientip}', [
690 'user' => $user->getName(),
691 'clientip' => $this->request->getIP(),
695 $beginReqs, RememberMeAuthenticationRequest::class
700 $session->remove(
'AuthManager::authnState' );
702 \Hooks::run(
'AuthManagerLoginAuthenticateAudit', [ $ret, $user, $user->getName(), [] ] );
704 }
catch ( \Exception $ex ) {
705 $session->remove(
'AuthManager::authnState' );
724 $this->logger->debug( __METHOD__ .
": Checking $operation" );
726 $session = $this->request->getSession();
727 $aId = $session->getUser()->getId();
731 $this->logger->info( __METHOD__ .
": Not logged in! $operation is $status" );
735 if ( $session->canSetUser() ) {
736 $id = $session->get(
'AuthManager:lastAuthId' );
737 $last = $session->get(
'AuthManager:lastAuthTimestamp' );
738 if ( $id !== $aId ||
$last ===
null ) {
739 $timeSinceLogin = PHP_INT_MAX;
741 $timeSinceLogin = max( 0, time() -
$last );
744 $thresholds = $this->config->get(
'ReauthenticateTime' );
745 if ( isset( $thresholds[$operation] ) ) {
746 $threshold = $thresholds[$operation];
747 } elseif ( isset( $thresholds[
'default'] ) ) {
748 $threshold = $thresholds[
'default'];
750 throw new \UnexpectedValueException(
'$wgReauthenticateTime lacks a default' );
753 if ( $threshold >= 0 && $timeSinceLogin > $threshold ) {
757 $timeSinceLogin = -1;
759 $pass = $this->config->get(
'AllowSecuritySensitiveOperationIfCannotReauthenticate' );
760 if ( isset( $pass[$operation] ) ) {
762 } elseif ( isset( $pass[
'default'] ) ) {
765 throw new \UnexpectedValueException(
766 '$wgAllowSecuritySensitiveOperationIfCannotReauthenticate lacks a default'
772 &
$status, $operation, $session, $timeSinceLogin
780 $this->logger->info( __METHOD__ .
": $operation is $status for '{user}'",
782 'user' => $session->getUser()->getName(),
801 if ( $provider->testUserCanAuthenticate( $username ) ) {
825 $normalized = $provider->providerNormalizeUsername( $username );
826 if ( $normalized !==
null ) {
827 $ret[$normalized] =
true;
830 return array_keys( $ret );
848 $this->logger->info(
'Revoking access for {user}', [
867 foreach ( $providers as $provider ) {
868 $status = $provider->providerAllowsAuthenticationDataChange( $req, $checkData );
872 $any = $any ||
$status->value !==
'ignored';
876 $status->warning(
'authmanager-change-not-supported' );
900 $this->logger->info(
'Changing authentication data for {user} class {what}', [
901 'user' => is_string( $req->username ) ? $req->username :
'<no name>',
902 'what' => get_class( $req ),
909 if ( !$isAddition ) {
927 switch ( $provider->accountCreationType() ) {
946 if ( is_int( $options ) ) {
947 $options = [
'flags' => $options ];
950 'flags' => User::READ_NORMAL,
953 $flags = $options[
'flags'];
959 if ( $this->
userExists( $username, $flags ) ) {
964 if ( !is_object( $user ) ) {
967 $user->load( $flags );
968 if ( $user->getId() !== 0 ) {
977 foreach ( $providers as $provider ) {
978 $status = $provider->testUserForCreation( $user,
false, $options );
1000 ->getUserPermissionsErrors(
'createaccount', $creator,
'secure' );
1001 if ( $permErrors ) {
1003 foreach ( $permErrors as
$args ) {
1012 $block->getTarget(),
1013 $block->getReason() ?:
wfMessage(
'blockednoreason' )->text(),
1018 $errorMessage =
'cantcreateaccount-range-text';
1019 $errorParams[] = $this->
getRequest()->getIP();
1021 $errorMessage =
'cantcreateaccount-text';
1030 ->isDnsBlacklisted( $ip,
true )
1058 $session = $this->request->getSession();
1061 $session->remove(
'AuthManager::accountCreationState' );
1062 throw new \LogicException(
'Account creation is not possible' );
1067 }
catch ( \UnexpectedValueException $ex ) {
1070 if ( $username ===
null ) {
1071 $this->logger->debug( __METHOD__ .
': No username provided' );
1078 $this->logger->debug( __METHOD__ .
': {creator} cannot create users: {reason}', [
1079 'user' => $username,
1080 'creator' => $creator->
getName(),
1081 'reason' =>
$status->getWikiText(
null,
null,
'en' )
1090 $this->logger->debug( __METHOD__ .
': {user} cannot be created: {reason}', [
1091 'user' => $username,
1092 'creator' => $creator->
getName(),
1093 'reason' =>
$status->getWikiText(
null,
null,
'en' )
1099 foreach ( $reqs as $req ) {
1100 $req->username = $username;
1101 $req->returnToUrl = $returnToUrl;
1103 $status = $req->populateUser( $user );
1106 $session->remove(
'AuthManager::accountCreationState' );
1107 $this->logger->debug( __METHOD__ .
': UserData is invalid: {reason}', [
1108 'user' => $user->getName(),
1109 'creator' => $creator->
getName(),
1110 'reason' =>
$status->getWikiText(
null,
null,
'en' ),
1120 'username' => $username,
1122 'creatorid' => $creator->
getId(),
1123 'creatorname' => $creator->
getName(),
1125 'returnToUrl' => $returnToUrl,
1127 'primaryResponse' =>
null,
1129 'continueRequests' => [],
1131 'ranPreTests' =>
false,
1136 $reqs, CreateFromLoginAuthenticationRequest::class
1139 $state[
'maybeLink'] = $req->maybeLink;
1141 if ( $req->createRequest ) {
1142 $reqs[] = $req->createRequest;
1143 $state[
'reqs'][] = $req->createRequest;
1147 $session->setSecret(
'AuthManager::accountCreationState', $state );
1148 $session->persist();
1159 $session = $this->request->getSession();
1163 $session->remove(
'AuthManager::accountCreationState' );
1164 throw new \LogicException(
'Account creation is not possible' );
1167 $state = $session->getSecret(
'AuthManager::accountCreationState' );
1168 if ( !is_array( $state ) ) {
1170 wfMessage(
'authmanager-create-not-in-progress' )
1173 $state[
'continueRequests'] = [];
1178 if ( !is_object( $user ) ) {
1179 $session->remove(
'AuthManager::accountCreationState' );
1180 $this->logger->debug( __METHOD__ .
': Invalid username', [
1181 'user' => $state[
'username'],
1186 if ( $state[
'creatorid'] ) {
1189 $creator =
new User;
1190 $creator->
setName( $state[
'creatorname'] );
1195 $lock =
$cache->getScopedLock(
$cache->makeGlobalKey(
'account', md5( $user->getName() ) ) );
1199 $this->logger->debug( __METHOD__ .
': Could not acquire account creation lock', [
1200 'user' => $user->getName(),
1201 'creator' => $creator->getName(),
1209 $this->logger->debug( __METHOD__ .
': {creator} cannot create users: {reason}', [
1210 'user' => $user->getName(),
1211 'creator' => $creator->getName(),
1212 'reason' =>
$status->getWikiText(
null,
null,
'en' )
1216 $session->remove(
'AuthManager::accountCreationState' );
1223 if ( $state[
'userid'] === 0 ) {
1224 if ( $user->getId() !== 0 ) {
1225 $this->logger->debug( __METHOD__ .
': User exists locally', [
1226 'user' => $user->getName(),
1227 'creator' => $creator->getName(),
1231 $session->remove(
'AuthManager::accountCreationState' );
1235 if ( $user->getId() === 0 ) {
1236 $this->logger->debug( __METHOD__ .
': User does not exist locally when it should', [
1237 'user' => $user->getName(),
1238 'creator' => $creator->getName(),
1239 'expected_id' => $state[
'userid'],
1241 throw new \UnexpectedValueException(
1242 "User \"{$state['username']}\" should exist now, but doesn't!"
1245 if ( $user->getId() !== $state[
'userid'] ) {
1246 $this->logger->debug( __METHOD__ .
': User ID/name mismatch', [
1247 'user' => $user->getName(),
1248 'creator' => $creator->getName(),
1249 'expected_id' => $state[
'userid'],
1250 'actual_id' => $user->getId(),
1252 throw new \UnexpectedValueException(
1253 "User \"{$state['username']}\" exists, but " .
1254 "ID {$user->getId()} !== {$state['userid']}!"
1258 foreach ( $state[
'reqs'] as $req ) {
1260 $status = $req->populateUser( $user );
1264 $this->logger->debug( __METHOD__ .
': UserData is invalid: {reason}', [
1265 'user' => $user->getName(),
1266 'creator' => $creator->getName(),
1267 'reason' =>
$status->getWikiText(
null,
null,
'en' ),
1271 $session->remove(
'AuthManager::accountCreationState' );
1277 foreach ( $reqs as $req ) {
1278 $req->returnToUrl = $state[
'returnToUrl'];
1279 $req->username = $state[
'username'];
1283 if ( !$state[
'ranPreTests'] ) {
1287 foreach ( $providers as $id => $provider ) {
1288 $status = $provider->testForAccountCreation( $user, $creator, $reqs );
1290 $this->logger->debug( __METHOD__ .
": Fail in pre-authentication by $id", [
1291 'user' => $user->getName(),
1292 'creator' => $creator->getName(),
1298 $session->remove(
'AuthManager::accountCreationState' );
1303 $state[
'ranPreTests'] =
true;
1308 if ( $state[
'primary'] ===
null ) {
1314 $res = $provider->beginPrimaryAccountCreation( $user, $creator, $reqs );
1315 switch (
$res->status ) {
1317 $this->logger->debug( __METHOD__ .
": Primary creation passed by $id", [
1318 'user' => $user->getName(),
1319 'creator' => $creator->getName(),
1321 $state[
'primary'] = $id;
1322 $state[
'primaryResponse'] =
$res;
1325 $this->logger->debug( __METHOD__ .
": Primary creation failed by $id", [
1326 'user' => $user->getName(),
1327 'creator' => $creator->getName(),
1330 $session->remove(
'AuthManager::accountCreationState' );
1337 $this->logger->debug( __METHOD__ .
": Primary creation $res->status by $id", [
1338 'user' => $user->getName(),
1339 'creator' => $creator->getName(),
1342 $state[
'primary'] = $id;
1343 $state[
'continueRequests'] =
$res->neededRequests;
1344 $session->setSecret(
'AuthManager::accountCreationState', $state );
1349 throw new \DomainException(
1350 get_class( $provider ) .
"::beginPrimaryAccountCreation() returned $res->status"
1355 if ( $state[
'primary'] ===
null ) {
1356 $this->logger->debug( __METHOD__ .
': Primary creation failed because no provider accepted', [
1357 'user' => $user->getName(),
1358 'creator' => $creator->getName(),
1361 wfMessage(
'authmanager-create-no-primary' )
1364 $session->remove(
'AuthManager::accountCreationState' );
1367 } elseif ( $state[
'primaryResponse'] ===
null ) {
1373 wfMessage(
'authmanager-create-not-in-progress' )
1376 $session->remove(
'AuthManager::accountCreationState' );
1380 $id = $provider->getUniqueId();
1381 $res = $provider->continuePrimaryAccountCreation( $user, $creator, $reqs );
1382 switch (
$res->status ) {
1384 $this->logger->debug( __METHOD__ .
": Primary creation passed by $id", [
1385 'user' => $user->getName(),
1386 'creator' => $creator->getName(),
1388 $state[
'primaryResponse'] =
$res;
1391 $this->logger->debug( __METHOD__ .
": Primary creation failed by $id", [
1392 'user' => $user->getName(),
1393 'creator' => $creator->getName(),
1396 $session->remove(
'AuthManager::accountCreationState' );
1400 $this->logger->debug( __METHOD__ .
": Primary creation $res->status by $id", [
1401 'user' => $user->getName(),
1402 'creator' => $creator->getName(),
1405 $state[
'continueRequests'] =
$res->neededRequests;
1406 $session->setSecret(
'AuthManager::accountCreationState', $state );
1409 throw new \DomainException(
1410 get_class( $provider ) .
"::continuePrimaryAccountCreation() returned $res->status"
1418 if ( $state[
'userid'] === 0 ) {
1419 $this->logger->info(
'Creating user {user} during account creation', [
1420 'user' => $user->getName(),
1421 'creator' => $creator->getName(),
1423 $status = $user->addToDatabase();
1428 $session->remove(
'AuthManager::accountCreationState' );
1434 $user->saveSettings();
1435 $state[
'userid'] = $user->getId();
1444 $logSubtype = $provider->finishAccountCreation( $user, $creator, $state[
'primaryResponse'] );
1447 if ( $this->config->get(
'NewUserLog' ) ) {
1448 $isAnon = $creator->isAnon();
1449 $logEntry = new \ManualLogEntry(
1451 $logSubtype ?: ( $isAnon ?
'create' :
'create2' )
1453 $logEntry->setPerformer( $isAnon ? $user : $creator );
1454 $logEntry->setTarget( $user->getUserPage() );
1457 $state[
'reqs'], CreationReasonAuthenticationRequest::class
1459 $logEntry->setComment( $req ? $req->reason :
'' );
1460 $logEntry->setParameters( [
1461 '4::userid' => $user->getId(),
1463 $logid = $logEntry->insert();
1464 $logEntry->publish( $logid );
1470 $beginReqs = $state[
'reqs'];
1473 if ( !isset( $state[
'secondary'][$id] ) ) {
1477 $func =
'beginSecondaryAccountCreation';
1478 $res = $provider->beginSecondaryAccountCreation( $user, $creator, $beginReqs );
1479 } elseif ( !$state[
'secondary'][$id] ) {
1480 $func =
'continueSecondaryAccountCreation';
1481 $res = $provider->continueSecondaryAccountCreation( $user, $creator, $reqs );
1485 switch (
$res->status ) {
1487 $this->logger->debug( __METHOD__ .
": Secondary creation passed by $id", [
1488 'user' => $user->getName(),
1489 'creator' => $creator->getName(),
1493 $state[
'secondary'][$id] =
true;
1497 $this->logger->debug( __METHOD__ .
": Secondary creation $res->status by $id", [
1498 'user' => $user->getName(),
1499 'creator' => $creator->getName(),
1502 $state[
'secondary'][$id] =
false;
1503 $state[
'continueRequests'] =
$res->neededRequests;
1504 $session->setSecret(
'AuthManager::accountCreationState', $state );
1507 throw new \DomainException(
1508 get_class( $provider ) .
"::{$func}() returned $res->status." .
1509 ' Secondary providers are not allowed to fail account creation, that' .
1510 ' should have been done via testForAccountCreation().'
1514 throw new \DomainException(
1515 get_class( $provider ) .
"::{$func}() returned $res->status"
1521 $id = $user->getId();
1522 $name = $user->getName();
1525 $ret->loginRequest = $req;
1526 $this->createdAccountAuthenticationRequests[] = $req;
1528 $this->logger->info( __METHOD__ .
': Account creation succeeded for {user}', [
1529 'user' => $user->getName(),
1530 'creator' => $creator->getName(),
1534 $session->remove(
'AuthManager::accountCreationState' );
1537 }
catch ( \Exception $ex ) {
1538 $session->remove(
'AuthManager::accountCreationState' );
1561 if (
$source !== self::AUTOCREATE_SOURCE_SESSION &&
1562 $source !== self::AUTOCREATE_SOURCE_MAINT &&
1565 throw new \InvalidArgumentException(
"Unknown auto-creation source: $source" );
1572 $flags = User::READ_NORMAL;
1582 $flags = User::READ_LATEST;
1587 $this->logger->debug( __METHOD__ .
': {username} already exists locally', [
1588 'username' => $username,
1590 $user->
setId( $localId );
1596 $status->warning(
'userexists' );
1602 $this->logger->debug( __METHOD__ .
': denied by wfReadOnly(): {reason}', [
1603 'username' => $username,
1613 $session = $this->request->getSession();
1614 if ( $session->get(
'AuthManager::AutoCreateBlacklist' ) ) {
1615 $this->logger->debug( __METHOD__ .
': blacklisted in session {sessionid}', [
1616 'username' => $username,
1617 'sessionid' => $session->getId(),
1621 $reason = $session->get(
'AuthManager::AutoCreateBlacklist' );
1631 $this->logger->debug( __METHOD__ .
': name "{username}" is not creatable', [
1632 'username' => $username,
1634 $session->set(
'AuthManager::AutoCreateBlacklist',
'noname' );
1644 ->userHasAnyRight( $anon,
'createaccount',
'autocreateaccount' )
1646 $this->logger->debug( __METHOD__ .
': IP lacks the ability to create or autocreate accounts', [
1647 'username' => $username,
1648 'ip' => $anon->getName(),
1650 $session->set(
'AuthManager::AutoCreateBlacklist',
'authmanager-autocreate-noperm' );
1651 $session->persist();
1659 $lock =
$cache->getScopedLock(
$cache->makeGlobalKey(
'account', md5( $username ) ) );
1661 $this->logger->debug( __METHOD__ .
': Could not acquire account creation lock', [
1662 'user' => $username,
1671 'flags' => User::READ_LATEST,
1677 foreach ( $providers as $provider ) {
1678 $status = $provider->testUserForCreation( $user,
$source, $options );
1681 $this->logger->debug( __METHOD__ .
': Provider denied creation of {username}: {reason}', [
1682 'username' => $username,
1683 'reason' => $ret->getWikiText(
null,
null,
'en' ),
1685 $session->set(
'AuthManager::AutoCreateBlacklist',
$status );
1692 $backoffKey =
$cache->makeKey(
'AuthManager',
'autocreate-failed', md5( $username ) );
1693 if (
$cache->get( $backoffKey ) ) {
1694 $this->logger->debug( __METHOD__ .
': {username} denied by prior creation attempt failures', [
1695 'username' => $username,
1703 $from = $_SERVER[
'REQUEST_URI'] ??
'CLI';
1704 $this->logger->info( __METHOD__ .
': creating new user ({username}) - from: {from}', [
1705 'username' => $username,
1711 $old = $trxProfiler->setSilenced(
true );
1717 if ( $user->
getId() ) {
1718 $this->logger->info( __METHOD__ .
': {username} already exists locally (race)', [
1719 'username' => $username,
1725 $status->warning(
'userexists' );
1727 $this->logger->error( __METHOD__ .
': {username} failed with message {msg}', [
1728 'username' => $username,
1729 'msg' =>
$status->getWikiText(
null,
null,
'en' )
1736 }
catch ( \Exception $ex ) {
1737 $trxProfiler->setSilenced( $old );
1738 $this->logger->error( __METHOD__ .
': {username} failed with exception {exception}', [
1739 'username' => $username,
1743 $cache->set( $backoffKey, 1, 600 );
1753 \Hooks::run(
'LocalUserCreated', [ $user,
true ] );
1764 if ( $this->config->get(
'NewUserLog' ) ) {
1765 $logEntry = new \ManualLogEntry(
'newusers',
'autocreate' );
1766 $logEntry->setPerformer( $user );
1768 $logEntry->setComment(
'' );
1769 $logEntry->setParameters( [
1770 '4::userid' => $user->
getId(),
1772 $logEntry->insert();
1775 $trxProfiler->setSilenced( $old );
1814 $session = $this->request->getSession();
1815 $session->remove(
'AuthManager::accountLinkState' );
1819 throw new \LogicException(
'Account linking is not possible' );
1822 if ( $user->
getId() === 0 ) {
1830 foreach ( $reqs as $req ) {
1831 $req->username = $user->
getName();
1832 $req->returnToUrl = $returnToUrl;
1838 foreach ( $providers as $id => $provider ) {
1839 $status = $provider->testForAccountLink( $user );
1841 $this->logger->debug( __METHOD__ .
": Account linking pre-check failed by $id", [
1853 'username' => $user->
getName(),
1854 'userid' => $user->
getId(),
1855 'returnToUrl' => $returnToUrl,
1857 'continueRequests' => [],
1861 foreach ( $providers as $id => $provider ) {
1866 $res = $provider->beginPrimaryAccountLink( $user, $reqs );
1867 switch (
$res->status ) {
1869 $this->logger->info(
"Account linked to {user} by $id", [
1876 $this->logger->debug( __METHOD__ .
": Account linking failed by $id", [
1888 $this->logger->debug( __METHOD__ .
": Account linking $res->status by $id", [
1892 $state[
'primary'] = $id;
1893 $state[
'continueRequests'] =
$res->neededRequests;
1894 $session->setSecret(
'AuthManager::accountLinkState', $state );
1895 $session->persist();
1900 throw new \DomainException(
1901 get_class( $provider ) .
"::beginPrimaryAccountLink() returned $res->status"
1907 $this->logger->debug( __METHOD__ .
': Account linking failed because no provider accepted', [
1911 wfMessage(
'authmanager-link-no-primary' )
1923 $session = $this->request->getSession();
1927 $session->remove(
'AuthManager::accountLinkState' );
1928 throw new \LogicException(
'Account linking is not possible' );
1931 $state = $session->getSecret(
'AuthManager::accountLinkState' );
1932 if ( !is_array( $state ) ) {
1934 wfMessage(
'authmanager-link-not-in-progress' )
1937 $state[
'continueRequests'] = [];
1942 if ( !is_object( $user ) ) {
1943 $session->remove(
'AuthManager::accountLinkState' );
1946 if ( $user->getId() !== $state[
'userid'] ) {
1947 throw new \UnexpectedValueException(
1948 "User \"{$state['username']}\" is valid, but " .
1949 "ID {$user->getId()} !== {$state['userid']}!"
1953 foreach ( $reqs as $req ) {
1954 $req->username = $state[
'username'];
1955 $req->returnToUrl = $state[
'returnToUrl'];
1965 wfMessage(
'authmanager-link-not-in-progress' )
1968 $session->remove(
'AuthManager::accountLinkState' );
1972 $id = $provider->getUniqueId();
1973 $res = $provider->continuePrimaryAccountLink( $user, $reqs );
1974 switch (
$res->status ) {
1976 $this->logger->info(
"Account linked to {user} by $id", [
1977 'user' => $user->getName(),
1980 $session->remove(
'AuthManager::accountLinkState' );
1983 $this->logger->debug( __METHOD__ .
": Account linking failed by $id", [
1984 'user' => $user->getName(),
1987 $session->remove(
'AuthManager::accountLinkState' );
1991 $this->logger->debug( __METHOD__ .
": Account linking $res->status by $id", [
1992 'user' => $user->getName(),
1994 $this->
fillRequests(
$res->neededRequests, self::ACTION_LINK, $user->getName() );
1995 $state[
'continueRequests'] =
$res->neededRequests;
1996 $session->setSecret(
'AuthManager::accountLinkState', $state );
1999 throw new \DomainException(
2000 get_class( $provider ) .
"::continuePrimaryAccountLink() returned $res->status"
2003 }
catch ( \Exception $ex ) {
2004 $session->remove(
'AuthManager::accountLinkState' );
2048 $state = $this->request->getSession()->getSecret(
'AuthManager::authnState' );
2049 return is_array( $state ) ? $state[
'continueRequests'] : [];
2052 $state = $this->request->getSession()->getSecret(
'AuthManager::accountCreationState' );
2053 return is_array( $state ) ? $state[
'continueRequests'] : [];
2071 $state = $this->request->getSession()->getSecret(
'AuthManager::accountLinkState' );
2072 return is_array( $state ) ? $state[
'continueRequests'] : [];
2082 throw new \DomainException( __METHOD__ .
": Invalid action \"$action\"" );
2099 $providerAction, array $options, array $providers,
User $user =
null
2102 $options[
'username'] = $user->isAnon() ? null : $user->getName();
2106 foreach ( $providers as $provider ) {
2108 foreach ( $provider->getAuthenticationRequests( $providerAction, $options ) as $req ) {
2112 if ( $isPrimary && $req->required ) {
2117 !isset( $reqs[$id] )
2127 switch ( $providerAction ) {
2135 if ( $options[
'username'] !==
null ) {
2137 $options[
'username'] =
null;
2143 $this->
fillRequests( $reqs, $providerAction, $options[
'username'],
true );
2146 if ( $providerAction === self::ACTION_CHANGE || $providerAction === self::ACTION_REMOVE ) {
2147 $reqs = array_filter( $reqs,
function ( $req ) {
2152 return array_values( $reqs );
2163 foreach ( $reqs as $req ) {
2164 if ( !$req->action || $forceAction ) {
2167 if ( $req->username ===
null ) {
2168 $req->username = $username;
2179 public function userExists( $username, $flags = User::READ_NORMAL ) {
2181 if ( $provider->testUserExists( $username, $flags ) ) {
2203 foreach ( $providers as $provider ) {
2204 if ( !$provider->providerAllowsPropertyChange( $property ) ) {
2221 if ( isset( $this->allAuthenticationProviders[$id] ) ) {
2222 return $this->allAuthenticationProviders[$id];
2227 if ( isset( $providers[$id] ) ) {
2228 return $providers[$id];
2231 if ( isset( $providers[$id] ) ) {
2232 return $providers[$id];
2235 if ( isset( $providers[$id] ) ) {
2236 return $providers[$id];
2256 $session = $this->request->getSession();
2257 $arr = $session->getSecret(
'authData' );
2258 if ( !is_array( $arr ) ) {
2262 $session->setSecret(
'authData', $arr );
2273 $arr = $this->request->getSession()->getSecret(
'authData' );
2274 if ( is_array( $arr ) && array_key_exists( $key, $arr ) ) {
2287 $session = $this->request->getSession();
2288 if ( $key ===
null ) {
2289 $session->remove(
'authData' );
2291 $arr = $session->getSecret(
'authData' );
2292 if ( is_array( $arr ) && array_key_exists( $key, $arr ) ) {
2293 unset( $arr[$key] );
2294 $session->setSecret(
'authData', $arr );
2307 foreach ( $specs as &$spec ) {
2308 $spec = [
'sort2' => $i++ ] + $spec + [
'sort' => 0 ];
2312 usort( $specs,
function ( $a, $b ) {
2313 return $a[
'sort'] <=> $b[
'sort']
2314 ?: $a[
'sort2'] <=> $b[
'sort2'];
2318 foreach ( $specs as $spec ) {
2319 $provider = ObjectFactory::getObjectFromSpec( $spec );
2320 if ( !$provider instanceof $class ) {
2321 throw new \RuntimeException(
2322 "Expected instance of $class, got " . get_class( $provider )
2325 $provider->setLogger( $this->logger );
2326 $provider->setManager( $this );
2327 $provider->setConfig( $this->config );
2328 $id = $provider->getUniqueId();
2329 if ( isset( $this->allAuthenticationProviders[$id] ) ) {
2330 throw new \RuntimeException(
2331 "Duplicate specifications for id $id (classes " .
2332 get_class( $provider ) .
' and ' .
2333 get_class( $this->allAuthenticationProviders[$id] ) .
')'
2336 $this->allAuthenticationProviders[$id] = $provider;
2337 $ret[$id] = $provider;
2347 return $this->config->get(
'AuthManagerConfig' ) ?: $this->config->get(
'AuthManagerAutoConfig' );
2355 if ( $this->preAuthenticationProviders ===
null ) {
2358 PreAuthenticationProvider::class, $conf[
'preauth']
2369 if ( $this->primaryAuthenticationProviders ===
null ) {
2372 PrimaryAuthenticationProvider::class, $conf[
'primaryauth']
2383 if ( $this->secondaryAuthenticationProviders ===
null ) {
2386 SecondaryAuthenticationProvider::class, $conf[
'secondaryauth']
2398 $session = $this->request->getSession();
2399 $delay = $session->delaySave();
2401 $session->resetId();
2402 $session->resetAllTokens();
2403 if ( $session->canSetUser() ) {
2404 $session->setUser( $user );
2406 if ( $remember !==
null ) {
2407 $session->setRememberUser( $remember );
2409 $session->set(
'AuthManager:lastAuthId', $user->getId() );
2410 $session->set(
'AuthManager:lastAuthTimestamp', time() );
2411 $session->persist();
2413 \Wikimedia\ScopedCallback::consume( $delay );
2430 if ( $contLang->hasVariants() ) {
2431 $user->
setOption(
'variant', $contLang->getPreferredVariant() );
2451 foreach ( $providers as $provider ) {
2452 $provider->$method( ...
$args );
2461 if ( !defined(
'MW_PHPUNIT_TEST' ) ) {
2463 throw new \MWException( __METHOD__ .
' may only be called from unit tests!' );
2467 self::$instance =
null;