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();
 }
Exemple #5
0
 /**
  * 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'));
     }
 }