108 if ( self::$instance === null ) {
109 self::$instance =
new self(
114 return self::$instance;
148 $this->logger->warning(
"Overriding AuthManager primary authn because $why" );
150 if ( $this->primaryAuthenticationProviders !== null ) {
151 $this->logger->warning(
152 'PrimaryAuthenticationProviders have already been accessed! I hope nothing breaks.'
155 $this->allAuthenticationProviders = array_diff_key(
156 $this->allAuthenticationProviders,
157 $this->primaryAuthenticationProviders
159 $session = $this->
request->getSession();
160 $session->remove(
'AuthManager::authnState' );
161 $session->remove(
'AuthManager::accountCreationState' );
162 $session->remove(
'AuthManager::accountLinkState' );
163 $this->createdAccountAuthenticationRequests = [];
166 $this->primaryAuthenticationProviders = [];
167 foreach ( $providers
as $provider ) {
169 throw new \RuntimeException(
170 'Expected instance of MediaWiki\\Auth\\PrimaryAuthenticationProvider, got ' .
171 get_class( $provider )
174 $provider->setLogger( $this->logger );
175 $provider->setManager( $this );
176 $provider->setConfig( $this->config );
177 $id = $provider->getUniqueId();
178 if ( isset( $this->allAuthenticationProviders[$id] ) ) {
179 throw new \RuntimeException(
180 "Duplicate specifications for id $id (classes " .
181 get_class( $provider ) .
' and ' .
182 get_class( $this->allAuthenticationProviders[$id] ) .
')'
185 $this->allAuthenticationProviders[$id] = $provider;
186 $this->primaryAuthenticationProviders[$id] = $provider;
203 return call_user_func_array( [ $wgAuth, $method ], $params );
223 return $this->
request->getSession()->canSetUser();
245 $session = $this->
request->getSession();
246 if ( !$session->canSetUser() ) {
248 $session->remove(
'AuthManager::authnState' );
249 throw new \LogicException(
'Authentication is not possible now' );
252 $guessUserName = null;
253 foreach ( $reqs
as $req ) {
254 $req->returnToUrl = $returnToUrl;
256 if ( $req->username !== null && $req->username !==
'' ) {
257 if ( $guessUserName === null ) {
258 $guessUserName = $req->username;
259 } elseif ( $guessUserName !== $req->username ) {
260 $guessUserName = null;
272 if ( !in_array( $req, $this->createdAccountAuthenticationRequests,
true ) ) {
273 throw new \LogicException(
274 'CreatedAccountAuthenticationRequests are only valid on ' .
275 'the same AuthManager that created the account'
282 throw new \UnexpectedValueException(
283 "CreatedAccountAuthenticationRequest had invalid username \"{$req->username}\""
285 } elseif (
$user->getId() != $req->id ) {
286 throw new \UnexpectedValueException(
287 "ID for \"{$req->username}\" was {$user->getId()}, expected {$req->id}"
292 $this->logger->info(
'Logging in {user} after account creation', [
293 'user' =>
$user->getName(),
298 $session->remove(
'AuthManager::authnState' );
306 $status = $provider->testForAuthentication( $reqs );
308 $this->logger->debug(
'Login failed in pre-authentication by ' . $provider->getUniqueId() );
315 \Hooks::run(
'AuthManagerLoginAuthenticateAudit', [
$ret, null, $guessUserName ] );
322 'returnToUrl' => $returnToUrl,
323 'guessUserName' => $guessUserName,
325 'primaryResponse' => null,
328 'continueRequests' => [],
336 $state[
'maybeLink'] = $req->maybeLink;
339 $session = $this->
request->getSession();
340 $session->setSecret(
'AuthManager::authnState', $state );
369 $session = $this->
request->getSession();
371 if ( !$session->canSetUser() ) {
374 throw new \LogicException(
'Authentication is not possible now' );
378 $state = $session->getSecret(
'AuthManager::authnState' );
379 if ( !is_array( $state ) ) {
381 wfMessage(
'authmanager-authn-not-in-progress' )
384 $state[
'continueRequests'] = [];
386 $guessUserName = $state[
'guessUserName'];
388 foreach ( $reqs
as $req ) {
389 $req->returnToUrl = $state[
'returnToUrl'];
394 if ( $state[
'primary'] === null ) {
397 $guessUserName = null;
398 foreach ( $reqs
as $req ) {
399 if ( $req->username !== null && $req->username !==
'' ) {
400 if ( $guessUserName === null ) {
401 $guessUserName = $req->username;
402 } elseif ( $guessUserName !== $req->username ) {
403 $guessUserName = null;
408 $state[
'guessUserName'] = $guessUserName;
410 $state[
'reqs'] = $reqs;
413 $res = $provider->beginPrimaryAuthentication( $reqs );
414 switch (
$res->status ) {
416 $state[
'primary'] = $id;
417 $state[
'primaryResponse'] =
$res;
418 $this->logger->debug(
"Primary login with $id succeeded" );
421 $this->logger->debug(
"Login failed in primary authentication by $id" );
422 if (
$res->createRequest || $state[
'maybeLink'] ) {
424 $res->createRequest, $state[
'maybeLink']
430 $session->remove(
'AuthManager::authnState' );
431 \Hooks::run(
'AuthManagerLoginAuthenticateAudit', [
$res, null, $guessUserName ] );
438 $this->logger->debug(
"Primary login with $id returned $res->status" );
439 $this->
fillRequests(
$res->neededRequests, self::ACTION_LOGIN, $guessUserName );
440 $state[
'primary'] = $id;
441 $state[
'continueRequests'] =
$res->neededRequests;
442 $session->setSecret(
'AuthManager::authnState', $state );
447 throw new \DomainException(
448 get_class( $provider ) .
"::beginPrimaryAuthentication() returned $res->status"
453 if ( $state[
'primary'] === null ) {
454 $this->logger->debug(
'Login failed in primary authentication because no provider accepted' );
456 wfMessage(
'authmanager-authn-no-primary' )
461 $session->remove(
'AuthManager::authnState' );
464 } elseif ( $state[
'primaryResponse'] === null ) {
470 wfMessage(
'authmanager-authn-not-in-progress' )
475 $session->remove(
'AuthManager::authnState' );
479 $id = $provider->getUniqueId();
480 $res = $provider->continuePrimaryAuthentication( $reqs );
481 switch (
$res->status ) {
483 $state[
'primaryResponse'] =
$res;
484 $this->logger->debug(
"Primary login with $id succeeded" );
487 $this->logger->debug(
"Login failed in primary authentication by $id" );
488 if (
$res->createRequest || $state[
'maybeLink'] ) {
490 $res->createRequest, $state[
'maybeLink']
496 $session->remove(
'AuthManager::authnState' );
497 \Hooks::run(
'AuthManagerLoginAuthenticateAudit', [
$res, null, $guessUserName ] );
501 $this->logger->debug(
"Primary login with $id returned $res->status" );
502 $this->
fillRequests(
$res->neededRequests, self::ACTION_LOGIN, $guessUserName );
503 $state[
'continueRequests'] =
$res->neededRequests;
504 $session->setSecret(
'AuthManager::authnState', $state );
507 throw new \DomainException(
508 get_class( $provider ) .
"::continuePrimaryAuthentication() returned $res->status"
513 $res = $state[
'primaryResponse'];
514 if (
$res->username === null ) {
520 wfMessage(
'authmanager-authn-not-in-progress' )
525 $session->remove(
'AuthManager::authnState' );
535 $state[
'maybeLink'][
$res->linkRequest->getUniqueId()] =
$res->linkRequest;
536 $msg =
'authmanager-authn-no-local-user-link';
538 $msg =
'authmanager-authn-no-local-user';
540 $this->logger->debug(
541 "Primary login with {$provider->getUniqueId()} succeeded, but returned no user"
549 if (
$res->createRequest || $state[
'maybeLink'] ) {
551 $res->createRequest, $state[
'maybeLink']
553 $ret->neededRequests[] =
$ret->createRequest;
555 $this->
fillRequests(
$ret->neededRequests, self::ACTION_LOGIN, null,
true );
556 $session->setSecret(
'AuthManager::authnState', [
559 'primaryResponse' => null,
561 'continueRequests' =>
$ret->neededRequests,
571 throw new \DomainException(
572 get_class( $provider ) .
" returned an invalid username: {$res->username}"
575 if (
$user->getId() === 0 ) {
577 $this->logger->info(
'Auto-creating {user} on login', [
578 'user' =>
$user->getName(),
586 $session->remove(
'AuthManager::authnState' );
594 $beginReqs = $state[
'reqs'];
597 if ( !isset( $state[
'secondary'][$id] ) ) {
601 $func =
'beginSecondaryAuthentication';
602 $res = $provider->beginSecondaryAuthentication(
$user, $beginReqs );
603 } elseif ( !$state[
'secondary'][$id] ) {
604 $func =
'continueSecondaryAuthentication';
605 $res = $provider->continueSecondaryAuthentication(
$user, $reqs );
609 switch (
$res->status ) {
611 $this->logger->debug(
"Secondary login with $id succeeded" );
614 $state[
'secondary'][$id] =
true;
617 $this->logger->debug(
"Login failed in secondary authentication by $id" );
619 $session->remove(
'AuthManager::authnState' );
624 $this->logger->debug(
"Secondary login with $id returned " . $res->status );
625 $this->
fillRequests( $res->neededRequests, self::ACTION_LOGIN,
$user->getName() );
626 $state[
'secondary'][$id] =
false;
627 $state[
'continueRequests'] = $res->neededRequests;
628 $session->setSecret(
'AuthManager::authnState', $state );
633 throw new \DomainException(
634 get_class( $provider ) .
"::{$func}() returned $res->status"
643 $this->logger->info(
'Login for {user} succeeded', [
644 'user' =>
$user->getName(),
652 $session->remove(
'AuthManager::authnState' );
657 $session->remove(
'AuthManager::authnState' );
676 $this->logger->debug( __METHOD__ .
": Checking $operation" );
678 $session = $this->
request->getSession();
679 $aId = $session->getUser()->getId();
683 $this->logger->info( __METHOD__ .
": Not logged in! $operation is $status" );
687 if ( $session->canSetUser() ) {
688 $id = $session->get(
'AuthManager:lastAuthId' );
689 $last = $session->get(
'AuthManager:lastAuthTimestamp' );
690 if ( $id !== $aId ||
$last === null ) {
691 $timeSinceLogin = PHP_INT_MAX;
693 $timeSinceLogin = max( 0, time() -
$last );
696 $thresholds = $this->config->get(
'ReauthenticateTime' );
697 if ( isset( $thresholds[$operation] ) ) {
698 $threshold = $thresholds[$operation];
699 } elseif ( isset( $thresholds[
'default'] ) ) {
700 $threshold = $thresholds[
'default'];
702 throw new \UnexpectedValueException(
'$wgReauthenticateTime lacks a default' );
705 if ( $threshold >= 0 && $timeSinceLogin > $threshold ) {
709 $timeSinceLogin = -1;
711 $pass = $this->config->get(
'AllowSecuritySensitiveOperationIfCannotReauthenticate' );
712 if ( isset( $pass[$operation] ) ) {
713 $status = $pass[$operation] ? self::SEC_OK : self::SEC_FAIL;
714 } elseif ( isset( $pass[
'default'] ) ) {
715 $status = $pass[
'default'] ? self::SEC_OK : self::SEC_FAIL;
717 throw new \UnexpectedValueException(
718 '$wgAllowSecuritySensitiveOperationIfCannotReauthenticate lacks a default'
724 &
$status, $operation, $session, $timeSinceLogin
732 $this->logger->info( __METHOD__ .
": $operation is $status" );
745 if ( $provider->testUserCanAuthenticate(
$username ) ) {
769 $normalized = $provider->providerNormalizeUsername(
$username );
770 if ( $normalized !== null ) {
771 $ret[$normalized] =
true;
774 return array_keys(
$ret );
792 $this->logger->info(
'Revoking access for {user}', [
811 foreach ( $providers
as $provider ) {
812 $status = $provider->providerAllowsAuthenticationDataChange( $req, $checkData );
816 $any = $any ||
$status->value !==
'ignored';
820 $status->warning(
'authmanager-change-not-supported' );
838 $this->logger->info(
'Changing authentication data for {user} class {what}', [
839 'user' => is_string( $req->username ) ? $req->username :
'<no name>',
840 'what' => get_class( $req ),
863 switch ( $provider->accountCreationType() ) {
888 if ( !is_object(
$user ) ) {
892 if (
$user->getId() !== 0 ) {
901 foreach ( $providers
as $provider ) {
902 $status = $provider->testUserForCreation(
$user,
false );
924 ->getUserPermissionsErrors(
'createaccount', $creator,
'secure' );
927 foreach ( $permErrors
as $args ) {
928 call_user_func_array( [
$status,
'fatal' ], $args );
937 $block->mReason ?:
wfMessage(
'blockednoreason' )->text(),
942 $errorMessage =
'cantcreateaccount-range-text';
943 $errorParams[] = $this->
getRequest()->getIP();
945 $errorMessage =
'cantcreateaccount-text';
979 $session = $this->
request->getSession();
982 $session->remove(
'AuthManager::accountCreationState' );
983 throw new \LogicException(
'Account creation is not possible' );
992 $this->logger->debug( __METHOD__ .
': No username provided' );
999 $this->logger->debug( __METHOD__ .
': {creator} cannot create users: {reason}', [
1001 'creator' => $creator->
getName(),
1002 'reason' =>
$status->getWikiText( null, null,
'en' )
1009 $this->logger->debug( __METHOD__ .
': {user} cannot be created: {reason}', [
1011 'creator' => $creator->
getName(),
1012 'reason' =>
$status->getWikiText( null, null,
'en' )
1018 foreach ( $reqs
as $req ) {
1020 $req->returnToUrl = $returnToUrl;
1025 $session->remove(
'AuthManager::accountCreationState' );
1026 $this->logger->debug( __METHOD__ .
': UserData is invalid: {reason}', [
1027 'user' =>
$user->getName(),
1028 'creator' => $creator->
getName(),
1029 'reason' =>
$status->getWikiText( null, null,
'en' ),
1041 'creatorid' => $creator->
getId(),
1042 'creatorname' => $creator->
getName(),
1044 'returnToUrl' => $returnToUrl,
1046 'primaryResponse' => null,
1048 'continueRequests' => [],
1050 'ranPreTests' =>
false,
1058 $state[
'maybeLink'] = $req->maybeLink;
1060 if ( $req->createRequest ) {
1061 $reqs[] = $req->createRequest;
1062 $state[
'reqs'][] = $req->createRequest;
1066 $session->setSecret(
'AuthManager::accountCreationState', $state );
1067 $session->persist();
1078 $session = $this->
request->getSession();
1082 $session->remove(
'AuthManager::accountCreationState' );
1083 throw new \LogicException(
'Account creation is not possible' );
1086 $state = $session->getSecret(
'AuthManager::accountCreationState' );
1087 if ( !is_array( $state ) ) {
1089 wfMessage(
'authmanager-create-not-in-progress' )
1092 $state[
'continueRequests'] = [];
1097 if ( !is_object(
$user ) ) {
1098 $session->remove(
'AuthManager::accountCreationState' );
1099 $this->logger->debug( __METHOD__ .
': Invalid username', [
1100 'user' => $state[
'username'],
1105 if ( $state[
'creatorid'] ) {
1108 $creator =
new User;
1109 $creator->setName( $state[
'creatorname'] );
1114 $lock =
$cache->getScopedLock(
$cache->makeGlobalKey(
'account', md5(
$user->getName() ) ) );
1118 $this->logger->debug( __METHOD__ .
': Could not acquire account creation lock', [
1119 'user' =>
$user->getName(),
1120 'creator' => $creator->getName(),
1128 $this->logger->debug( __METHOD__ .
': {creator} cannot create users: {reason}', [
1129 'user' =>
$user->getName(),
1130 'creator' => $creator->getName(),
1131 'reason' =>
$status->getWikiText( null, null,
'en' )
1135 $session->remove(
'AuthManager::accountCreationState' );
1142 if ( $state[
'userid'] === 0 ) {
1143 if (
$user->getId() != 0 ) {
1144 $this->logger->debug( __METHOD__ .
': User exists locally', [
1145 'user' =>
$user->getName(),
1146 'creator' => $creator->getName(),
1150 $session->remove(
'AuthManager::accountCreationState' );
1154 if (
$user->getId() == 0 ) {
1155 $this->logger->debug( __METHOD__ .
': User does not exist locally when it should', [
1156 'user' =>
$user->getName(),
1157 'creator' => $creator->getName(),
1158 'expected_id' => $state[
'userid'],
1160 throw new \UnexpectedValueException(
1161 "User \"{$state['username']}\" should exist now, but doesn't!"
1164 if (
$user->getId() != $state[
'userid'] ) {
1165 $this->logger->debug( __METHOD__ .
': User ID/name mismatch', [
1166 'user' =>
$user->getName(),
1167 'creator' => $creator->getName(),
1168 'expected_id' => $state[
'userid'],
1169 'actual_id' =>
$user->getId(),
1171 throw new \UnexpectedValueException(
1172 "User \"{$state['username']}\" exists, but " .
1173 "ID {$user->getId()} != {$state['userid']}!"
1177 foreach ( $state[
'reqs']
as $req ) {
1183 $this->logger->debug( __METHOD__ .
': UserData is invalid: {reason}', [
1184 'user' =>
$user->getName(),
1185 'creator' => $creator->getName(),
1186 'reason' =>
$status->getWikiText( null, null,
'en' ),
1190 $session->remove(
'AuthManager::accountCreationState' );
1196 foreach ( $reqs
as $req ) {
1197 $req->returnToUrl = $state[
'returnToUrl'];
1198 $req->username = $state[
'username'];
1202 if ( !$state[
'ranPreTests'] ) {
1206 foreach ( $providers
as $id => $provider ) {
1207 $status = $provider->testForAccountCreation(
$user, $creator, $reqs );
1209 $this->logger->debug( __METHOD__ .
": Fail in pre-authentication by $id", [
1210 'user' =>
$user->getName(),
1211 'creator' => $creator->getName(),
1217 $session->remove(
'AuthManager::accountCreationState' );
1222 $state[
'ranPreTests'] =
true;
1227 if ( $state[
'primary'] === null ) {
1233 $res = $provider->beginPrimaryAccountCreation(
$user, $creator, $reqs );
1234 switch (
$res->status ) {
1236 $this->logger->debug( __METHOD__ .
": Primary creation passed by $id", [
1237 'user' =>
$user->getName(),
1238 'creator' => $creator->getName(),
1240 $state[
'primary'] = $id;
1241 $state[
'primaryResponse'] =
$res;
1244 $this->logger->debug( __METHOD__ .
": Primary creation failed by $id", [
1245 'user' =>
$user->getName(),
1246 'creator' => $creator->getName(),
1249 $session->remove(
'AuthManager::accountCreationState' );
1256 $this->logger->debug( __METHOD__ .
": Primary creation $res->status by $id", [
1257 'user' =>
$user->getName(),
1258 'creator' => $creator->getName(),
1261 $state[
'primary'] = $id;
1262 $state[
'continueRequests'] =
$res->neededRequests;
1263 $session->setSecret(
'AuthManager::accountCreationState', $state );
1268 throw new \DomainException(
1269 get_class( $provider ) .
"::beginPrimaryAccountCreation() returned $res->status"
1274 if ( $state[
'primary'] === null ) {
1275 $this->logger->debug( __METHOD__ .
': Primary creation failed because no provider accepted', [
1276 'user' =>
$user->getName(),
1277 'creator' => $creator->getName(),
1280 wfMessage(
'authmanager-create-no-primary' )
1283 $session->remove(
'AuthManager::accountCreationState' );
1286 } elseif ( $state[
'primaryResponse'] === null ) {
1292 wfMessage(
'authmanager-create-not-in-progress' )
1295 $session->remove(
'AuthManager::accountCreationState' );
1299 $id = $provider->getUniqueId();
1300 $res = $provider->continuePrimaryAccountCreation(
$user, $creator, $reqs );
1301 switch (
$res->status ) {
1303 $this->logger->debug( __METHOD__ .
": Primary creation passed by $id", [
1304 'user' =>
$user->getName(),
1305 'creator' => $creator->getName(),
1307 $state[
'primaryResponse'] =
$res;
1310 $this->logger->debug( __METHOD__ .
": Primary creation failed by $id", [
1311 'user' =>
$user->getName(),
1312 'creator' => $creator->getName(),
1315 $session->remove(
'AuthManager::accountCreationState' );
1319 $this->logger->debug( __METHOD__ .
": Primary creation $res->status by $id", [
1320 'user' =>
$user->getName(),
1321 'creator' => $creator->getName(),
1324 $state[
'continueRequests'] =
$res->neededRequests;
1325 $session->setSecret(
'AuthManager::accountCreationState', $state );
1328 throw new \DomainException(
1329 get_class( $provider ) .
"::continuePrimaryAccountCreation() returned $res->status"
1337 if ( $state[
'userid'] === 0 ) {
1338 $this->logger->info(
'Creating user {user} during account creation', [
1339 'user' =>
$user->getName(),
1340 'creator' => $creator->getName(),
1347 $session->remove(
'AuthManager::accountCreationState' );
1353 $user->saveSettings();
1354 $state[
'userid'] =
$user->getId();
1363 $logSubtype = $provider->finishAccountCreation(
$user, $creator, $state[
'primaryResponse'] );
1366 if ( $this->config->get(
'NewUserLog' ) ) {
1367 $isAnon = $creator->isAnon();
1368 $logEntry = new \ManualLogEntry(
1370 $logSubtype ?: ( $isAnon ?
'create' :
'create2' )
1372 $logEntry->setPerformer( $isAnon ?
$user : $creator );
1373 $logEntry->setTarget(
$user->getUserPage() );
1377 $logEntry->setComment( $req ? $req->reason :
'' );
1378 $logEntry->setParameters( [
1379 '4::userid' =>
$user->getId(),
1381 $logid = $logEntry->insert();
1382 $logEntry->publish( $logid );
1388 $beginReqs = $state[
'reqs'];
1391 if ( !isset( $state[
'secondary'][$id] ) ) {
1395 $func =
'beginSecondaryAccountCreation';
1396 $res = $provider->beginSecondaryAccountCreation(
$user, $creator, $beginReqs );
1397 } elseif ( !$state[
'secondary'][$id] ) {
1398 $func =
'continueSecondaryAccountCreation';
1399 $res = $provider->continueSecondaryAccountCreation(
$user, $creator, $reqs );
1403 switch (
$res->status ) {
1405 $this->logger->debug( __METHOD__ .
": Secondary creation passed by $id", [
1406 'user' =>
$user->getName(),
1407 'creator' => $creator->getName(),
1411 $state[
'secondary'][$id] =
true;
1415 $this->logger->debug( __METHOD__ .
": Secondary creation $res->status by $id", [
1416 'user' =>
$user->getName(),
1417 'creator' => $creator->getName(),
1420 $state[
'secondary'][$id] =
false;
1421 $state[
'continueRequests'] =
$res->neededRequests;
1422 $session->setSecret(
'AuthManager::accountCreationState', $state );
1425 throw new \DomainException(
1426 get_class( $provider ) .
"::{$func}() returned $res->status." .
1427 ' Secondary providers are not allowed to fail account creation, that' .
1428 ' should have been done via testForAccountCreation().'
1432 throw new \DomainException(
1433 get_class( $provider ) .
"::{$func}() returned $res->status"
1439 $id =
$user->getId();
1444 $this->createdAccountAuthenticationRequests[] =
$req;
1446 $this->logger->info( __METHOD__ .
': Account creation succeeded for {user}', [
1447 'user' =>
$user->getName(),
1448 'creator' => $creator->getName(),
1452 $session->remove(
'AuthManager::accountCreationState' );
1456 $session->remove(
'AuthManager::accountCreationState' );
1470 if (
$source !== self::AUTOCREATE_SOURCE_SESSION &&
1473 throw new \InvalidArgumentException(
"Unknown auto-creation source: $source" );
1485 if ( !$localId &&
wfGetLB()->getReaderIndex() != 0 ) {
1492 $this->logger->debug( __METHOD__ .
': {username} already exists locally', [
1495 $user->
setId( $localId );
1501 $status->warning(
'userexists' );
1507 $this->logger->debug( __METHOD__ .
': denied by wfReadOnly(): {reason}', [
1518 $session = $this->
request->getSession();
1519 if ( $session->get(
'AuthManager::AutoCreateBlacklist' ) ) {
1520 $this->logger->debug( __METHOD__ .
': blacklisted in session {sessionid}', [
1522 'sessionid' => $session->getId(),
1526 $reason = $session->get(
'AuthManager::AutoCreateBlacklist' );
1536 $this->logger->debug( __METHOD__ .
': name "{username}" is not creatable', [
1539 $session->set(
'AuthManager::AutoCreateBlacklist',
'noname', 600 );
1547 if ( !$anon->isAllowedAny(
'createaccount',
'autocreateaccount' ) ) {
1548 $this->logger->debug( __METHOD__ .
': IP lacks the ability to create or autocreate accounts', [
1550 'ip' => $anon->getName(),
1552 $session->set(
'AuthManager::AutoCreateBlacklist',
'authmanager-autocreate-noperm', 600 );
1553 $session->persist();
1563 $this->logger->debug( __METHOD__ .
': Could not acquire account creation lock', [
1575 foreach ( $providers
as $provider ) {
1579 $this->logger->debug( __METHOD__ .
': Provider denied creation of {username}: {reason}', [
1581 'reason' =>
$ret->getWikiText( null, null,
'en' ),
1583 $session->set(
'AuthManager::AutoCreateBlacklist',
$status, 600 );
1594 if (
$cache->get( $backoffKey ) ) {
1595 $this->logger->debug( __METHOD__ .
': {username} denied by prior creation attempt failures', [
1604 $from = isset( $_SERVER[
'REQUEST_URI'] ) ? $_SERVER[
'REQUEST_URI'] :
'CLI';
1605 $this->logger->info( __METHOD__ .
': creating new user ({username}) - from: {from}', [
1616 $this->logger->info( __METHOD__ .
': {username} already exists locally (race)', [
1619 $user->
setId( $localId );
1625 $status->warning(
'userexists' );
1627 $this->logger->error( __METHOD__ .
': {username} failed with message {message}', [
1629 'message' =>
$status->getWikiText( null, null,
'en' )
1637 $this->logger->error( __METHOD__ .
': {username} failed with exception {exception}', [
1642 $cache->set( $backoffKey, 1, 600 );
1652 \Hooks::run(
'AuthPluginAutoCreate', [ $user ],
'1.27' );
1653 \Hooks::run(
'LocalUserCreated', [ $user,
true ] );
1663 if ( $this->config->get(
'NewUserLog' ) ) {
1664 $logEntry = new \ManualLogEntry(
'newusers',
'autocreate' );
1665 $logEntry->setPerformer( $user );
1667 $logEntry->setComment(
'' );
1668 $logEntry->setParameters( [
1669 '4::userid' => $user->
getId(),
1671 $logid = $logEntry->insert();
1711 $session = $this->
request->getSession();
1712 $session->remove(
'AuthManager::accountLinkState' );
1716 throw new \LogicException(
'Account linking is not possible' );
1719 if ( $user->
getId() === 0 ) {
1727 foreach ( $reqs
as $req ) {
1728 $req->username = $user->
getName();
1729 $req->returnToUrl = $returnToUrl;
1735 foreach ( $providers
as $id => $provider ) {
1736 $status = $provider->testForAccountLink( $user );
1738 $this->logger->debug( __METHOD__ .
": Account linking pre-check failed by $id", [
1750 'username' => $user->
getName(),
1751 'userid' => $user->
getId(),
1752 'returnToUrl' => $returnToUrl,
1754 'continueRequests' => [],
1758 foreach ( $providers
as $id => $provider ) {
1763 $res = $provider->beginPrimaryAccountLink( $user, $reqs );
1764 switch (
$res->status ) {
1766 $this->logger->info(
"Account linked to {user} by $id", [
1773 $this->logger->debug( __METHOD__ .
": Account linking failed by $id", [
1785 $this->logger->debug( __METHOD__ .
": Account linking $res->status by $id", [
1789 $state[
'primary'] = $id;
1790 $state[
'continueRequests'] =
$res->neededRequests;
1791 $session->setSecret(
'AuthManager::accountLinkState', $state );
1792 $session->persist();
1797 throw new \DomainException(
1798 get_class( $provider ) .
"::beginPrimaryAccountLink() returned $res->status"
1804 $this->logger->debug( __METHOD__ .
': Account linking failed because no provider accepted', [
1808 wfMessage(
'authmanager-link-no-primary' )
1820 $session = $this->
request->getSession();
1824 $session->remove(
'AuthManager::accountLinkState' );
1825 throw new \LogicException(
'Account linking is not possible' );
1828 $state = $session->getSecret(
'AuthManager::accountLinkState' );
1829 if ( !is_array( $state ) ) {
1831 wfMessage(
'authmanager-link-not-in-progress' )
1834 $state[
'continueRequests'] = [];
1839 if ( !is_object(
$user ) ) {
1840 $session->remove(
'AuthManager::accountLinkState' );
1843 if (
$user->getId() != $state[
'userid'] ) {
1844 throw new \UnexpectedValueException(
1845 "User \"{$state['username']}\" is valid, but " .
1846 "ID {$user->getId()} != {$state['userid']}!"
1850 foreach ( $reqs
as $req ) {
1851 $req->username = $state[
'username'];
1852 $req->returnToUrl = $state[
'returnToUrl'];
1862 wfMessage(
'authmanager-link-not-in-progress' )
1865 $session->remove(
'AuthManager::accountLinkState' );
1869 $id = $provider->getUniqueId();
1870 $res = $provider->continuePrimaryAccountLink(
$user, $reqs );
1871 switch (
$res->status ) {
1873 $this->logger->info(
"Account linked to {user} by $id", [
1874 'user' =>
$user->getName(),
1877 $session->remove(
'AuthManager::accountLinkState' );
1880 $this->logger->debug( __METHOD__ .
": Account linking failed by $id", [
1881 'user' =>
$user->getName(),
1884 $session->remove(
'AuthManager::accountLinkState' );
1888 $this->logger->debug( __METHOD__ .
": Account linking $res->status by $id", [
1889 'user' =>
$user->getName(),
1892 $state[
'continueRequests'] =
$res->neededRequests;
1893 $session->setSecret(
'AuthManager::accountLinkState', $state );
1896 throw new \DomainException(
1897 get_class( $provider ) .
"::continuePrimaryAccountLink() returned $res->status"
1901 $session->remove(
'AuthManager::accountLinkState' );
1937 case self::ACTION_LOGIN:
1938 case self::ACTION_CREATE:
1944 case self::ACTION_LOGIN_CONTINUE:
1945 $state = $this->
request->getSession()->getSecret(
'AuthManager::authnState' );
1946 return is_array( $state ) ? $state[
'continueRequests'] : [];
1948 case self::ACTION_CREATE_CONTINUE:
1949 $state = $this->
request->getSession()->getSecret(
'AuthManager::accountCreationState' );
1950 return is_array( $state ) ? $state[
'continueRequests'] : [];
1952 case self::ACTION_LINK:
1958 case self::ACTION_UNLINK:
1964 $providerAction = self::ACTION_REMOVE;
1967 case self::ACTION_LINK_CONTINUE:
1968 $state = $this->
request->getSession()->getSecret(
'AuthManager::accountLinkState' );
1969 return is_array( $state ) ? $state[
'continueRequests'] : [];
1971 case self::ACTION_CHANGE:
1972 case self::ACTION_REMOVE:
1979 throw new \DomainException( __METHOD__ .
": Invalid action \"$action\"" );
1999 $options[
'username'] =
$user->isAnon() ? null :
$user->getName();
2003 foreach ( $providers
as $provider ) {
2005 foreach ( $provider->getAuthenticationRequests( $providerAction, $options )
as $req ) {
2006 $id =
$req->getUniqueId();
2010 if (
$req->required ) {
2016 !isset( $reqs[$id] )
2026 switch ( $providerAction ) {
2027 case self::ACTION_LOGIN:
2031 case self::ACTION_CREATE:
2034 if ( $options[
'username'] !== null ) {
2036 $options[
'username'] = null;
2042 $this->
fillRequests( $reqs, $providerAction, $options[
'username'],
true );
2045 if ( $providerAction === self::ACTION_CHANGE || $providerAction === self::ACTION_REMOVE ) {
2046 $reqs = array_filter( $reqs,
function (
$req ) {
2051 return array_values( $reqs );
2062 foreach ( $reqs
as $req ) {
2063 if ( !$req->action || $forceAction ) {
2066 if ( $req->username === null ) {
2102 foreach ( $providers
as $provider ) {
2103 if ( !$provider->providerAllowsPropertyChange(
$property ) ) {
2120 if ( isset( $this->allAuthenticationProviders[$id] ) ) {
2121 return $this->allAuthenticationProviders[$id];
2126 if ( isset( $providers[$id] ) ) {
2127 return $providers[$id];
2130 if ( isset( $providers[$id] ) ) {
2131 return $providers[$id];
2134 if ( isset( $providers[$id] ) ) {
2135 return $providers[$id];
2155 $session = $this->
request->getSession();
2156 $arr = $session->getSecret(
'authData' );
2157 if ( !is_array( $arr ) ) {
2161 $session->setSecret(
'authData', $arr );
2172 $arr = $this->
request->getSession()->getSecret(
'authData' );
2173 if ( is_array( $arr ) && array_key_exists(
$key, $arr ) ) {
2186 $session = $this->
request->getSession();
2187 if (
$key === null ) {
2188 $session->remove(
'authData' );
2190 $arr = $session->getSecret(
'authData' );
2191 if ( is_array( $arr ) && array_key_exists(
$key, $arr ) ) {
2192 unset( $arr[
$key] );
2193 $session->setSecret(
'authData', $arr );
2206 foreach ( $specs
as &$spec ) {
2207 $spec = [
'sort2' => $i++ ] + $spec + [
'sort' => 0 ];
2210 usort( $specs,
function ( $a, $b ) {
2211 return ( (
int)$a[
'sort'] ) - ( (
int)$b[
'sort'] )
2212 ?: $a[
'sort2'] - $b[
'sort2'];
2216 foreach ( $specs
as $spec ) {
2218 if ( !$provider instanceof $class ) {
2219 throw new \RuntimeException(
2220 "Expected instance of $class, got " . get_class( $provider )
2223 $provider->setLogger( $this->logger );
2224 $provider->setManager( $this );
2225 $provider->setConfig( $this->config );
2226 $id = $provider->getUniqueId();
2227 if ( isset( $this->allAuthenticationProviders[$id] ) ) {
2228 throw new \RuntimeException(
2229 "Duplicate specifications for id $id (classes " .
2230 get_class( $provider ) .
' and ' .
2231 get_class( $this->allAuthenticationProviders[$id] ) .
')'
2234 $this->allAuthenticationProviders[$id] = $provider;
2235 $ret[$id] = $provider;
2245 return $this->config->get(
'AuthManagerConfig' ) ?: $this->config->get(
'AuthManagerAutoConfig' );
2253 if ( $this->preAuthenticationProviders === null ) {
2267 if ( $this->primaryAuthenticationProviders === null ) {
2281 if ( $this->secondaryAuthenticationProviders === null ) {
2295 $session = $this->
request->getSession();
2296 $delay = $session->delaySave();
2298 $session->resetId();
2299 $session->resetAllTokens();
2300 if ( $session->canSetUser() ) {
2301 $session->setUser(
$user );
2303 if ( $remember !== null ) {
2304 $session->setRememberUser( $remember );
2306 $session->set(
'AuthManager:lastAuthId',
$user->getId() );
2307 $session->set(
'AuthManager:lastAuthTimestamp', time() );
2308 $session->persist();
2327 if ( $wgContLang->hasVariants() ) {
2328 $user->
setOption(
'variant', $wgContLang->getPreferredVariant() );
2348 foreach ( $providers
as $provider ) {
2349 call_user_func_array( [ $provider, $method ], $args );
2357 if ( !defined(
'MW_PHPUNIT_TEST' ) ) {
2359 throw new \MWException( __METHOD__ .
' may only be called from unit tests!' );
2363 self::$instance = null;
static newFromName($name, $validate= 'valid')
Static factory method for creation from username.
the array() calling protocol came about after MediaWiki 1.4rc1.
static wrap($sv)
Succinct helper method to wrap a StatusValue.
magic word the default is to use $key to get the and $key value or $key value text $key value html to format the value $key
static getObjectFromSpec($spec)
Instantiate an object based on a specification array.
isDnsBlacklisted($ip, $checkWhitelist=false)
Whether the given IP is in a DNS blacklist.
saveSettings()
Save this user's settings into the database.
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
static getTitleFor($name, $subpage=false, $fragment= '')
Get a localised Title object for a specified special page name.
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses & $ret
static instance()
Singleton.
if(!isset($args[0])) $lang
static isUsableName($name)
Usernames which fail to pass this function will be blocked from user login and new account registrati...
static newFromId($id)
Static factory method for creation from a given user ID.
static getLocalClusterInstance()
Get the main cluster-local cache object.
it s the revision text itself In either if gzip is the revision text is gzipped $flags
when a variable name is used in a it is silently declared as a new local masking the global
static newFatal($message)
Factory function for fatal errors.
setToken($token=false)
Set the random token (used for persistent authentication) Called from loadDefaults() among other plac...
setOption($oname, $val)
Set the given option for a user.
getName()
Get the user name, or the IP of an anonymous user.
isBlockedFromCreateAccount()
Get whether the user is explicitly blocked from account creation.
wfGetLB($wiki=false)
Get a load balancer object.
wfReadOnly()
Check whether the wiki is in read-only mode.
static getMain()
Static methods.
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context $options
static invalidateAllPasswordsForUser($username)
Invalidate all passwords for a user, by name.
Class for handling updates to the site_stats table.
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return an< a > element with HTML attributes $attribs and contents $html will be returned If you return $ret will be returned and may include noclasses after processing after in associative array form externallinks including delete and has completed for all link tables whether this was an auto creation default is conds Array Extra conditions for the No matching items in log is displayed if loglist is empty msgKey Array If you want a nice box with a set this to the key of the message First element is the message additional optional elements are parameters for the key that are processed with wfMessage() -> params() ->parseAsBlock()-offset Set to overwrite offset parameter in $wgRequest set to ''to unsetoffset-wrap String Wrap the message in html(usually something like"<
static addUpdate(DeferrableUpdate $update, $type=self::POSTSEND)
Add an update to the deferred list.
static run($event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a local account $user
static getDefaultInstance()
setId($v)
Set the user and reload all fields according to a given ID.
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
this hook is for auditing only $req
this hook is for auditing only or null if authentication failed before getting that far $username
wfReadOnlyReason()
Check if the site is in read-only mode and return the message if so.
getId()
Get the user's ID.
addToDatabase()
Add this existing user object to the database.
you have access to all of the normal MediaWiki so you can get a DB use the etc For full docs on the Maintenance class
static isCreatableName($name)
Usernames which fail to pass this function will be blocked from new account registrations, but may be used internally either by batch processes or by user accounts which have already been created.
getUserPage()
Get this user's personal page title.
static idFromName($name, $flags=self::READ_NORMAL)
Get database id given a user name.
this class mediates it Skin Encapsulates a look and feel for the wiki All of the functions that render HTML and make choices about how to render it are here and are called from various other places when and is meant to be subclassed with other skins that may override some of its functions The User object contains a reference to a and so rather than having a global skin object we just rely on the global User and get the skin with $wgUser and also has some character encoding functions and other locale stuff The current user interface language is instantiated as and the local content language as $wgContLang
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set $status
static consume(ScopedCallback &$sc=null)
Trigger a scoped callback and destroy it.
wfMemcKey()
Make a cache key for the local wiki.
loadFromId($flags=self::READ_NORMAL)
Load user table data, given mId has already been set.
addWatch($title, $checkRights=self::CHECK_USER_RIGHTS)
Watch an article.
this hook is for auditing only etc instead of letting the login form give the generic error message that the account does not exist For when the account has been renamed or deleted or an array to pass a message key and parameters create2 Corresponds to logging log_action database field and which is displayed in the UI similar to $comment this hook should only be used to add variables that depend on the current page request
static newGood($value=null)
Factory function for good results.
Allows to change the fields on the form that will be generated $name