public function testForAuthentication(array $reqs) { $req = AuthenticationRequest::getRequestByClass($reqs, PasswordAuthenticationRequest::class); if ($req) { $user = User::newFromName($req->username); $password = $req->password; } else { $user = null; foreach ($reqs as $req) { if ($req->username !== null) { $user = User::newFromName($req->username); break; } } if (!$user) { $this->logger->debug(__METHOD__ . ': No username in $reqs, skipping hooks'); return StatusValue::newGood(); } // Something random for the 'AbortLogin' hook. $password = wfRandomString(32); } $msg = null; if (!\Hooks::run('LoginUserMigrated', [$user, &$msg])) { return $this->makeFailResponse($user, null, LoginForm::USER_MIGRATED, $msg, 'LoginUserMigrated'); } $abort = LoginForm::ABORTED; $msg = null; if (!\Hooks::run('AbortLogin', [$user, $password, &$abort, &$msg])) { return $this->makeFailResponse($user, null, $abort, $msg, 'AbortLogin'); } return StatusValue::newGood(); }
/** * Continue the link attempt * @param User $user * @param string $key Session key to look in * @param AuthenticationRequest[] $reqs * @return AuthenticationResponse */ protected function continueLinkAttempt($user, $key, array $reqs) { $req = ButtonAuthenticationRequest::getRequestByName($reqs, 'linkOk'); if ($req) { return AuthenticationResponse::newPass(); } $req = AuthenticationRequest::getRequestByClass($reqs, ConfirmLinkAuthenticationRequest::class); if (!$req) { // WTF? Retry. return $this->beginLinkAttempt($user, $key); } $session = $this->manager->getRequest()->getSession(); $state = $session->getSecret($key); if (!is_array($state)) { return AuthenticationResponse::newAbstain(); } $maybeLink = []; foreach ($state['maybeLink'] as $linkReq) { $maybeLink[$linkReq->getUniqueId()] = $linkReq; } if (!$maybeLink) { return AuthenticationResponse::newAbstain(); } $state['maybeLink'] = []; $session->setSecret($key, $state); $statuses = []; $anyFailed = false; foreach ($req->confirmedLinkIDs as $id) { if (isset($maybeLink[$id])) { $req = $maybeLink[$id]; $req->username = $user->getName(); if (!$req->action) { // Make sure the action is set, but don't override it if // the provider filled it in. $req->action = AuthManager::ACTION_CHANGE; } $status = $this->manager->allowsAuthenticationDataChange($req); $statuses[] = [$req, $status]; if ($status->isGood()) { $this->manager->changeAuthenticationData($req); } else { $anyFailed = true; } } } if (!$anyFailed) { return AuthenticationResponse::newPass(); } $combinedStatus = \Status::newGood(); foreach ($statuses as $data) { list($req, $status) = $data; $descriptionInfo = $req->describeCredentials(); $description = wfMessage('authprovider-confirmlink-option', $descriptionInfo['provider']->text(), $descriptionInfo['account']->text())->text(); if ($status->isGood()) { $combinedStatus->error(wfMessage('authprovider-confirmlink-success-line', $description)); } else { $combinedStatus->error(wfMessage('authprovider-confirmlink-failure-line', $description, $status->getMessage()->text())); } } return AuthenticationResponse::newUI([new ButtonAuthenticationRequest('linkOk', wfMessage('ok'), wfMessage('authprovider-confirmlink-ok-help'))], $combinedStatus->getMessage('authprovider-confirmlink-failed')); }
public function beginPrimaryAccountCreation($user, $creator, array $reqs) { /** @var TemporaryPasswordAuthenticationRequest $req */ $req = AuthenticationRequest::getRequestByClass($reqs, TemporaryPasswordAuthenticationRequest::class); if ($req) { if ($req->username !== null && $req->password !== null) { // Nothing we can do yet, because the user isn't in the DB yet if ($req->username !== $user->getName()) { $req = clone $req; $req->username = $user->getName(); } if ($req->mailpassword) { // prevent EmailNotificationSecondaryAuthenticationProvider from sending another mail $this->manager->setAuthenticationSessionData('no-email', true); } $ret = AuthenticationResponse::newPass($req->username); $ret->createRequest = $req; return $ret; } } return AuthenticationResponse::newAbstain(); }
public function beginPrimaryAccountCreation($user, $creator, array $reqs) { if ($this->accountCreationType() === self::TYPE_NONE) { throw new \BadMethodCallException('Shouldn\'t call this when accountCreationType() is NONE'); } $req = AuthenticationRequest::getRequestByClass($reqs, PasswordAuthenticationRequest::class); if ($req) { if ($req->username !== null && $req->password !== null) { // Nothing we can do besides claim it, because the user isn't in // the DB yet if ($req->username !== $user->getName()) { $req = clone $req; $req->username = $user->getName(); } $ret = AuthenticationResponse::newPass($req->username); $ret->createRequest = $req; return $ret; } } return AuthenticationResponse::newAbstain(); }
/** * Continue an account creation flow * @param AuthenticationRequest[] $reqs * @return AuthenticationResponse */ public function continueAccountCreation(array $reqs) { $session = $this->request->getSession(); try { if (!$this->canCreateAccounts()) { // Caller should have called canCreateAccounts() $session->remove('AuthManager::accountCreationState'); throw new \LogicException('Account creation is not possible'); } $state = $session->getSecret('AuthManager::accountCreationState'); if (!is_array($state)) { return AuthenticationResponse::newFail(wfMessage('authmanager-create-not-in-progress')); } $state['continueRequests'] = []; // Step 0: Prepare and validate the input $user = User::newFromName($state['username'], 'creatable'); if (!is_object($user)) { $session->remove('AuthManager::accountCreationState'); $this->logger->debug(__METHOD__ . ': Invalid username', ['user' => $state['username']]); return AuthenticationResponse::newFail(wfMessage('noname')); } if ($state['creatorid']) { $creator = User::newFromId($state['creatorid']); } else { $creator = new User(); $creator->setName($state['creatorname']); } // Avoid account creation races on double submissions $cache = \ObjectCache::getLocalClusterInstance(); $lock = $cache->getScopedLock($cache->makeGlobalKey('account', md5($user->getName()))); if (!$lock) { // Don't clear AuthManager::accountCreationState for this code // path because the process that won the race owns it. $this->logger->debug(__METHOD__ . ': Could not acquire account creation lock', ['user' => $user->getName(), 'creator' => $creator->getName()]); return AuthenticationResponse::newFail(wfMessage('usernameinprogress')); } // Permissions check $status = $this->checkAccountCreatePermissions($creator); if (!$status->isGood()) { $this->logger->debug(__METHOD__ . ': {creator} cannot create users: {reason}', ['user' => $user->getName(), 'creator' => $creator->getName(), 'reason' => $status->getWikiText(null, null, 'en')]); $ret = AuthenticationResponse::newFail($status->getMessage()); $this->callMethodOnProviders(7, 'postAccountCreation', [$user, $creator, $ret]); $session->remove('AuthManager::accountCreationState'); return $ret; } // Load from master for existence check $user->load(User::READ_LOCKING); if ($state['userid'] === 0) { if ($user->getId() != 0) { $this->logger->debug(__METHOD__ . ': User exists locally', ['user' => $user->getName(), 'creator' => $creator->getName()]); $ret = AuthenticationResponse::newFail(wfMessage('userexists')); $this->callMethodOnProviders(7, 'postAccountCreation', [$user, $creator, $ret]); $session->remove('AuthManager::accountCreationState'); return $ret; } } else { if ($user->getId() == 0) { $this->logger->debug(__METHOD__ . ': User does not exist locally when it should', ['user' => $user->getName(), 'creator' => $creator->getName(), 'expected_id' => $state['userid']]); throw new \UnexpectedValueException("User \"{$state['username']}\" should exist now, but doesn't!"); } if ($user->getId() != $state['userid']) { $this->logger->debug(__METHOD__ . ': User ID/name mismatch', ['user' => $user->getName(), 'creator' => $creator->getName(), 'expected_id' => $state['userid'], 'actual_id' => $user->getId()]); throw new \UnexpectedValueException("User \"{$state['username']}\" exists, but " . "ID {$user->getId()} != {$state['userid']}!"); } } foreach ($state['reqs'] as $req) { if ($req instanceof UserDataAuthenticationRequest) { $status = $req->populateUser($user); if (!$status->isGood()) { // This should never happen... $status = Status::wrap($status); $this->logger->debug(__METHOD__ . ': UserData is invalid: {reason}', ['user' => $user->getName(), 'creator' => $creator->getName(), 'reason' => $status->getWikiText(null, null, 'en')]); $ret = AuthenticationResponse::newFail($status->getMessage()); $this->callMethodOnProviders(7, 'postAccountCreation', [$user, $creator, $ret]); $session->remove('AuthManager::accountCreationState'); return $ret; } } } foreach ($reqs as $req) { $req->returnToUrl = $state['returnToUrl']; $req->username = $state['username']; } // If we're coming in from a create-from-login UI response, we need // to extract the createRequest (if any). $req = AuthenticationRequest::getRequestByClass($reqs, CreateFromLoginAuthenticationRequest::class); if ($req && $req->createRequest) { $reqs[] = $req->createRequest; } // Run pre-creation tests, if we haven't already if (!$state['ranPreTests']) { $providers = $this->getPreAuthenticationProviders() + $this->getPrimaryAuthenticationProviders() + $this->getSecondaryAuthenticationProviders(); foreach ($providers as $id => $provider) { $status = $provider->testForAccountCreation($user, $creator, $reqs); if (!$status->isGood()) { $this->logger->debug(__METHOD__ . ": Fail in pre-authentication by {$id}", ['user' => $user->getName(), 'creator' => $creator->getName()]); $ret = AuthenticationResponse::newFail(Status::wrap($status)->getMessage()); $this->callMethodOnProviders(7, 'postAccountCreation', [$user, $creator, $ret]); $session->remove('AuthManager::accountCreationState'); return $ret; } } $state['ranPreTests'] = true; } // Step 1: Choose a primary authentication provider and call it until it succeeds. if ($state['primary'] === null) { // We haven't picked a PrimaryAuthenticationProvider yet foreach ($this->getPrimaryAuthenticationProviders() as $id => $provider) { if ($provider->accountCreationType() === PrimaryAuthenticationProvider::TYPE_NONE) { continue; } $res = $provider->beginPrimaryAccountCreation($user, $creator, $reqs); switch ($res->status) { case AuthenticationResponse::PASS: $this->logger->debug(__METHOD__ . ": Primary creation passed by {$id}", ['user' => $user->getName(), 'creator' => $creator->getName()]); $state['primary'] = $id; $state['primaryResponse'] = $res; break 2; case AuthenticationResponse::FAIL: $this->logger->debug(__METHOD__ . ": Primary creation failed by {$id}", ['user' => $user->getName(), 'creator' => $creator->getName()]); $this->callMethodOnProviders(7, 'postAccountCreation', [$user, $creator, $res]); $session->remove('AuthManager::accountCreationState'); return $res; case AuthenticationResponse::ABSTAIN: // Continue loop break; case AuthenticationResponse::REDIRECT: case AuthenticationResponse::UI: $this->logger->debug(__METHOD__ . ": Primary creation {$res->status} by {$id}", ['user' => $user->getName(), 'creator' => $creator->getName()]); $state['primary'] = $id; $state['continueRequests'] = $res->neededRequests; $session->setSecret('AuthManager::accountCreationState', $state); return $res; // @codeCoverageIgnoreStart // @codeCoverageIgnoreStart default: throw new \DomainException(get_class($provider) . "::beginPrimaryAccountCreation() returned {$res->status}"); // @codeCoverageIgnoreEnd } } if ($state['primary'] === null) { $this->logger->debug(__METHOD__ . ': Primary creation failed because no provider accepted', ['user' => $user->getName(), 'creator' => $creator->getName()]); $ret = AuthenticationResponse::newFail(wfMessage('authmanager-create-no-primary')); $this->callMethodOnProviders(7, 'postAccountCreation', [$user, $creator, $ret]); $session->remove('AuthManager::accountCreationState'); return $ret; } } elseif ($state['primaryResponse'] === null) { $provider = $this->getAuthenticationProvider($state['primary']); if (!$provider instanceof PrimaryAuthenticationProvider) { // Configuration changed? Force them to start over. // @codeCoverageIgnoreStart $ret = AuthenticationResponse::newFail(wfMessage('authmanager-create-not-in-progress')); $this->callMethodOnProviders(7, 'postAccountCreation', [$user, $creator, $ret]); $session->remove('AuthManager::accountCreationState'); return $ret; // @codeCoverageIgnoreEnd } $id = $provider->getUniqueId(); $res = $provider->continuePrimaryAccountCreation($user, $creator, $reqs); switch ($res->status) { case AuthenticationResponse::PASS: $this->logger->debug(__METHOD__ . ": Primary creation passed by {$id}", ['user' => $user->getName(), 'creator' => $creator->getName()]); $state['primaryResponse'] = $res; break; case AuthenticationResponse::FAIL: $this->logger->debug(__METHOD__ . ": Primary creation failed by {$id}", ['user' => $user->getName(), 'creator' => $creator->getName()]); $this->callMethodOnProviders(7, 'postAccountCreation', [$user, $creator, $res]); $session->remove('AuthManager::accountCreationState'); return $res; case AuthenticationResponse::REDIRECT: case AuthenticationResponse::UI: $this->logger->debug(__METHOD__ . ": Primary creation {$res->status} by {$id}", ['user' => $user->getName(), 'creator' => $creator->getName()]); $state['continueRequests'] = $res->neededRequests; $session->setSecret('AuthManager::accountCreationState', $state); return $res; default: throw new \DomainException(get_class($provider) . "::continuePrimaryAccountCreation() returned {$res->status}"); } } // Step 2: Primary authentication succeeded, create the User object // and add the user locally. if ($state['userid'] === 0) { $this->logger->info('Creating user {user} during account creation', ['user' => $user->getName(), 'creator' => $creator->getName()]); $status = $user->addToDatabase(); if (!$status->isOk()) { // @codeCoverageIgnoreStart $ret = AuthenticationResponse::newFail($status->getMessage()); $this->callMethodOnProviders(7, 'postAccountCreation', [$user, $creator, $ret]); $session->remove('AuthManager::accountCreationState'); return $ret; // @codeCoverageIgnoreEnd } $this->setDefaultUserOptions($user, $creator->isAnon()); \Hooks::run('LocalUserCreated', [$user, false]); $user->saveSettings(); $state['userid'] = $user->getId(); // Update user count \DeferredUpdates::addUpdate(new \SiteStatsUpdate(0, 0, 0, 0, 1)); // Watch user's userpage and talk page $user->addWatch($user->getUserPage(), User::IGNORE_USER_RIGHTS); // Inform the provider $logSubtype = $provider->finishAccountCreation($user, $creator, $state['primaryResponse']); // Log the creation if ($this->config->get('NewUserLog')) { $isAnon = $creator->isAnon(); $logEntry = new \ManualLogEntry('newusers', $logSubtype ?: ($isAnon ? 'create' : 'create2')); $logEntry->setPerformer($isAnon ? $user : $creator); $logEntry->setTarget($user->getUserPage()); $req = AuthenticationRequest::getRequestByClass($state['reqs'], CreationReasonAuthenticationRequest::class); $logEntry->setComment($req ? $req->reason : ''); $logEntry->setParameters(['4::userid' => $user->getId()]); $logid = $logEntry->insert(); $logEntry->publish($logid); } } // Step 3: Iterate over all the secondary authentication providers. $beginReqs = $state['reqs']; foreach ($this->getSecondaryAuthenticationProviders() as $id => $provider) { if (!isset($state['secondary'][$id])) { // This provider isn't started yet, so we pass it the set // of reqs from beginAuthentication instead of whatever // might have been used by a previous provider in line. $func = 'beginSecondaryAccountCreation'; $res = $provider->beginSecondaryAccountCreation($user, $creator, $beginReqs); } elseif (!$state['secondary'][$id]) { $func = 'continueSecondaryAccountCreation'; $res = $provider->continueSecondaryAccountCreation($user, $creator, $reqs); } else { continue; } switch ($res->status) { case AuthenticationResponse::PASS: $this->logger->debug(__METHOD__ . ": Secondary creation passed by {$id}", ['user' => $user->getName(), 'creator' => $creator->getName()]); // fall through // fall through case AuthenticationResponse::ABSTAIN: $state['secondary'][$id] = true; break; case AuthenticationResponse::REDIRECT: case AuthenticationResponse::UI: $this->logger->debug(__METHOD__ . ": Secondary creation {$res->status} by {$id}", ['user' => $user->getName(), 'creator' => $creator->getName()]); $state['secondary'][$id] = false; $state['continueRequests'] = $res->neededRequests; $session->setSecret('AuthManager::accountCreationState', $state); return $res; case AuthenticationResponse::FAIL: throw new \DomainException(get_class($provider) . "::{$func}() returned {$res->status}." . ' Secondary providers are not allowed to fail account creation, that' . ' should have been done via testForAccountCreation().'); // @codeCoverageIgnoreStart // @codeCoverageIgnoreStart default: throw new \DomainException(get_class($provider) . "::{$func}() returned {$res->status}"); // @codeCoverageIgnoreEnd } } $id = $user->getId(); $name = $user->getName(); $req = new CreatedAccountAuthenticationRequest($id, $name); $ret = AuthenticationResponse::newPass($name); $ret->loginRequest = $req; $this->createdAccountAuthenticationRequests[] = $req; $this->logger->info(__METHOD__ . ': Account creation succeeded for {user}', ['user' => $user->getName(), 'creator' => $creator->getName()]); $this->callMethodOnProviders(7, 'postAccountCreation', [$user, $creator, $ret]); $session->remove('AuthManager::accountCreationState'); $this->removeAuthenticationSessionData(null); return $ret; } catch (\Exception $ex) { $session->remove('AuthManager::accountCreationState'); throw $ex; } }
public function beginPrimaryAccountCreation($user, $creator, array $reqs) { if ($this->accountCreationType() === self::TYPE_NONE) { throw new \BadMethodCallException('Shouldn\'t call this when accountCreationType() is NONE'); } $req = AuthenticationRequest::getRequestByClass($reqs, $this->requestType); if (!$req || $req->username === null || $req->password === null || $this->hasDomain && $req->domain === null) { return AuthenticationResponse::newAbstain(); } $username = User::getCanonicalName($req->username, 'usable'); if ($username === false) { return AuthenticationResponse::newAbstain(); } $this->setDomain($req); if ($this->auth->addUser($user, $req->password, $user->getEmail(), $user->getRealName())) { return AuthenticationResponse::newPass(); } else { return AuthenticationResponse::newFail(new \Message('authmanager-authplugin-create-fail')); } }