34 use Psr\Log\LoggerAwareInterface;
35 use Psr\Log\LoggerInterface;
36 use Psr\Log\NullLogger;
43 use Wikimedia\ObjectFactory;
242 $this->logger->warning(
"Overriding AuthManager primary authn because $why" );
244 if ( $this->primaryAuthenticationProviders !==
null ) {
245 $this->logger->warning(
246 'PrimaryAuthenticationProviders have already been accessed! I hope nothing breaks.'
249 $this->allAuthenticationProviders = array_diff_key(
250 $this->allAuthenticationProviders,
251 $this->primaryAuthenticationProviders
253 $session = $this->request->getSession();
254 $session->remove(
'AuthManager::authnState' );
255 $session->remove(
'AuthManager::accountCreationState' );
256 $session->remove(
'AuthManager::accountLinkState' );
257 $this->createdAccountAuthenticationRequests = [];
260 $this->primaryAuthenticationProviders = [];
261 foreach ( $providers as $provider ) {
263 throw new \RuntimeException(
264 'Expected instance of MediaWiki\\Auth\\PrimaryAuthenticationProvider, got ' .
265 get_class( $provider )
268 $provider->setLogger( $this->logger );
269 $provider->setManager( $this );
270 $provider->setConfig( $this->config );
271 $provider->setHookContainer( $this->hookContainer );
272 $id = $provider->getUniqueId();
273 if ( isset( $this->allAuthenticationProviders[$id] ) ) {
274 throw new \RuntimeException(
275 "Duplicate specifications for id $id (classes " .
276 get_class( $provider ) .
' and ' .
277 get_class( $this->allAuthenticationProviders[$id] ) .
')'
280 $this->allAuthenticationProviders[$id] = $provider;
281 $this->primaryAuthenticationProviders[$id] = $provider;
298 return $this->request->getSession()->canSetUser();
320 $session = $this->request->getSession();
321 if ( !$session->canSetUser() ) {
323 $session->remove(
'AuthManager::authnState' );
324 throw new \LogicException(
'Authentication is not possible now' );
327 $guessUserName =
null;
328 foreach ( $reqs as $req ) {
329 $req->returnToUrl = $returnToUrl;
331 if ( $req->username !==
null && $req->username !==
'' ) {
332 if ( $guessUserName ===
null ) {
333 $guessUserName = $req->username;
334 } elseif ( $guessUserName !== $req->username ) {
335 $guessUserName =
null;
344 $reqs, CreatedAccountAuthenticationRequest::class
347 if ( !in_array( $req, $this->createdAccountAuthenticationRequests,
true ) ) {
348 throw new \LogicException(
349 'CreatedAccountAuthenticationRequests are only valid on ' .
350 'the same AuthManager that created the account'
357 throw new \UnexpectedValueException(
358 "CreatedAccountAuthenticationRequest had invalid username \"{$req->username}\""
360 } elseif ( $user->getId() != $req->id ) {
361 throw new \UnexpectedValueException(
362 "ID for \"{$req->username}\" was {$user->getId()}, expected {$req->id}"
367 $this->logger->info(
'Logging in {user} after account creation', [
368 'user' => $user->getName(),
373 $session->remove(
'AuthManager::authnState' );
375 $ret, $user, $user->getName(), [] );
382 $status = $provider->testForAuthentication( $reqs );
383 if ( !$status->isGood() ) {
384 $this->logger->debug(
'Login failed in pre-authentication by ' . $provider->getUniqueId() );
391 $this->
getHookRunner()->onAuthManagerLoginAuthenticateAudit( $ret,
null, $guessUserName, [] );
398 'returnToUrl' => $returnToUrl,
399 'guessUserName' => $guessUserName,
401 'primaryResponse' =>
null,
404 'continueRequests' => [],
409 $reqs, CreateFromLoginAuthenticationRequest::class
412 $state[
'maybeLink'] = $req->maybeLink;
415 $session = $this->request->getSession();
416 $session->setSecret(
'AuthManager::authnState', $state );
445 $session = $this->request->getSession();
447 if ( !$session->canSetUser() ) {
450 throw new \LogicException(
'Authentication is not possible now' );
454 $state = $session->getSecret(
'AuthManager::authnState' );
455 if ( !is_array( $state ) ) {
457 wfMessage(
'authmanager-authn-not-in-progress' )
460 $state[
'continueRequests'] = [];
462 $guessUserName = $state[
'guessUserName'];
464 foreach ( $reqs as $req ) {
465 $req->returnToUrl = $state[
'returnToUrl'];
470 if ( $state[
'primary'] ===
null ) {
473 $guessUserName =
null;
474 foreach ( $reqs as $req ) {
475 if ( $req->username !==
null && $req->username !==
'' ) {
476 if ( $guessUserName ===
null ) {
477 $guessUserName = $req->username;
478 } elseif ( $guessUserName !== $req->username ) {
479 $guessUserName =
null;
484 $state[
'guessUserName'] = $guessUserName;
486 $state[
'reqs'] = $reqs;
489 $res = $provider->beginPrimaryAuthentication( $reqs );
490 switch (
$res->status ) {
492 $state[
'primary'] = $id;
493 $state[
'primaryResponse'] =
$res;
494 $this->logger->debug(
"Primary login with $id succeeded" );
497 $this->logger->debug(
"Login failed in primary authentication by $id" );
498 if (
$res->createRequest || $state[
'maybeLink'] ) {
500 $res->createRequest, $state[
'maybeLink']
506 $session->remove(
'AuthManager::authnState' );
508 $res,
null, $guessUserName, [] );
515 $this->logger->debug(
"Primary login with $id returned $res->status" );
516 $this->
fillRequests(
$res->neededRequests, self::ACTION_LOGIN, $guessUserName );
517 $state[
'primary'] = $id;
518 $state[
'continueRequests'] =
$res->neededRequests;
519 $session->setSecret(
'AuthManager::authnState', $state );
524 throw new \DomainException(
525 get_class( $provider ) .
"::beginPrimaryAuthentication() returned $res->status"
530 if ( $state[
'primary'] ===
null ) {
531 $this->logger->debug(
'Login failed in primary authentication because no provider accepted' );
533 wfMessage(
'authmanager-authn-no-primary' )
538 $session->remove(
'AuthManager::authnState' );
541 } elseif ( $state[
'primaryResponse'] ===
null ) {
547 wfMessage(
'authmanager-authn-not-in-progress' )
552 $session->remove(
'AuthManager::authnState' );
556 $id = $provider->getUniqueId();
557 $res = $provider->continuePrimaryAuthentication( $reqs );
558 switch (
$res->status ) {
560 $state[
'primaryResponse'] =
$res;
561 $this->logger->debug(
"Primary login with $id succeeded" );
564 $this->logger->debug(
"Login failed in primary authentication by $id" );
565 if (
$res->createRequest || $state[
'maybeLink'] ) {
567 $res->createRequest, $state[
'maybeLink']
573 $session->remove(
'AuthManager::authnState' );
575 $res,
null, $guessUserName, [] );
579 $this->logger->debug(
"Primary login with $id returned $res->status" );
580 $this->
fillRequests(
$res->neededRequests, self::ACTION_LOGIN, $guessUserName );
581 $state[
'continueRequests'] =
$res->neededRequests;
582 $session->setSecret(
'AuthManager::authnState', $state );
585 throw new \DomainException(
586 get_class( $provider ) .
"::continuePrimaryAuthentication() returned $res->status"
591 $res = $state[
'primaryResponse'];
592 if (
$res->username ===
null ) {
598 wfMessage(
'authmanager-authn-not-in-progress' )
603 $session->remove(
'AuthManager::authnState' );
611 $this->getAuthenticationProvider( ConfirmLinkSecondaryAuthenticationProvider::class )
613 $state[
'maybeLink'][
$res->linkRequest->getUniqueId()] =
$res->linkRequest;
614 $msg =
'authmanager-authn-no-local-user-link';
616 $msg =
'authmanager-authn-no-local-user';
618 $this->logger->debug(
619 "Primary login with {$provider->getUniqueId()} succeeded, but returned no user"
627 if (
$res->createRequest || $state[
'maybeLink'] ) {
629 $res->createRequest, $state[
'maybeLink']
631 $ret->neededRequests[] = $ret->createRequest;
633 $this->
fillRequests( $ret->neededRequests, self::ACTION_LOGIN,
null,
true );
634 $session->setSecret(
'AuthManager::authnState', [
637 'primaryResponse' =>
null,
639 'continueRequests' => $ret->neededRequests,
650 throw new \DomainException(
651 get_class( $provider ) .
" returned an invalid username: {$res->username}"
654 if ( $user->getId() === 0 ) {
656 $this->logger->info(
'Auto-creating {user} on login', [
657 'user' => $user->getName(),
659 $status = $this->
autoCreateUser( $user, $state[
'primary'],
false );
660 if ( !$status->isGood() ) {
662 Status::wrap( $status )->getMessage(
'authmanager-authn-autocreate-failed' )
665 $session->remove(
'AuthManager::authnState' );
667 $ret, $user, $user->getName(), [] );
674 $beginReqs = $state[
'reqs'];
677 if ( !isset( $state[
'secondary'][$id] ) ) {
681 $func =
'beginSecondaryAuthentication';
682 $res = $provider->beginSecondaryAuthentication( $user, $beginReqs );
683 } elseif ( !$state[
'secondary'][$id] ) {
684 $func =
'continueSecondaryAuthentication';
685 $res = $provider->continueSecondaryAuthentication( $user, $reqs );
689 switch (
$res->status ) {
691 $this->logger->debug(
"Secondary login with $id succeeded" );
694 $state[
'secondary'][$id] =
true;
697 $this->logger->debug(
"Login failed in secondary authentication by $id" );
699 $session->remove(
'AuthManager::authnState' );
701 $res, $user, $user->getName(), [] );
705 $this->logger->debug(
"Secondary login with $id returned " .
$res->status );
706 $this->
fillRequests(
$res->neededRequests, self::ACTION_LOGIN, $user->getName() );
707 $state[
'secondary'][$id] =
false;
708 $state[
'continueRequests'] =
$res->neededRequests;
709 $session->setSecret(
'AuthManager::authnState', $state );
714 throw new \DomainException(
715 get_class( $provider ) .
"::{$func}() returned $res->status"
724 $this->logger->info(
'Login for {user} succeeded from {clientip}', [
725 'user' => $user->getName(),
726 'clientip' => $this->request->getIP(),
730 $beginReqs, RememberMeAuthenticationRequest::class
735 $session->remove(
'AuthManager::authnState' );
738 $ret, $user, $user->getName(), [] );
740 }
catch ( \Exception $ex ) {
741 $session->remove(
'AuthManager::authnState' );
760 $this->logger->debug( __METHOD__ .
": Checking $operation" );
762 $session = $this->request->getSession();
763 $aId = $session->getUser()->getId();
767 $this->logger->info( __METHOD__ .
": Not logged in! $operation is $status" );
771 if ( $session->canSetUser() ) {
772 $id = $session->get(
'AuthManager:lastAuthId' );
773 $last = $session->get(
'AuthManager:lastAuthTimestamp' );
774 if ( $id !== $aId || $last ===
null ) {
775 $timeSinceLogin = PHP_INT_MAX;
777 $timeSinceLogin = max( 0, time() - $last );
780 $thresholds = $this->config->get(
'ReauthenticateTime' );
781 if ( isset( $thresholds[$operation] ) ) {
782 $threshold = $thresholds[$operation];
783 } elseif ( isset( $thresholds[
'default'] ) ) {
784 $threshold = $thresholds[
'default'];
786 throw new \UnexpectedValueException(
'$wgReauthenticateTime lacks a default' );
789 if ( $threshold >= 0 && $timeSinceLogin > $threshold ) {
793 $timeSinceLogin = -1;
795 $pass = $this->config->get(
'AllowSecuritySensitiveOperationIfCannotReauthenticate' );
796 if ( isset( $pass[$operation] ) ) {
798 } elseif ( isset( $pass[
'default'] ) ) {
801 throw new \UnexpectedValueException(
802 '$wgAllowSecuritySensitiveOperationIfCannotReauthenticate lacks a default'
808 $status, $operation, $session, $timeSinceLogin );
815 $this->logger->info( __METHOD__ .
": $operation is $status for '{user}'",
817 'user' => $session->getUser()->getName(),
818 'clientip' => $this->getRequest()->getIP(),
836 if ( $provider->testUserCanAuthenticate( $username ) ) {
860 $normalized = $provider->providerNormalizeUsername( $username );
861 if ( $normalized !==
null ) {
862 $ret[$normalized] =
true;
865 return array_keys( $ret );
882 $this->logger->info(
'Revoking access for {user}', [
902 foreach ( $providers as $provider ) {
903 $status = $provider->providerAllowsAuthenticationDataChange( $req, $checkData );
904 if ( !$status->isGood() ) {
908 return $status->hasMessage(
'throttled-mailpassword' )
912 $any = $any || $status->value !==
'ignored';
916 $status->warning(
'authmanager-change-not-supported' );
940 $this->logger->info(
'Changing authentication data for {user} class {what}', [
941 'user' => is_string( $req->username ) ? $req->username :
'<no name>',
942 'what' => get_class( $req ),
949 if ( !$isAddition ) {
966 switch ( $provider->accountCreationType() ) {
985 if ( is_int( $options ) ) {
986 $options = [
'flags' => $options ];
989 'flags' => User::READ_NORMAL,
992 $flags = $options[
'flags'];
998 if ( $this->
userExists( $username, $flags ) ) {
1003 if ( !is_object( $user ) ) {
1006 $user->load( $flags );
1007 if ( $user->getId() !== 0 ) {
1016 foreach ( $providers as $provider ) {
1017 $status = $provider->testUserForCreation( $user,
false, $options );
1018 if ( !$status->isGood() ) {
1033 if ( $this->readOnlyMode->isReadOnly() ) {
1052 $this->blockErrorFormatter->getMessage( $block, $creator, $language, $ip )
1056 if ( $this->blockManager->isDnsBlacklisted( $ip,
true ) ) {
1083 $session = $this->request->getSession();
1086 $session->remove(
'AuthManager::accountCreationState' );
1087 throw new \LogicException(
'Account creation is not possible' );
1092 }
catch ( \UnexpectedValueException $ex ) {
1095 if ( $username ===
null ) {
1096 $this->logger->debug( __METHOD__ .
': No username provided' );
1102 if ( !$status->isGood() ) {
1103 $this->logger->debug( __METHOD__ .
': {creator} cannot create users: {reason}', [
1104 'user' => $username,
1105 'creator' => $creator->
getName(),
1106 'reason' => $status->getWikiText(
null,
null,
'en' )
1112 $username, [
'flags' => User::READ_LOCKING,
'creating' =>
true ]
1114 if ( !$status->isGood() ) {
1115 $this->logger->debug( __METHOD__ .
': {user} cannot be created: {reason}', [
1116 'user' => $username,
1117 'creator' => $creator->
getName(),
1118 'reason' => $status->getWikiText(
null,
null,
'en' )
1124 foreach ( $reqs as $req ) {
1125 $req->username = $username;
1126 $req->returnToUrl = $returnToUrl;
1128 $status = $req->populateUser( $user );
1129 if ( !$status->isGood() ) {
1131 $session->remove(
'AuthManager::accountCreationState' );
1132 $this->logger->debug( __METHOD__ .
': UserData is invalid: {reason}', [
1133 'user' => $user->getName(),
1134 'creator' => $creator->
getName(),
1135 'reason' => $status->getWikiText(
null,
null,
'en' ),
1145 'username' => $username,
1147 'creatorid' => $creator->
getId(),
1148 'creatorname' => $creator->
getName(),
1150 'returnToUrl' => $returnToUrl,
1152 'primaryResponse' =>
null,
1154 'continueRequests' => [],
1156 'ranPreTests' =>
false,
1161 $reqs, CreateFromLoginAuthenticationRequest::class
1164 $state[
'maybeLink'] = $req->maybeLink;
1166 if ( $req->createRequest ) {
1167 $reqs[] = $req->createRequest;
1168 $state[
'reqs'][] = $req->createRequest;
1172 $session->setSecret(
'AuthManager::accountCreationState', $state );
1173 $session->persist();
1184 $session = $this->request->getSession();
1188 $session->remove(
'AuthManager::accountCreationState' );
1189 throw new \LogicException(
'Account creation is not possible' );
1192 $state = $session->getSecret(
'AuthManager::accountCreationState' );
1193 if ( !is_array( $state ) ) {
1195 wfMessage(
'authmanager-create-not-in-progress' )
1198 $state[
'continueRequests'] = [];
1203 if ( !is_object( $user ) ) {
1204 $session->remove(
'AuthManager::accountCreationState' );
1205 $this->logger->debug( __METHOD__ .
': Invalid username', [
1206 'user' => $state[
'username'],
1211 if ( $state[
'creatorid'] ) {
1214 $creator =
new User;
1215 $creator->
setName( $state[
'creatorname'] );
1220 $lock =
$cache->getScopedLock(
$cache->makeGlobalKey(
'account', md5( $user->getName() ) ) );
1224 $this->logger->debug( __METHOD__ .
': Could not acquire account creation lock', [
1225 'user' => $user->getName(),
1226 'creator' => $creator->getName(),
1233 if ( !$status->isGood() ) {
1234 $this->logger->debug( __METHOD__ .
': {creator} cannot create users: {reason}', [
1235 'user' => $user->getName(),
1236 'creator' => $creator->getName(),
1237 'reason' => $status->getWikiText(
null,
null,
'en' )
1241 $session->remove(
'AuthManager::accountCreationState' );
1246 $user->load( User::READ_LOCKING );
1248 if ( $state[
'userid'] === 0 ) {
1249 if ( $user->getId() !== 0 ) {
1250 $this->logger->debug( __METHOD__ .
': User exists locally', [
1251 'user' => $user->getName(),
1252 'creator' => $creator->getName(),
1256 $session->remove(
'AuthManager::accountCreationState' );
1260 if ( $user->getId() === 0 ) {
1261 $this->logger->debug( __METHOD__ .
': User does not exist locally when it should', [
1262 'user' => $user->getName(),
1263 'creator' => $creator->getName(),
1264 'expected_id' => $state[
'userid'],
1266 throw new \UnexpectedValueException(
1267 "User \"{$state['username']}\" should exist now, but doesn't!"
1270 if ( $user->getId() !== $state[
'userid'] ) {
1271 $this->logger->debug( __METHOD__ .
': User ID/name mismatch', [
1272 'user' => $user->getName(),
1273 'creator' => $creator->getName(),
1274 'expected_id' => $state[
'userid'],
1275 'actual_id' => $user->getId(),
1277 throw new \UnexpectedValueException(
1278 "User \"{$state['username']}\" exists, but " .
1279 "ID {$user->getId()} !== {$state['userid']}!"
1283 foreach ( $state[
'reqs'] as $req ) {
1285 $status = $req->populateUser( $user );
1286 if ( !$status->isGood() ) {
1289 $this->logger->debug( __METHOD__ .
': UserData is invalid: {reason}', [
1290 'user' => $user->getName(),
1291 'creator' => $creator->getName(),
1292 'reason' => $status->getWikiText(
null,
null,
'en' ),
1296 $session->remove(
'AuthManager::accountCreationState' );
1302 foreach ( $reqs as $req ) {
1303 $req->returnToUrl = $state[
'returnToUrl'];
1304 $req->username = $state[
'username'];
1308 if ( !$state[
'ranPreTests'] ) {
1312 foreach ( $providers as $id => $provider ) {
1313 $status = $provider->testForAccountCreation( $user, $creator, $reqs );
1314 if ( !$status->isGood() ) {
1315 $this->logger->debug( __METHOD__ .
": Fail in pre-authentication by $id", [
1316 'user' => $user->getName(),
1317 'creator' => $creator->getName(),
1323 $session->remove(
'AuthManager::accountCreationState' );
1328 $state[
'ranPreTests'] =
true;
1333 if ( $state[
'primary'] ===
null ) {
1339 $res = $provider->beginPrimaryAccountCreation( $user, $creator, $reqs );
1340 switch (
$res->status ) {
1342 $this->logger->debug( __METHOD__ .
": Primary creation passed by $id", [
1343 'user' => $user->getName(),
1344 'creator' => $creator->getName(),
1346 $state[
'primary'] = $id;
1347 $state[
'primaryResponse'] =
$res;
1350 $this->logger->debug( __METHOD__ .
": Primary creation failed by $id", [
1351 'user' => $user->getName(),
1352 'creator' => $creator->getName(),
1355 $session->remove(
'AuthManager::accountCreationState' );
1362 $this->logger->debug( __METHOD__ .
": Primary creation $res->status by $id", [
1363 'user' => $user->getName(),
1364 'creator' => $creator->getName(),
1367 $state[
'primary'] = $id;
1368 $state[
'continueRequests'] =
$res->neededRequests;
1369 $session->setSecret(
'AuthManager::accountCreationState', $state );
1374 throw new \DomainException(
1375 get_class( $provider ) .
"::beginPrimaryAccountCreation() returned $res->status"
1380 if ( $state[
'primary'] ===
null ) {
1381 $this->logger->debug( __METHOD__ .
': Primary creation failed because no provider accepted', [
1382 'user' => $user->getName(),
1383 'creator' => $creator->getName(),
1386 wfMessage(
'authmanager-create-no-primary' )
1389 $session->remove(
'AuthManager::accountCreationState' );
1392 } elseif ( $state[
'primaryResponse'] ===
null ) {
1398 wfMessage(
'authmanager-create-not-in-progress' )
1401 $session->remove(
'AuthManager::accountCreationState' );
1405 $id = $provider->getUniqueId();
1406 $res = $provider->continuePrimaryAccountCreation( $user, $creator, $reqs );
1407 switch (
$res->status ) {
1409 $this->logger->debug( __METHOD__ .
": Primary creation passed by $id", [
1410 'user' => $user->getName(),
1411 'creator' => $creator->getName(),
1413 $state[
'primaryResponse'] =
$res;
1416 $this->logger->debug( __METHOD__ .
": Primary creation failed by $id", [
1417 'user' => $user->getName(),
1418 'creator' => $creator->getName(),
1421 $session->remove(
'AuthManager::accountCreationState' );
1425 $this->logger->debug( __METHOD__ .
": Primary creation $res->status by $id", [
1426 'user' => $user->getName(),
1427 'creator' => $creator->getName(),
1430 $state[
'continueRequests'] =
$res->neededRequests;
1431 $session->setSecret(
'AuthManager::accountCreationState', $state );
1434 throw new \DomainException(
1435 get_class( $provider ) .
"::continuePrimaryAccountCreation() returned $res->status"
1443 if ( $state[
'userid'] === 0 ) {
1444 $this->logger->info(
'Creating user {user} during account creation', [
1445 'user' => $user->getName(),
1446 'creator' => $creator->getName(),
1448 $status = $user->addToDatabase();
1449 if ( !$status->isOK() ) {
1453 $session->remove(
'AuthManager::accountCreationState' );
1459 $user->saveSettings();
1460 $state[
'userid'] = $user->getId();
1469 $logSubtype = $provider->finishAccountCreation( $user, $creator, $state[
'primaryResponse'] );
1472 if ( $this->config->get(
'NewUserLog' ) ) {
1473 $isAnon = $creator->isAnon();
1474 $logEntry = new \ManualLogEntry(
1476 $logSubtype ?: ( $isAnon ?
'create' :
'create2' )
1478 $logEntry->setPerformer( $isAnon ? $user : $creator );
1479 $logEntry->setTarget( $user->getUserPage() );
1482 $state[
'reqs'], CreationReasonAuthenticationRequest::class
1484 $logEntry->setComment( $req ? $req->reason :
'' );
1485 $logEntry->setParameters( [
1486 '4::userid' => $user->getId(),
1488 $logid = $logEntry->insert();
1489 $logEntry->publish( $logid );
1495 $beginReqs = $state[
'reqs'];
1498 if ( !isset( $state[
'secondary'][$id] ) ) {
1502 $func =
'beginSecondaryAccountCreation';
1503 $res = $provider->beginSecondaryAccountCreation( $user, $creator, $beginReqs );
1504 } elseif ( !$state[
'secondary'][$id] ) {
1505 $func =
'continueSecondaryAccountCreation';
1506 $res = $provider->continueSecondaryAccountCreation( $user, $creator, $reqs );
1510 switch (
$res->status ) {
1512 $this->logger->debug( __METHOD__ .
": Secondary creation passed by $id", [
1513 'user' => $user->getName(),
1514 'creator' => $creator->getName(),
1518 $state[
'secondary'][$id] =
true;
1522 $this->logger->debug( __METHOD__ .
": Secondary creation $res->status by $id", [
1523 'user' => $user->getName(),
1524 'creator' => $creator->getName(),
1527 $state[
'secondary'][$id] =
false;
1528 $state[
'continueRequests'] =
$res->neededRequests;
1529 $session->setSecret(
'AuthManager::accountCreationState', $state );
1532 throw new \DomainException(
1533 get_class( $provider ) .
"::{$func}() returned $res->status." .
1534 ' Secondary providers are not allowed to fail account creation, that' .
1535 ' should have been done via testForAccountCreation().'
1539 throw new \DomainException(
1540 get_class( $provider ) .
"::{$func}() returned $res->status"
1546 $id = $user->getId();
1547 $name = $user->getName();
1550 $ret->loginRequest = $req;
1551 $this->createdAccountAuthenticationRequests[] = $req;
1553 $this->logger->info( __METHOD__ .
': Account creation succeeded for {user}', [
1554 'user' => $user->getName(),
1555 'creator' => $creator->getName(),
1559 $session->remove(
'AuthManager::accountCreationState' );
1562 }
catch ( \Exception $ex ) {
1563 $session->remove(
'AuthManager::accountCreationState' );
1587 if (
$source !== self::AUTOCREATE_SOURCE_SESSION &&
1588 $source !== self::AUTOCREATE_SOURCE_MAINT &&
1591 throw new \InvalidArgumentException(
"Unknown auto-creation source: $source" );
1598 $flags = User::READ_NORMAL;
1608 $flags = User::READ_LATEST;
1613 $this->logger->debug( __METHOD__ .
': {username} already exists locally', [
1614 'username' => $username,
1616 $user->
setId( $localId );
1622 $status->warning(
'userexists' );
1627 if ( $this->readOnlyMode->isReadOnly() ) {
1628 $reason = $this->readOnlyMode->getReason();
1629 $this->logger->debug( __METHOD__ .
': denied because of read only mode: {reason}', [
1630 'username' => $username,
1631 'reason' => $reason,
1640 $session = $this->request->getSession();
1641 if ( $session->get(
'AuthManager::AutoCreateBlacklist' ) ) {
1642 $this->logger->debug( __METHOD__ .
': blacklisted in session {sessionid}', [
1643 'username' => $username,
1644 'sessionid' => $session->getId(),
1648 $reason = $session->get(
'AuthManager::AutoCreateBlacklist' );
1657 if ( !$this->userNameUtils->isCreatable( $username ) ) {
1658 $this->logger->debug( __METHOD__ .
': name "{username}" is not creatable', [
1659 'username' => $username,
1661 $session->set(
'AuthManager::AutoCreateBlacklist',
'noname' );
1669 if (
$source !== self::AUTOCREATE_SOURCE_MAINT &&
1670 !$anon->isAllowedAny(
'createaccount',
'autocreateaccount' )
1672 $this->logger->debug( __METHOD__ .
': IP lacks the ability to create or autocreate accounts', [
1673 'username' => $username,
1674 'clientip' => $anon->getName(),
1676 $session->set(
'AuthManager::AutoCreateBlacklist',
'authmanager-autocreate-noperm' );
1677 $session->persist();
1685 $lock =
$cache->getScopedLock(
$cache->makeGlobalKey(
'account', md5( $username ) ) );
1687 $this->logger->debug( __METHOD__ .
': Could not acquire account creation lock', [
1688 'user' => $username,
1697 'flags' => User::READ_LATEST,
1703 foreach ( $providers as $provider ) {
1704 $status = $provider->testUserForCreation( $user,
$source, $options );
1705 if ( !$status->isGood() ) {
1707 $this->logger->debug( __METHOD__ .
': Provider denied creation of {username}: {reason}', [
1708 'username' => $username,
1709 'reason' => $ret->getWikiText(
null,
null,
'en' ),
1711 $session->set(
'AuthManager::AutoCreateBlacklist', $status );
1718 $backoffKey =
$cache->makeKey(
'AuthManager',
'autocreate-failed', md5( $username ) );
1719 if (
$cache->get( $backoffKey ) ) {
1720 $this->logger->debug( __METHOD__ .
': {username} denied by prior creation attempt failures', [
1721 'username' => $username,
1729 $from = $_SERVER[
'REQUEST_URI'] ??
'CLI';
1730 $this->logger->info( __METHOD__ .
': creating new user ({username}) - from: {from}', [
1731 'username' => $username,
1737 $old = $trxProfiler->setSilenced(
true );
1740 if ( !$status->isOK() ) {
1743 if ( $user->
getId() ) {
1744 $this->logger->info( __METHOD__ .
': {username} already exists locally (race)', [
1745 'username' => $username,
1751 $status->warning(
'userexists' );
1753 $this->logger->error( __METHOD__ .
': {username} failed with message {msg}', [
1754 'username' => $username,
1755 'msg' => $status->getWikiText(
null,
null,
'en' )
1762 }
catch ( \Exception $ex ) {
1763 $trxProfiler->setSilenced( $old );
1764 $this->logger->error( __METHOD__ .
': {username} failed with exception {exception}', [
1765 'username' => $username,
1769 $cache->set( $backoffKey, 1, 600 );
1790 if ( $this->config->get(
'NewUserLog' ) && $log ) {
1791 $logEntry = new \ManualLogEntry(
'newusers',
'autocreate' );
1792 $logEntry->setPerformer( $user );
1794 $logEntry->setComment(
'' );
1795 $logEntry->setParameters( [
1796 '4::userid' => $user->
getId(),
1798 $logEntry->insert();
1801 $trxProfiler->setSilenced( $old );
1839 $session = $this->request->getSession();
1840 $session->remove(
'AuthManager::accountLinkState' );
1844 throw new \LogicException(
'Account linking is not possible' );
1847 if ( $user->
getId() === 0 ) {
1848 if ( !$this->userNameUtils->isUsable( $user->
getName() ) ) {
1855 foreach ( $reqs as $req ) {
1856 $req->username = $user->
getName();
1857 $req->returnToUrl = $returnToUrl;
1863 foreach ( $providers as $id => $provider ) {
1864 $status = $provider->testForAccountLink( $user );
1865 if ( !$status->isGood() ) {
1866 $this->logger->debug( __METHOD__ .
": Account linking pre-check failed by $id", [
1878 'username' => $user->
getName(),
1879 'userid' => $user->
getId(),
1880 'returnToUrl' => $returnToUrl,
1882 'continueRequests' => [],
1886 foreach ( $providers as $id => $provider ) {
1891 $res = $provider->beginPrimaryAccountLink( $user, $reqs );
1892 switch (
$res->status ) {
1894 $this->logger->info(
"Account linked to {user} by $id", [
1901 $this->logger->debug( __METHOD__ .
": Account linking failed by $id", [
1913 $this->logger->debug( __METHOD__ .
": Account linking $res->status by $id", [
1917 $state[
'primary'] = $id;
1918 $state[
'continueRequests'] =
$res->neededRequests;
1919 $session->setSecret(
'AuthManager::accountLinkState', $state );
1920 $session->persist();
1925 throw new \DomainException(
1926 get_class( $provider ) .
"::beginPrimaryAccountLink() returned $res->status"
1932 $this->logger->debug( __METHOD__ .
': Account linking failed because no provider accepted', [
1936 wfMessage(
'authmanager-link-no-primary' )
1948 $session = $this->request->getSession();
1952 $session->remove(
'AuthManager::accountLinkState' );
1953 throw new \LogicException(
'Account linking is not possible' );
1956 $state = $session->getSecret(
'AuthManager::accountLinkState' );
1957 if ( !is_array( $state ) ) {
1959 wfMessage(
'authmanager-link-not-in-progress' )
1962 $state[
'continueRequests'] = [];
1967 if ( !is_object( $user ) ) {
1968 $session->remove(
'AuthManager::accountLinkState' );
1971 if ( $user->getId() !== $state[
'userid'] ) {
1972 throw new \UnexpectedValueException(
1973 "User \"{$state['username']}\" is valid, but " .
1974 "ID {$user->getId()} !== {$state['userid']}!"
1978 foreach ( $reqs as $req ) {
1979 $req->username = $state[
'username'];
1980 $req->returnToUrl = $state[
'returnToUrl'];
1990 wfMessage(
'authmanager-link-not-in-progress' )
1993 $session->remove(
'AuthManager::accountLinkState' );
1997 $id = $provider->getUniqueId();
1998 $res = $provider->continuePrimaryAccountLink( $user, $reqs );
1999 switch (
$res->status ) {
2001 $this->logger->info(
"Account linked to {user} by $id", [
2002 'user' => $user->getName(),
2005 $session->remove(
'AuthManager::accountLinkState' );
2008 $this->logger->debug( __METHOD__ .
": Account linking failed by $id", [
2009 'user' => $user->getName(),
2012 $session->remove(
'AuthManager::accountLinkState' );
2016 $this->logger->debug( __METHOD__ .
": Account linking $res->status by $id", [
2017 'user' => $user->getName(),
2019 $this->
fillRequests(
$res->neededRequests, self::ACTION_LINK, $user->getName() );
2020 $state[
'continueRequests'] =
$res->neededRequests;
2021 $session->setSecret(
'AuthManager::accountLinkState', $state );
2024 throw new \DomainException(
2025 get_class( $provider ) .
"::continuePrimaryAccountLink() returned $res->status"
2028 }
catch ( \Exception $ex ) {
2029 $session->remove(
'AuthManager::accountLinkState' );
2072 $state = $this->request->getSession()->getSecret(
'AuthManager::authnState' );
2073 return is_array( $state ) ? $state[
'continueRequests'] : [];
2076 $state = $this->request->getSession()->getSecret(
'AuthManager::accountCreationState' );
2077 return is_array( $state ) ? $state[
'continueRequests'] : [];
2101 $state = $this->request->getSession()->getSecret(
'AuthManager::accountLinkState' );
2102 return is_array( $state ) ? $state[
'continueRequests'] : [];
2112 throw new \DomainException( __METHOD__ .
": Invalid action \"$action\"" );
2129 $providerAction, array $options, array $providers,
User $user =
null
2132 $options[
'username'] = $user->isAnon() ? null : $user->getName();
2136 foreach ( $providers as $provider ) {
2138 foreach ( $provider->getAuthenticationRequests( $providerAction, $options ) as $req ) {
2142 if ( $isPrimary && $req->required ) {
2147 !isset( $reqs[$id] )
2157 switch ( $providerAction ) {
2165 if ( $options[
'username'] !==
null ) {
2167 $options[
'username'] =
null;
2173 $this->
fillRequests( $reqs, $providerAction, $options[
'username'],
true );
2176 if ( $providerAction === self::ACTION_CHANGE || $providerAction === self::ACTION_REMOVE ) {
2177 $reqs = array_filter( $reqs,
function ( $req ) {
2182 return array_values( $reqs );
2193 foreach ( $reqs as $req ) {
2194 if ( !$req->action || $forceAction ) {
2197 if ( $req->username ===
null ) {
2198 $req->username = $username;
2209 public function userExists( $username, $flags = User::READ_NORMAL ) {
2211 if ( $provider->testUserExists( $username, $flags ) ) {
2233 foreach ( $providers as $provider ) {
2234 if ( !$provider->providerAllowsPropertyChange( $property ) ) {
2251 if ( isset( $this->allAuthenticationProviders[$id] ) ) {
2252 return $this->allAuthenticationProviders[$id];
2257 if ( isset( $providers[$id] ) ) {
2258 return $providers[$id];
2261 if ( isset( $providers[$id] ) ) {
2262 return $providers[$id];
2265 if ( isset( $providers[$id] ) ) {
2266 return $providers[$id];
2285 $session = $this->request->getSession();
2286 $arr = $session->getSecret(
'authData' );
2287 if ( !is_array( $arr ) ) {
2291 $session->setSecret(
'authData', $arr );
2302 $arr = $this->request->getSession()->getSecret(
'authData' );
2303 if ( is_array( $arr ) && array_key_exists( $key, $arr ) ) {
2316 $session = $this->request->getSession();
2317 if ( $key ===
null ) {
2318 $session->remove(
'authData' );
2320 $arr = $session->getSecret(
'authData' );
2321 if ( is_array( $arr ) && array_key_exists( $key, $arr ) ) {
2322 unset( $arr[$key] );
2323 $session->setSecret(
'authData', $arr );
2336 foreach ( $specs as &$spec ) {
2337 $spec = [
'sort2' => $i++ ] + $spec + [
'sort' => 0 ];
2341 usort( $specs,
static function ( $a, $b ) {
2342 return $a[
'sort'] <=> $b[
'sort']
2343 ?: $a[
'sort2'] <=> $b[
'sort2'];
2347 foreach ( $specs as $spec ) {
2349 $provider = $this->objectFactory->createObject( $spec, [
'assertClass' => $class ] );
2350 $provider->setLogger( $this->logger );
2351 $provider->setManager( $this );
2352 $provider->setConfig( $this->config );
2354 $id = $provider->getUniqueId();
2355 if ( isset( $this->allAuthenticationProviders[$id] ) ) {
2356 throw new \RuntimeException(
2357 "Duplicate specifications for id $id (classes " .
2358 get_class( $provider ) .
' and ' .
2359 get_class( $this->allAuthenticationProviders[$id] ) .
')'
2362 $this->allAuthenticationProviders[$id] = $provider;
2363 $ret[$id] = $provider;
2372 return $this->config->get(
'AuthManagerConfig' ) ?: $this->config->get(
'AuthManagerAutoConfig' );
2380 if ( $this->preAuthenticationProviders ===
null ) {
2383 PreAuthenticationProvider::class, $conf[
'preauth']
2394 if ( $this->primaryAuthenticationProviders ===
null ) {
2397 PrimaryAuthenticationProvider::class, $conf[
'primaryauth']
2408 if ( $this->secondaryAuthenticationProviders ===
null ) {
2411 SecondaryAuthenticationProvider::class, $conf[
'secondaryauth']
2423 $session = $this->request->getSession();
2424 $delay = $session->delaySave();
2426 $session->resetId();
2427 $session->resetAllTokens();
2428 if ( $session->canSetUser() ) {
2429 $session->setUser( $user );
2431 if ( $remember !==
null ) {
2432 $session->setRememberUser( $remember );
2434 $session->set(
'AuthManager:lastAuthId', $user->getId() );
2435 $session->set(
'AuthManager:lastAuthTimestamp', time() );
2436 $session->persist();
2438 \Wikimedia\ScopedCallback::consume( $delay );
2456 ->getLanguageConverter();
2457 if ( $contLangConverter->hasVariants() ) {
2458 $user->
setOption(
'variant', $contLangConverter->getPreferredVariant() );
2478 foreach ( $providers as $provider ) {
2479 $provider->$method( ...
$args );
2489 if ( !defined(
'MW_PHPUNIT_TEST' ) ) {
2491 throw new \MWException( __METHOD__ .
' may only be called from unit tests!' );
2495 self::$instance =
null;