public function execute() { $params = $this->extractRequestParams(); $this->requireAtLeastOneParameter($params, 'continue', 'returnurl'); if ($params['returnurl'] !== null) { $bits = wfParseUrl($params['returnurl']); if (!$bits || $bits['scheme'] === '') { $encParamName = $this->encodeParamName('returnurl'); $this->dieUsage("Invalid value '{$params['returnurl']}' for url parameter {$encParamName}", "badurl_{$encParamName}"); } } $helper = new ApiAuthManagerHelper($this); $manager = AuthManager::singleton(); // Make sure it's possible to log in if (!$manager->canAuthenticateNow()) { $this->getResult()->addValue(null, 'clientlogin', $helper->formatAuthenticationResponse(AuthenticationResponse::newFail($this->msg('userlogin-cannot-' . AuthManager::ACTION_LOGIN)))); return; } // Perform the login step if ($params['continue']) { $reqs = $helper->loadAuthenticationRequests(AuthManager::ACTION_LOGIN_CONTINUE); $res = $manager->continueAuthentication($reqs); } else { $reqs = $helper->loadAuthenticationRequests(AuthManager::ACTION_LOGIN); if ($params['preservestate']) { $req = $helper->getPreservedRequest(); if ($req) { $reqs[] = $req; } } $res = $manager->beginAuthentication($reqs, $params['returnurl']); } $this->getResult()->addValue(null, 'clientlogin', $helper->formatAuthenticationResponse($res)); }
public function execute() { if (!$this->getUser()->isLoggedIn()) { $this->dieUsage('Must be logged in to link accounts', 'notloggedin'); } $params = $this->extractRequestParams(); $this->requireAtLeastOneParameter($params, 'continue', 'returnurl'); if ($params['returnurl'] !== null) { $bits = wfParseUrl($params['returnurl']); if (!$bits || $bits['scheme'] === '') { $encParamName = $this->encodeParamName('returnurl'); $this->dieUsage("Invalid value '{$params['returnurl']}' for url parameter {$encParamName}", "badurl_{$encParamName}"); } } $helper = new ApiAuthManagerHelper($this); $manager = AuthManager::singleton(); // Check security-sensitive operation status $helper->securitySensitiveOperation('LinkAccounts'); // Make sure it's possible to link accounts if (!$manager->canLinkAccounts()) { $this->getResult()->addValue(null, 'linkaccount', $helper->formatAuthenticationResponse(AuthenticationResponse::newFail($this->msg('userlogin-cannot-' . AuthManager::ACTION_LINK)))); return; } // Perform the link step if ($params['continue']) { $reqs = $helper->loadAuthenticationRequests(AuthManager::ACTION_LINK_CONTINUE); $res = $manager->continueAccountLink($reqs); } else { $reqs = $helper->loadAuthenticationRequests(AuthManager::ACTION_LINK); $res = $manager->beginAccountLink($this->getUser(), $reqs, $params['returnurl']); } $this->getResult()->addValue(null, 'linkaccount', $helper->formatAuthenticationResponse($res)); }
/** * Return the appropriate response for failure * @param PasswordAuthenticationRequest $req * @return AuthenticationResponse */ protected function failResponse(PasswordAuthenticationRequest $req) { if ($this->authoritative) { return AuthenticationResponse::newFail(wfMessage($req->password === '' ? 'wrongpasswordempty' : 'wrongpassword')); } else { return AuthenticationResponse::newAbstain(); } }
public function beginSecondaryAuthentication($user, array $reqs) { if (!$this->blockDisablesLogin) { return AuthenticationResponse::newAbstain(); } elseif ($user->isBlocked()) { return AuthenticationResponse::newFail(new \Message('login-userblocked', [$user->getName()])); } else { return AuthenticationResponse::newPass(); } }
public function execute() { $params = $this->extractRequestParams(); $this->requireAtLeastOneParameter($params, 'continue', 'returnurl'); if ($params['returnurl'] !== null) { $bits = wfParseUrl($params['returnurl']); if (!$bits || $bits['scheme'] === '') { $encParamName = $this->encodeParamName('returnurl'); $this->dieUsage("Invalid value '{$params['returnurl']}' for url parameter {$encParamName}", "badurl_{$encParamName}"); } } $helper = new ApiAuthManagerHelper($this); $manager = AuthManager::singleton(); // Make sure it's possible to log in if (!$manager->canAuthenticateNow()) { $this->getResult()->addValue(null, 'clientlogin', $helper->formatAuthenticationResponse(AuthenticationResponse::newFail($this->msg('userlogin-cannot-' . AuthManager::ACTION_LOGIN)))); $helper->logAuthenticationResult('login', 'userlogin-cannot-' . AuthManager::ACTION_LOGIN); return; } // Perform the login step if ($params['continue']) { $reqs = $helper->loadAuthenticationRequests(AuthManager::ACTION_LOGIN_CONTINUE); $res = $manager->continueAuthentication($reqs); } else { $reqs = $helper->loadAuthenticationRequests(AuthManager::ACTION_LOGIN); if ($params['preservestate']) { $req = $helper->getPreservedRequest(); if ($req) { $reqs[] = $req; } } $res = $manager->beginAuthentication($reqs, $params['returnurl']); } // Remove CreateFromLoginAuthenticationRequest from $res->neededRequests. // It's there so a RESTART treated as UI will work right, but showing // it to the API client is just confusing. $res->neededRequests = ApiAuthManagerHelper::blacklistAuthenticationRequests($res->neededRequests, [CreateFromLoginAuthenticationRequest::class]); $this->getResult()->addValue(null, 'clientlogin', $helper->formatAuthenticationResponse($res)); $helper->logAuthenticationResult('login', $res); }
/** * @param string $action One of the AuthManager::ACTION_* constants * @param AuthenticationRequest[] $requests * @return AuthenticationResponse * @throws LogicException if $action is invalid */ protected function performAuthenticationStep($action, array $requests) { if (!in_array($action, static::$allowedActions, true)) { throw new InvalidArgumentException('invalid action: ' . $action); } $authManager = AuthManager::singleton(); $returnToUrl = $this->getPageTitle('return')->getFullURL($this->getPreservedParams(true), false, PROTO_HTTPS); switch ($action) { case AuthManager::ACTION_LOGIN: return $authManager->beginAuthentication($requests, $returnToUrl); case AuthManager::ACTION_LOGIN_CONTINUE: return $authManager->continueAuthentication($requests); case AuthManager::ACTION_CREATE: return $authManager->beginAccountCreation($this->getUser(), $requests, $returnToUrl); case AuthManager::ACTION_CREATE_CONTINUE: return $authManager->continueAccountCreation($requests); case AuthManager::ACTION_LINK: return $authManager->beginAccountLink($this->getUser(), $requests, $returnToUrl); case AuthManager::ACTION_LINK_CONTINUE: return $authManager->continueAccountLink($requests); case AuthManager::ACTION_CHANGE: case AuthManager::ACTION_REMOVE: case AuthManager::ACTION_UNLINK: if (count($requests) > 1) { throw new InvalidArgumentException('only one auth request can be changed at a time'); } elseif (!$requests) { throw new InvalidArgumentException('no auth request'); } $req = reset($requests); $status = $authManager->allowsAuthenticationDataChange($req); Hooks::run('ChangeAuthenticationDataAudit', [$req, $status]); if (!$status->isOK()) { return AuthenticationResponse::newFail($status->getMessage()); } $authManager->changeAuthenticationData($req); return AuthenticationResponse::newPass(); default: // should never reach here but makes static code analyzers happy throw new InvalidArgumentException('invalid action: ' . $action); } }
public function provideAccountLink() { $req = $this->getMockForAbstractClass(AuthenticationRequest::class); $good = StatusValue::newGood(); return ['Pre-link test fail in pre' => [StatusValue::newFatal('fail-from-pre'), [], [AuthenticationResponse::newFail($this->message('fail-from-pre'))]], 'Failure in primary' => [$good, $tmp = [AuthenticationResponse::newFail($this->message('fail-from-primary'))], $tmp], 'All primary abstain' => [$good, [AuthenticationResponse::newAbstain()], [AuthenticationResponse::newFail($this->message('authmanager-link-no-primary'))]], 'Primary UI, then redirect, then fail' => [$good, $tmp = [AuthenticationResponse::newUI([$req], $this->message('...')), AuthenticationResponse::newRedirect([$req], '/foo.html', ['foo' => 'bar']), AuthenticationResponse::newFail($this->message('fail-in-primary-continue'))], $tmp], 'Primary redirect, then abstain' => [$good, [$tmp = AuthenticationResponse::newRedirect([$req], '/foo.html', ['foo' => 'bar']), AuthenticationResponse::newAbstain()], [$tmp, new \DomainException('MockPrimaryAuthenticationProvider::continuePrimaryAccountLink() returned ABSTAIN')]], 'Primary UI, then pass' => [$good, [$tmp1 = AuthenticationResponse::newUI([$req], $this->message('...')), AuthenticationResponse::newPass()], [$tmp1, AuthenticationResponse::newPass('')]], 'Primary pass' => [$good, [AuthenticationResponse::newPass('')], [AuthenticationResponse::newPass('')]]]; }
public function beginPrimaryAuthentication(array $reqs) { $req = AuthenticationRequest::getRequestByClass($reqs, PasswordAuthenticationRequest::class); if (!$req || $req->username === null || $req->password === null) { return AuthenticationResponse::newAbstain(); } $username = User::getCanonicalName($req->username, 'usable'); if ($username === false) { return AuthenticationResponse::newAbstain(); } $dbr = wfGetDB(DB_REPLICA); $row = $dbr->selectRow('user', ['user_id', 'user_newpassword', 'user_newpass_time'], ['user_name' => $username], __METHOD__); if (!$row) { return AuthenticationResponse::newAbstain(); } $status = $this->checkPasswordValidity($username, $req->password); if (!$status->isOK()) { // Fatal, can't log in return AuthenticationResponse::newFail($status->getMessage()); } $pwhash = $this->getPassword($row->user_newpassword); if (!$pwhash->equals($req->password)) { return $this->failResponse($req); } if (!$this->isTimestampValid($row->user_newpass_time)) { return $this->failResponse($req); } $this->setPasswordResetFlag($username, $status); return AuthenticationResponse::newPass($username); }
public function beginPrimaryAuthentication(array $reqs) { $req = AuthenticationRequest::getRequestByClass($reqs, PasswordAuthenticationRequest::class); if (!$req) { return AuthenticationResponse::newAbstain(); } if ($req->username === null || $req->password === null) { return AuthenticationResponse::newAbstain(); } $username = User::getCanonicalName($req->username, 'usable'); if ($username === false) { return AuthenticationResponse::newAbstain(); } $fields = ['user_id', 'user_password', 'user_password_expires']; $dbr = wfGetDB(DB_REPLICA); $row = $dbr->selectRow('user', $fields, ['user_name' => $username], __METHOD__); if (!$row) { return AuthenticationResponse::newAbstain(); } $oldRow = clone $row; // Check for *really* old password hashes that don't even have a type // The old hash format was just an md5 hex hash, with no type information if (preg_match('/^[0-9a-f]{32}$/', $row->user_password)) { if ($this->config->get('PasswordSalt')) { $row->user_password = "******"; } else { $row->user_password = "******"; } } $status = $this->checkPasswordValidity($username, $req->password); if (!$status->isOK()) { // Fatal, can't log in return AuthenticationResponse::newFail($status->getMessage()); } $pwhash = $this->getPassword($row->user_password); if (!$pwhash->equals($req->password)) { if ($this->config->get('LegacyEncoding')) { // Some wikis were converted from ISO 8859-1 to UTF-8, the passwords can't be converted // Check for this with iconv $cp1252Password = iconv('UTF-8', 'WINDOWS-1252//TRANSLIT', $req->password); if ($cp1252Password === $req->password || !$pwhash->equals($cp1252Password)) { return $this->failResponse($req); } } else { return $this->failResponse($req); } } // @codeCoverageIgnoreStart if ($this->getPasswordFactory()->needsUpdate($pwhash)) { $pwhash = $this->getPasswordFactory()->newFromPlaintext($req->password); \DeferredUpdates::addCallableUpdate(function () use($pwhash, $oldRow) { $dbw = wfGetDB(DB_MASTER); $dbw->update('user', ['user_password' => $pwhash->toString()], ['user_id' => $oldRow->user_id, 'user_password' => $oldRow->user_password], __METHOD__); }); } // @codeCoverageIgnoreEnd $this->setPasswordResetFlag($username, $status, $row); return AuthenticationResponse::newPass($username); }
/** * Continue an account linking flow * @param AuthenticationRequest[] $reqs * @return AuthenticationResponse */ public function continueAccountLink(array $reqs) { $session = $this->request->getSession(); try { if (!$this->canLinkAccounts()) { // Caller should have called canLinkAccounts() $session->remove('AuthManager::accountLinkState'); throw new \LogicException('Account linking is not possible'); } $state = $session->getSecret('AuthManager::accountLinkState'); if (!is_array($state)) { return AuthenticationResponse::newFail(wfMessage('authmanager-link-not-in-progress')); } $state['continueRequests'] = []; // Step 0: Prepare and validate the input $user = User::newFromName($state['username'], 'usable'); if (!is_object($user)) { $session->remove('AuthManager::accountLinkState'); return AuthenticationResponse::newFail(wfMessage('noname')); } if ($user->getId() != $state['userid']) { throw new \UnexpectedValueException("User \"{$state['username']}\" is valid, but " . "ID {$user->getId()} != {$state['userid']}!"); } foreach ($reqs as $req) { $req->username = $state['username']; $req->returnToUrl = $state['returnToUrl']; } // Step 1: Call the primary again until it succeeds $provider = $this->getAuthenticationProvider($state['primary']); if (!$provider instanceof PrimaryAuthenticationProvider) { // Configuration changed? Force them to start over. // @codeCoverageIgnoreStart $ret = AuthenticationResponse::newFail(wfMessage('authmanager-link-not-in-progress')); $this->callMethodOnProviders(3, 'postAccountLink', [$user, $ret]); $session->remove('AuthManager::accountLinkState'); return $ret; // @codeCoverageIgnoreEnd } $id = $provider->getUniqueId(); $res = $provider->continuePrimaryAccountLink($user, $reqs); switch ($res->status) { case AuthenticationResponse::PASS: $this->logger->info("Account linked to {user} by {$id}", ['user' => $user->getName()]); $this->callMethodOnProviders(3, 'postAccountLink', [$user, $res]); $session->remove('AuthManager::accountLinkState'); return $res; case AuthenticationResponse::FAIL: $this->logger->debug(__METHOD__ . ": Account linking failed by {$id}", ['user' => $user->getName()]); $this->callMethodOnProviders(3, 'postAccountLink', [$user, $res]); $session->remove('AuthManager::accountLinkState'); return $res; case AuthenticationResponse::REDIRECT: case AuthenticationResponse::UI: $this->logger->debug(__METHOD__ . ": Account linking {$res->status} by {$id}", ['user' => $user->getName()]); $state['continueRequests'] = $res->neededRequests; $session->setSecret('AuthManager::accountLinkState', $state); return $res; default: throw new \DomainException(get_class($provider) . "::continuePrimaryAccountLink() returned {$res->status}"); } } catch (\Exception $ex) { $session->remove('AuthManager::accountLinkState'); 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')); } }