24 namespace MediaWiki\Auth;
27 use Psr\Log\LoggerAwareInterface;
28 use Psr\Log\LoggerInterface;
110 if ( $wgDisableAuthManager ) {
111 throw new \BadMethodCallException(
'$wgDisableAuthManager is set' );
114 if ( self::$instance === null ) {
115 self::$instance =
new self(
120 return self::$instance;
154 $this->logger->warning(
"Overriding AuthManager primary authn because $why" );
156 if ( $this->primaryAuthenticationProviders !== null ) {
157 $this->logger->warning(
158 'PrimaryAuthenticationProviders have already been accessed! I hope nothing breaks.'
161 $this->allAuthenticationProviders = array_diff_key(
162 $this->allAuthenticationProviders,
163 $this->primaryAuthenticationProviders
165 $session = $this->
request->getSession();
166 $session->remove(
'AuthManager::authnState' );
167 $session->remove(
'AuthManager::accountCreationState' );
168 $session->remove(
'AuthManager::accountLinkState' );
169 $this->createdAccountAuthenticationRequests = [];
172 $this->primaryAuthenticationProviders = [];
173 foreach ( $providers
as $provider ) {
175 throw new \RuntimeException(
176 'Expected instance of MediaWiki\\Auth\\PrimaryAuthenticationProvider, got ' .
177 get_class( $provider )
180 $provider->setLogger( $this->logger );
181 $provider->setManager( $this );
182 $provider->setConfig( $this->config );
183 $id = $provider->getUniqueId();
184 if ( isset( $this->allAuthenticationProviders[$id] ) ) {
185 throw new \RuntimeException(
186 "Duplicate specifications for id $id (classes " .
187 get_class( $provider ) .
' and ' .
188 get_class( $this->allAuthenticationProviders[$id] ) .
')'
191 $this->allAuthenticationProviders[$id] = $provider;
192 $this->primaryAuthenticationProviders[$id] = $provider;
209 return call_user_func_array( [ $wgAuth, $method ], $params );
229 return $this->
request->getSession()->canSetUser();
251 $session = $this->
request->getSession();
252 if ( !$session->canSetUser() ) {
254 $session->remove(
'AuthManager::authnState' );
255 throw new \LogicException(
'Authentication is not possible now' );
258 $guessUserName = null;
259 foreach ( $reqs
as $req ) {
260 $req->returnToUrl = $returnToUrl;
262 if ( $req->username !== null && $req->username !==
'' ) {
263 if ( $guessUserName === null ) {
264 $guessUserName = $req->username;
265 } elseif ( $guessUserName !== $req->username ) {
266 $guessUserName = null;
278 if ( !in_array( $req, $this->createdAccountAuthenticationRequests,
true ) ) {
279 throw new \LogicException(
280 'CreatedAccountAuthenticationRequests are only valid on ' .
281 'the same AuthManager that created the account'
288 throw new \UnexpectedValueException(
289 "CreatedAccountAuthenticationRequest had invalid username \"{$req->username}\""
291 } elseif (
$user->getId() != $req->id ) {
292 throw new \UnexpectedValueException(
293 "ID for \"{$req->username}\" was {$user->getId()}, expected {$req->id}"
298 $this->logger->info(
'Logging in {user} after account creation', [
299 'user' =>
$user->getName(),
304 $session->remove(
'AuthManager::authnState' );
312 $status = $provider->testForAuthentication( $reqs );
314 $this->logger->debug(
'Login failed in pre-authentication by ' . $provider->getUniqueId() );
321 \Hooks::run(
'AuthManagerLoginAuthenticateAudit', [
$ret, null, $guessUserName ] );
328 'returnToUrl' => $returnToUrl,
329 'guessUserName' => $guessUserName,
331 'primaryResponse' => null,
334 'continueRequests' => [],
342 $state[
'maybeLink'] = $req->maybeLink;
345 $session = $this->
request->getSession();
346 $session->setSecret(
'AuthManager::authnState', $state );
375 $session = $this->
request->getSession();
377 if ( !$session->canSetUser() ) {
380 throw new \LogicException(
'Authentication is not possible now' );
384 $state = $session->getSecret(
'AuthManager::authnState' );
385 if ( !is_array( $state ) ) {
387 wfMessage(
'authmanager-authn-not-in-progress' )
390 $state[
'continueRequests'] = [];
392 $guessUserName = $state[
'guessUserName'];
394 foreach ( $reqs
as $req ) {
395 $req->returnToUrl = $state[
'returnToUrl'];
400 if ( $state[
'primary'] === null ) {
403 $guessUserName = null;
404 foreach ( $reqs
as $req ) {
405 if ( $req->username !== null && $req->username !==
'' ) {
406 if ( $guessUserName === null ) {
407 $guessUserName = $req->username;
408 } elseif ( $guessUserName !== $req->username ) {
409 $guessUserName = null;
414 $state[
'guessUserName'] = $guessUserName;
416 $state[
'reqs'] = $reqs;
419 $res = $provider->beginPrimaryAuthentication( $reqs );
420 switch (
$res->status ) {
422 $state[
'primary'] = $id;
423 $state[
'primaryResponse'] =
$res;
424 $this->logger->debug(
"Primary login with $id succeeded" );
427 $this->logger->debug(
"Login failed in primary authentication by $id" );
428 if (
$res->createRequest || $state[
'maybeLink'] ) {
430 $res->createRequest, $state[
'maybeLink']
436 $session->remove(
'AuthManager::authnState' );
437 \Hooks::run(
'AuthManagerLoginAuthenticateAudit', [
$res, null, $guessUserName ] );
444 $this->logger->debug(
"Primary login with $id returned $res->status" );
445 $this->
fillRequests(
$res->neededRequests, self::ACTION_LOGIN, $guessUserName );
446 $state[
'primary'] = $id;
447 $state[
'continueRequests'] =
$res->neededRequests;
448 $session->setSecret(
'AuthManager::authnState', $state );
453 throw new \DomainException(
454 get_class( $provider ) .
"::beginPrimaryAuthentication() returned $res->status"
459 if ( $state[
'primary'] === null ) {
460 $this->logger->debug(
'Login failed in primary authentication because no provider accepted' );
462 wfMessage(
'authmanager-authn-no-primary' )
467 $session->remove(
'AuthManager::authnState' );
470 } elseif ( $state[
'primaryResponse'] === null ) {
476 wfMessage(
'authmanager-authn-not-in-progress' )
481 $session->remove(
'AuthManager::authnState' );
485 $id = $provider->getUniqueId();
486 $res = $provider->continuePrimaryAuthentication( $reqs );
487 switch (
$res->status ) {
489 $state[
'primaryResponse'] =
$res;
490 $this->logger->debug(
"Primary login with $id succeeded" );
493 $this->logger->debug(
"Login failed in primary authentication by $id" );
494 if (
$res->createRequest || $state[
'maybeLink'] ) {
496 $res->createRequest, $state[
'maybeLink']
502 $session->remove(
'AuthManager::authnState' );
503 \Hooks::run(
'AuthManagerLoginAuthenticateAudit', [
$res, null, $guessUserName ] );
507 $this->logger->debug(
"Primary login with $id returned $res->status" );
508 $this->
fillRequests(
$res->neededRequests, self::ACTION_LOGIN, $guessUserName );
509 $state[
'continueRequests'] =
$res->neededRequests;
510 $session->setSecret(
'AuthManager::authnState', $state );
513 throw new \DomainException(
514 get_class( $provider ) .
"::continuePrimaryAuthentication() returned $res->status"
519 $res = $state[
'primaryResponse'];
520 if (
$res->username === null ) {
526 wfMessage(
'authmanager-authn-not-in-progress' )
531 $session->remove(
'AuthManager::authnState' );
541 $state[
'maybeLink'][
$res->linkRequest->getUniqueId()] =
$res->linkRequest;
542 $msg =
'authmanager-authn-no-local-user-link';
544 $msg =
'authmanager-authn-no-local-user';
546 $this->logger->debug(
547 "Primary login with {$provider->getUniqueId()} succeeded, but returned no user"
555 if (
$res->createRequest || $state[
'maybeLink'] ) {
557 $res->createRequest, $state[
'maybeLink']
559 $ret->neededRequests[] =
$ret->createRequest;
561 $this->
fillRequests(
$ret->neededRequests, self::ACTION_LOGIN, null,
true );
562 $session->setSecret(
'AuthManager::authnState', [
565 'primaryResponse' => null,
567 'continueRequests' =>
$ret->neededRequests,
577 throw new \DomainException(
578 get_class( $provider ) .
" returned an invalid username: {$res->username}"
581 if (
$user->getId() === 0 ) {
583 $this->logger->info(
'Auto-creating {user} on login', [
584 'user' =>
$user->getName(),
592 $session->remove(
'AuthManager::authnState' );
600 $beginReqs = $state[
'reqs'];
603 if ( !isset( $state[
'secondary'][$id] ) ) {
607 $func =
'beginSecondaryAuthentication';
608 $res = $provider->beginSecondaryAuthentication(
$user, $beginReqs );
609 } elseif ( !$state[
'secondary'][$id] ) {
610 $func =
'continueSecondaryAuthentication';
611 $res = $provider->continueSecondaryAuthentication(
$user, $reqs );
615 switch (
$res->status ) {
617 $this->logger->debug(
"Secondary login with $id succeeded" );
620 $state[
'secondary'][$id] =
true;
623 $this->logger->debug(
"Login failed in secondary authentication by $id" );
625 $session->remove(
'AuthManager::authnState' );
630 $this->logger->debug(
"Secondary login with $id returned " . $res->status );
631 $this->
fillRequests( $res->neededRequests, self::ACTION_LOGIN,
$user->getName() );
632 $state[
'secondary'][$id] =
false;
633 $state[
'continueRequests'] = $res->neededRequests;
634 $session->setSecret(
'AuthManager::authnState', $state );
639 throw new \DomainException(
640 get_class( $provider ) .
"::{$func}() returned $res->status"
649 $this->logger->info(
'Login for {user} succeeded', [
650 'user' =>
$user->getName(),
658 $session->remove(
'AuthManager::authnState' );
662 }
catch ( \Exception $ex ) {
663 $session->remove(
'AuthManager::authnState' );
682 $this->logger->debug( __METHOD__ .
": Checking $operation" );
684 $session = $this->
request->getSession();
685 $aId = $session->getUser()->getId();
689 $this->logger->info( __METHOD__ .
": Not logged in! $operation is $status" );
693 if ( $session->canSetUser() ) {
694 $id = $session->get(
'AuthManager:lastAuthId' );
695 $last = $session->get(
'AuthManager:lastAuthTimestamp' );
696 if ( $id !== $aId ||
$last === null ) {
697 $timeSinceLogin = PHP_INT_MAX;
699 $timeSinceLogin = max( 0, time() -
$last );
702 $thresholds = $this->config->get(
'ReauthenticateTime' );
703 if ( isset( $thresholds[$operation] ) ) {
704 $threshold = $thresholds[$operation];
705 } elseif ( isset( $thresholds[
'default'] ) ) {
706 $threshold = $thresholds[
'default'];
708 throw new \UnexpectedValueException(
'$wgReauthenticateTime lacks a default' );
711 if ( $threshold >= 0 && $timeSinceLogin > $threshold ) {
715 $timeSinceLogin = -1;
717 $pass = $this->config->get(
'AllowSecuritySensitiveOperationIfCannotReauthenticate' );
718 if ( isset( $pass[$operation] ) ) {
719 $status = $pass[$operation] ? self::SEC_OK : self::SEC_FAIL;
720 } elseif ( isset( $pass[
'default'] ) ) {
721 $status = $pass[
'default'] ? self::SEC_OK : self::SEC_FAIL;
723 throw new \UnexpectedValueException(
724 '$wgAllowSecuritySensitiveOperationIfCannotReauthenticate lacks a default'
730 &
$status, $operation, $session, $timeSinceLogin
738 $this->logger->info( __METHOD__ .
": $operation is $status" );
751 if ( $provider->testUserCanAuthenticate(
$username ) ) {
775 $normalized = $provider->providerNormalizeUsername(
$username );
776 if ( $normalized !== null ) {
777 $ret[$normalized] =
true;
780 return array_keys(
$ret );
798 $this->logger->info(
'Revoking access for {user}', [
817 foreach ( $providers
as $provider ) {
818 $status = $provider->providerAllowsAuthenticationDataChange( $req, $checkData );
822 $any = $any ||
$status->value !==
'ignored';
826 $status->warning(
'authmanager-change-not-supported' );
844 $this->logger->info(
'Changing authentication data for {user} class {what}', [
845 'user' => is_string( $req->username ) ? $req->username :
'<no name>',
846 'what' => get_class( $req ),
869 switch ( $provider->accountCreationType() ) {
906 if ( !is_object(
$user ) ) {
910 if (
$user->getId() !== 0 ) {
919 foreach ( $providers
as $provider ) {
942 ->getUserPermissionsErrors(
'createaccount', $creator,
'secure' );
945 foreach ( $permErrors
as $args ) {
946 call_user_func_array( [
$status,
'fatal' ], $args );
955 $block->mReason ?:
wfMessage(
'blockednoreason' )->text(),
960 $errorMessage =
'cantcreateaccount-range-text';
961 $errorParams[] = $this->
getRequest()->getIP();
963 $errorMessage =
'cantcreateaccount-text';
997 $session = $this->
request->getSession();
1000 $session->remove(
'AuthManager::accountCreationState' );
1001 throw new \LogicException(
'Account creation is not possible' );
1006 }
catch ( \UnexpectedValueException $ex ) {
1010 $this->logger->debug( __METHOD__ .
': No username provided' );
1017 $this->logger->debug( __METHOD__ .
': {creator} cannot create users: {reason}', [
1019 'creator' => $creator->
getName(),
1020 'reason' =>
$status->getWikiText( null, null,
'en' )
1029 $this->logger->debug( __METHOD__ .
': {user} cannot be created: {reason}', [
1031 'creator' => $creator->
getName(),
1032 'reason' =>
$status->getWikiText( null, null,
'en' )
1038 foreach ( $reqs
as $req ) {
1040 $req->returnToUrl = $returnToUrl;
1045 $session->remove(
'AuthManager::accountCreationState' );
1046 $this->logger->debug( __METHOD__ .
': UserData is invalid: {reason}', [
1047 'user' =>
$user->getName(),
1048 'creator' => $creator->
getName(),
1049 'reason' =>
$status->getWikiText( null, null,
'en' ),
1061 'creatorid' => $creator->
getId(),
1062 'creatorname' => $creator->
getName(),
1064 'returnToUrl' => $returnToUrl,
1066 'primaryResponse' => null,
1068 'continueRequests' => [],
1070 'ranPreTests' =>
false,
1078 $state[
'maybeLink'] = $req->maybeLink;
1080 if ( $req->createRequest ) {
1081 $reqs[] = $req->createRequest;
1082 $state[
'reqs'][] = $req->createRequest;
1086 $session->setSecret(
'AuthManager::accountCreationState', $state );
1087 $session->persist();
1098 $session = $this->
request->getSession();
1102 $session->remove(
'AuthManager::accountCreationState' );
1103 throw new \LogicException(
'Account creation is not possible' );
1106 $state = $session->getSecret(
'AuthManager::accountCreationState' );
1107 if ( !is_array( $state ) ) {
1109 wfMessage(
'authmanager-create-not-in-progress' )
1112 $state[
'continueRequests'] = [];
1117 if ( !is_object(
$user ) ) {
1118 $session->remove(
'AuthManager::accountCreationState' );
1119 $this->logger->debug( __METHOD__ .
': Invalid username', [
1120 'user' => $state[
'username'],
1125 if ( $state[
'creatorid'] ) {
1128 $creator =
new User;
1129 $creator->setName( $state[
'creatorname'] );
1134 $lock =
$cache->getScopedLock(
$cache->makeGlobalKey(
'account', md5(
$user->getName() ) ) );
1138 $this->logger->debug( __METHOD__ .
': Could not acquire account creation lock', [
1139 'user' =>
$user->getName(),
1140 'creator' => $creator->getName(),
1148 $this->logger->debug( __METHOD__ .
': {creator} cannot create users: {reason}', [
1149 'user' =>
$user->getName(),
1150 'creator' => $creator->getName(),
1151 'reason' =>
$status->getWikiText( null, null,
'en' )
1155 $session->remove(
'AuthManager::accountCreationState' );
1162 if ( $state[
'userid'] === 0 ) {
1163 if (
$user->getId() != 0 ) {
1164 $this->logger->debug( __METHOD__ .
': User exists locally', [
1165 'user' =>
$user->getName(),
1166 'creator' => $creator->getName(),
1170 $session->remove(
'AuthManager::accountCreationState' );
1174 if (
$user->getId() == 0 ) {
1175 $this->logger->debug( __METHOD__ .
': User does not exist locally when it should', [
1176 'user' =>
$user->getName(),
1177 'creator' => $creator->getName(),
1178 'expected_id' => $state[
'userid'],
1180 throw new \UnexpectedValueException(
1181 "User \"{$state['username']}\" should exist now, but doesn't!"
1184 if (
$user->getId() != $state[
'userid'] ) {
1185 $this->logger->debug( __METHOD__ .
': User ID/name mismatch', [
1186 'user' =>
$user->getName(),
1187 'creator' => $creator->getName(),
1188 'expected_id' => $state[
'userid'],
1189 'actual_id' =>
$user->getId(),
1191 throw new \UnexpectedValueException(
1192 "User \"{$state['username']}\" exists, but " .
1193 "ID {$user->getId()} != {$state['userid']}!"
1197 foreach ( $state[
'reqs']
as $req ) {
1203 $this->logger->debug( __METHOD__ .
': UserData is invalid: {reason}', [
1204 'user' =>
$user->getName(),
1205 'creator' => $creator->getName(),
1206 'reason' =>
$status->getWikiText( null, null,
'en' ),
1210 $session->remove(
'AuthManager::accountCreationState' );
1216 foreach ( $reqs
as $req ) {
1217 $req->returnToUrl = $state[
'returnToUrl'];
1218 $req->username = $state[
'username'];
1222 if ( !$state[
'ranPreTests'] ) {
1226 foreach ( $providers
as $id => $provider ) {
1227 $status = $provider->testForAccountCreation(
$user, $creator, $reqs );
1229 $this->logger->debug( __METHOD__ .
": Fail in pre-authentication by $id", [
1230 'user' =>
$user->getName(),
1231 'creator' => $creator->getName(),
1237 $session->remove(
'AuthManager::accountCreationState' );
1242 $state[
'ranPreTests'] =
true;
1247 if ( $state[
'primary'] === null ) {
1253 $res = $provider->beginPrimaryAccountCreation(
$user, $creator, $reqs );
1254 switch (
$res->status ) {
1256 $this->logger->debug( __METHOD__ .
": Primary creation passed by $id", [
1257 'user' =>
$user->getName(),
1258 'creator' => $creator->getName(),
1260 $state[
'primary'] = $id;
1261 $state[
'primaryResponse'] =
$res;
1264 $this->logger->debug( __METHOD__ .
": Primary creation failed by $id", [
1265 'user' =>
$user->getName(),
1266 'creator' => $creator->getName(),
1269 $session->remove(
'AuthManager::accountCreationState' );
1276 $this->logger->debug( __METHOD__ .
": Primary creation $res->status by $id", [
1277 'user' =>
$user->getName(),
1278 'creator' => $creator->getName(),
1281 $state[
'primary'] = $id;
1282 $state[
'continueRequests'] =
$res->neededRequests;
1283 $session->setSecret(
'AuthManager::accountCreationState', $state );
1288 throw new \DomainException(
1289 get_class( $provider ) .
"::beginPrimaryAccountCreation() returned $res->status"
1294 if ( $state[
'primary'] === null ) {
1295 $this->logger->debug( __METHOD__ .
': Primary creation failed because no provider accepted', [
1296 'user' =>
$user->getName(),
1297 'creator' => $creator->getName(),
1300 wfMessage(
'authmanager-create-no-primary' )
1303 $session->remove(
'AuthManager::accountCreationState' );
1306 } elseif ( $state[
'primaryResponse'] === null ) {
1312 wfMessage(
'authmanager-create-not-in-progress' )
1315 $session->remove(
'AuthManager::accountCreationState' );
1319 $id = $provider->getUniqueId();
1320 $res = $provider->continuePrimaryAccountCreation(
$user, $creator, $reqs );
1321 switch (
$res->status ) {
1323 $this->logger->debug( __METHOD__ .
": Primary creation passed by $id", [
1324 'user' =>
$user->getName(),
1325 'creator' => $creator->getName(),
1327 $state[
'primaryResponse'] =
$res;
1330 $this->logger->debug( __METHOD__ .
": Primary creation failed by $id", [
1331 'user' =>
$user->getName(),
1332 'creator' => $creator->getName(),
1335 $session->remove(
'AuthManager::accountCreationState' );
1339 $this->logger->debug( __METHOD__ .
": Primary creation $res->status by $id", [
1340 'user' =>
$user->getName(),
1341 'creator' => $creator->getName(),
1344 $state[
'continueRequests'] =
$res->neededRequests;
1345 $session->setSecret(
'AuthManager::accountCreationState', $state );
1348 throw new \DomainException(
1349 get_class( $provider ) .
"::continuePrimaryAccountCreation() returned $res->status"
1357 if ( $state[
'userid'] === 0 ) {
1358 $this->logger->info(
'Creating user {user} during account creation', [
1359 'user' =>
$user->getName(),
1360 'creator' => $creator->getName(),
1367 $session->remove(
'AuthManager::accountCreationState' );
1373 $user->saveSettings();
1374 $state[
'userid'] =
$user->getId();
1383 $logSubtype = $provider->finishAccountCreation(
$user, $creator, $state[
'primaryResponse'] );
1386 if ( $this->config->get(
'NewUserLog' ) ) {
1387 $isAnon = $creator->isAnon();
1388 $logEntry = new \ManualLogEntry(
1390 $logSubtype ?: ( $isAnon ?
'create' :
'create2' )
1392 $logEntry->setPerformer( $isAnon ?
$user : $creator );
1393 $logEntry->setTarget(
$user->getUserPage() );
1397 $logEntry->setComment( $req ? $req->reason :
'' );
1398 $logEntry->setParameters( [
1399 '4::userid' =>
$user->getId(),
1401 $logid = $logEntry->insert();
1402 $logEntry->publish( $logid );
1408 $beginReqs = $state[
'reqs'];
1411 if ( !isset( $state[
'secondary'][$id] ) ) {
1415 $func =
'beginSecondaryAccountCreation';
1416 $res = $provider->beginSecondaryAccountCreation(
$user, $creator, $beginReqs );
1417 } elseif ( !$state[
'secondary'][$id] ) {
1418 $func =
'continueSecondaryAccountCreation';
1419 $res = $provider->continueSecondaryAccountCreation(
$user, $creator, $reqs );
1423 switch (
$res->status ) {
1425 $this->logger->debug( __METHOD__ .
": Secondary creation passed by $id", [
1426 'user' =>
$user->getName(),
1427 'creator' => $creator->getName(),
1431 $state[
'secondary'][$id] =
true;
1435 $this->logger->debug( __METHOD__ .
": Secondary creation $res->status by $id", [
1436 'user' =>
$user->getName(),
1437 'creator' => $creator->getName(),
1440 $state[
'secondary'][$id] =
false;
1441 $state[
'continueRequests'] =
$res->neededRequests;
1442 $session->setSecret(
'AuthManager::accountCreationState', $state );
1445 throw new \DomainException(
1446 get_class( $provider ) .
"::{$func}() returned $res->status." .
1447 ' Secondary providers are not allowed to fail account creation, that' .
1448 ' should have been done via testForAccountCreation().'
1452 throw new \DomainException(
1453 get_class( $provider ) .
"::{$func}() returned $res->status"
1459 $id =
$user->getId();
1464 $this->createdAccountAuthenticationRequests[] =
$req;
1466 $this->logger->info( __METHOD__ .
': Account creation succeeded for {user}', [
1467 'user' =>
$user->getName(),
1468 'creator' => $creator->getName(),
1472 $session->remove(
'AuthManager::accountCreationState' );
1475 }
catch ( \Exception $ex ) {
1476 $session->remove(
'AuthManager::accountCreationState' );
1490 if (
$source !== self::AUTOCREATE_SOURCE_SESSION &&
1493 throw new \InvalidArgumentException(
"Unknown auto-creation source: $source" );
1505 if ( !$localId &&
wfGetLB()->getReaderIndex() != 0 ) {
1512 $this->logger->debug( __METHOD__ .
': {username} already exists locally', [
1515 $user->
setId( $localId );
1521 $status->warning(
'userexists' );
1527 $this->logger->debug( __METHOD__ .
': denied by wfReadOnly(): {reason}', [
1538 $session = $this->
request->getSession();
1539 if ( $session->get(
'AuthManager::AutoCreateBlacklist' ) ) {
1540 $this->logger->debug( __METHOD__ .
': blacklisted in session {sessionid}', [
1542 'sessionid' => $session->getId(),
1546 $reason = $session->get(
'AuthManager::AutoCreateBlacklist' );
1556 $this->logger->debug( __METHOD__ .
': name "{username}" is not creatable', [
1559 $session->set(
'AuthManager::AutoCreateBlacklist',
'noname', 600 );
1567 if ( !$anon->isAllowedAny(
'createaccount',
'autocreateaccount' ) ) {
1568 $this->logger->debug( __METHOD__ .
': IP lacks the ability to create or autocreate accounts', [
1570 'ip' => $anon->getName(),
1572 $session->set(
'AuthManager::AutoCreateBlacklist',
'authmanager-autocreate-noperm', 600 );
1573 $session->persist();
1583 $this->logger->debug( __METHOD__ .
': Could not acquire account creation lock', [
1599 foreach ( $providers
as $provider ) {
1603 $this->logger->debug( __METHOD__ .
': Provider denied creation of {username}: {reason}', [
1605 'reason' =>
$ret->getWikiText( null, null,
'en' ),
1607 $session->set(
'AuthManager::AutoCreateBlacklist',
$status, 600 );
1618 if (
$cache->get( $backoffKey ) ) {
1619 $this->logger->debug( __METHOD__ .
': {username} denied by prior creation attempt failures', [
1628 $from = isset( $_SERVER[
'REQUEST_URI'] ) ? $_SERVER[
'REQUEST_URI'] :
'CLI';
1629 $this->logger->info( __METHOD__ .
': creating new user ({username}) - from: {from}', [
1640 $this->logger->info( __METHOD__ .
': {username} already exists locally (race)', [
1643 $user->
setId( $localId );
1649 $status->warning(
'userexists' );
1651 $this->logger->error( __METHOD__ .
': {username} failed with message {message}', [
1653 'message' =>
$status->getWikiText( null, null,
'en' )
1660 }
catch ( \Exception $ex ) {
1661 $this->logger->error( __METHOD__ .
': {username} failed with exception {exception}', [
1666 $cache->set( $backoffKey, 1, 600 );
1676 \Hooks::run(
'AuthPluginAutoCreate', [ $user ],
'1.27' );
1677 \Hooks::run(
'LocalUserCreated', [ $user,
true ] );
1687 if ( $this->config->get(
'NewUserLog' ) ) {
1688 $logEntry = new \ManualLogEntry(
'newusers',
'autocreate' );
1689 $logEntry->setPerformer( $user );
1691 $logEntry->setComment(
'' );
1692 $logEntry->setParameters( [
1693 '4::userid' => $user->
getId(),
1695 $logid = $logEntry->insert();
1739 $session = $this->
request->getSession();
1740 $session->remove(
'AuthManager::accountLinkState' );
1744 throw new \LogicException(
'Account linking is not possible' );
1747 if ( $user->
getId() === 0 ) {
1755 foreach ( $reqs
as $req ) {
1756 $req->username = $user->
getName();
1757 $req->returnToUrl = $returnToUrl;
1763 foreach ( $providers
as $id => $provider ) {
1764 $status = $provider->testForAccountLink( $user );
1766 $this->logger->debug( __METHOD__ .
": Account linking pre-check failed by $id", [
1778 'username' => $user->
getName(),
1779 'userid' => $user->
getId(),
1780 'returnToUrl' => $returnToUrl,
1782 'continueRequests' => [],
1786 foreach ( $providers
as $id => $provider ) {
1791 $res = $provider->beginPrimaryAccountLink( $user, $reqs );
1792 switch (
$res->status ) {
1794 $this->logger->info(
"Account linked to {user} by $id", [
1801 $this->logger->debug( __METHOD__ .
": Account linking failed by $id", [
1813 $this->logger->debug( __METHOD__ .
": Account linking $res->status by $id", [
1817 $state[
'primary'] = $id;
1818 $state[
'continueRequests'] =
$res->neededRequests;
1819 $session->setSecret(
'AuthManager::accountLinkState', $state );
1820 $session->persist();
1825 throw new \DomainException(
1826 get_class( $provider ) .
"::beginPrimaryAccountLink() returned $res->status"
1832 $this->logger->debug( __METHOD__ .
': Account linking failed because no provider accepted', [
1836 wfMessage(
'authmanager-link-no-primary' )
1848 $session = $this->
request->getSession();
1852 $session->remove(
'AuthManager::accountLinkState' );
1853 throw new \LogicException(
'Account linking is not possible' );
1856 $state = $session->getSecret(
'AuthManager::accountLinkState' );
1857 if ( !is_array( $state ) ) {
1859 wfMessage(
'authmanager-link-not-in-progress' )
1862 $state[
'continueRequests'] = [];
1867 if ( !is_object(
$user ) ) {
1868 $session->remove(
'AuthManager::accountLinkState' );
1871 if (
$user->getId() != $state[
'userid'] ) {
1872 throw new \UnexpectedValueException(
1873 "User \"{$state['username']}\" is valid, but " .
1874 "ID {$user->getId()} != {$state['userid']}!"
1878 foreach ( $reqs
as $req ) {
1879 $req->username = $state[
'username'];
1880 $req->returnToUrl = $state[
'returnToUrl'];
1890 wfMessage(
'authmanager-link-not-in-progress' )
1893 $session->remove(
'AuthManager::accountLinkState' );
1897 $id = $provider->getUniqueId();
1898 $res = $provider->continuePrimaryAccountLink(
$user, $reqs );
1899 switch (
$res->status ) {
1901 $this->logger->info(
"Account linked to {user} by $id", [
1902 'user' =>
$user->getName(),
1905 $session->remove(
'AuthManager::accountLinkState' );
1908 $this->logger->debug( __METHOD__ .
": Account linking failed by $id", [
1909 'user' =>
$user->getName(),
1912 $session->remove(
'AuthManager::accountLinkState' );
1916 $this->logger->debug( __METHOD__ .
": Account linking $res->status by $id", [
1917 'user' =>
$user->getName(),
1920 $state[
'continueRequests'] =
$res->neededRequests;
1921 $session->setSecret(
'AuthManager::accountLinkState', $state );
1924 throw new \DomainException(
1925 get_class( $provider ) .
"::continuePrimaryAccountLink() returned $res->status"
1928 }
catch ( \Exception $ex ) {
1929 $session->remove(
'AuthManager::accountLinkState' );
1965 case self::ACTION_LOGIN:
1966 case self::ACTION_CREATE:
1972 case self::ACTION_LOGIN_CONTINUE:
1973 $state = $this->
request->getSession()->getSecret(
'AuthManager::authnState' );
1974 return is_array( $state ) ? $state[
'continueRequests'] : [];
1976 case self::ACTION_CREATE_CONTINUE:
1977 $state = $this->
request->getSession()->getSecret(
'AuthManager::accountCreationState' );
1978 return is_array( $state ) ? $state[
'continueRequests'] : [];
1980 case self::ACTION_LINK:
1986 case self::ACTION_UNLINK:
1992 $providerAction = self::ACTION_REMOVE;
1995 case self::ACTION_LINK_CONTINUE:
1996 $state = $this->
request->getSession()->getSecret(
'AuthManager::accountLinkState' );
1997 return is_array( $state ) ? $state[
'continueRequests'] : [];
1999 case self::ACTION_CHANGE:
2000 case self::ACTION_REMOVE:
2007 throw new \DomainException( __METHOD__ .
": Invalid action \"$action\"" );
2027 $options[
'username'] =
$user->isAnon() ? null :
$user->getName();
2031 $allPrimaryRequired = null;
2032 foreach ( $providers
as $provider ) {
2035 foreach ( $provider->getAuthenticationRequests( $providerAction, $options )
as $req ) {
2036 $id =
$req->getUniqueId();
2041 if (
$req->required ) {
2042 $thisRequired[$id] =
true;
2054 $allPrimaryRequired = $allPrimaryRequired === null
2056 : array_intersect_key( $allPrimaryRequired, $thisRequired );
2060 foreach ( (
array)$allPrimaryRequired
as $id => $dummy ) {
2065 switch ( $providerAction ) {
2066 case self::ACTION_LOGIN:
2070 case self::ACTION_CREATE:
2073 if ( $options[
'username'] !== null ) {
2075 $options[
'username'] = null;
2081 $this->
fillRequests( $reqs, $providerAction, $options[
'username'],
true );
2084 if ( $providerAction === self::ACTION_CHANGE || $providerAction === self::ACTION_REMOVE ) {
2085 $reqs = array_filter( $reqs,
function (
$req ) {
2090 return array_values( $reqs );
2101 foreach ( $reqs
as $req ) {
2102 if ( !$req->action || $forceAction ) {
2105 if ( $req->username === null ) {
2141 foreach ( $providers
as $provider ) {
2142 if ( !$provider->providerAllowsPropertyChange(
$property ) ) {
2159 if ( isset( $this->allAuthenticationProviders[$id] ) ) {
2160 return $this->allAuthenticationProviders[$id];
2165 if ( isset( $providers[$id] ) ) {
2166 return $providers[$id];
2169 if ( isset( $providers[$id] ) ) {
2170 return $providers[$id];
2173 if ( isset( $providers[$id] ) ) {
2174 return $providers[$id];
2194 $session = $this->
request->getSession();
2195 $arr = $session->getSecret(
'authData' );
2196 if ( !is_array( $arr ) ) {
2200 $session->setSecret(
'authData', $arr );
2211 $arr = $this->
request->getSession()->getSecret(
'authData' );
2212 if ( is_array( $arr ) && array_key_exists( $key, $arr ) ) {
2225 $session = $this->
request->getSession();
2226 if ( $key === null ) {
2227 $session->remove(
'authData' );
2229 $arr = $session->getSecret(
'authData' );
2230 if ( is_array( $arr ) && array_key_exists( $key, $arr ) ) {
2231 unset( $arr[$key] );
2232 $session->setSecret(
'authData', $arr );
2245 foreach ( $specs
as &$spec ) {
2246 $spec = [
'sort2' => $i++ ] + $spec + [
'sort' => 0 ];
2249 usort( $specs,
function ( $a, $b ) {
2250 return ( (
int)$a[
'sort'] ) - ( (
int)$b[
'sort'] )
2251 ?: $a[
'sort2'] - $b[
'sort2'];
2255 foreach ( $specs
as $spec ) {
2257 if ( !$provider instanceof $class ) {
2258 throw new \RuntimeException(
2259 "Expected instance of $class, got " . get_class( $provider )
2262 $provider->setLogger( $this->logger );
2263 $provider->setManager( $this );
2264 $provider->setConfig( $this->config );
2265 $id = $provider->getUniqueId();
2266 if ( isset( $this->allAuthenticationProviders[$id] ) ) {
2267 throw new \RuntimeException(
2268 "Duplicate specifications for id $id (classes " .
2269 get_class( $provider ) .
' and ' .
2270 get_class( $this->allAuthenticationProviders[$id] ) .
')'
2273 $this->allAuthenticationProviders[$id] = $provider;
2274 $ret[$id] = $provider;
2284 return $this->config->get(
'AuthManagerConfig' ) ?: $this->config->get(
'AuthManagerAutoConfig' );
2292 if ( $this->preAuthenticationProviders === null ) {
2306 if ( $this->primaryAuthenticationProviders === null ) {
2320 if ( $this->secondaryAuthenticationProviders === null ) {
2334 $session = $this->
request->getSession();
2335 $delay = $session->delaySave();
2337 $session->resetId();
2338 $session->resetAllTokens();
2339 if ( $session->canSetUser() ) {
2340 $session->setUser(
$user );
2342 if ( $remember !== null ) {
2343 $session->setRememberUser( $remember );
2345 $session->set(
'AuthManager:lastAuthId',
$user->getId() );
2346 $session->set(
'AuthManager:lastAuthTimestamp', time() );
2347 $session->persist();
2366 if ( $wgContLang->hasVariants() ) {
2367 $user->
setOption(
'variant', $wgContLang->getPreferredVariant() );
2387 foreach ( $providers
as $provider ) {
2388 call_user_func_array( [ $provider, $method ], $args );
2396 if ( !defined(
'MW_PHPUNIT_TEST' ) ) {
2398 throw new \MWException( __METHOD__ .
' may only be called from unit tests!' );
2402 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.
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.
static instance()
Singleton.
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form stripping il...
if(!isset($args[0])) $lang
Generic operation result class Has warning/error list, boolean status and arbitrary value...
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
$wgAuth $wgAuth
Authentication plugin.
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.
The User object encapsulates all of the user-specific settings (user_id, name, rights, email address, options, last login time).
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.
Interface for configuration instances.
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 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 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"<
Generic operation result class Has warning/error list, boolean status and arbitrary value...
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.
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
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
wfGetLBFactory()
Get the load balancer factory object.
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
$wgDisableAuthManager
Disable AuthManager.
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