Path: blob/master/src/applications/auth/provider/PhabricatorPasswordAuthProvider.php
12256 views
<?php12final class PhabricatorPasswordAuthProvider extends PhabricatorAuthProvider {34private $adapter;56public function getProviderName() {7return pht('Username/Password');8}910public function getConfigurationHelp() {11return pht(12"(WARNING) Examine the table below for information on how password ".13"hashes will be stored in the database.\n\n".14"(NOTE) You can select a minimum password length by setting ".15"`%s` in configuration.",16'account.minimum-password-length');17}1819public function renderConfigurationFooter() {20$hashers = PhabricatorPasswordHasher::getAllHashers();21$hashers = msort($hashers, 'getStrength');22$hashers = array_reverse($hashers);2324$yes = phutil_tag(25'strong',26array(27'style' => 'color: #009900',28),29pht('Yes'));3031$no = phutil_tag(32'strong',33array(34'style' => 'color: #990000',35),36pht('Not Installed'));3738$best_hasher_name = null;39try {40$best_hasher = PhabricatorPasswordHasher::getBestHasher();41$best_hasher_name = $best_hasher->getHashName();42} catch (PhabricatorPasswordHasherUnavailableException $ex) {43// There are no suitable hashers. The user might be able to enable some,44// so we don't want to fatal here. We'll fatal when users try to actually45// use this stuff if it isn't fixed before then. Until then, we just46// don't highlight a row. In practice, at least one hasher should always47// be available.48}4950$rows = array();51$rowc = array();52foreach ($hashers as $hasher) {53$is_installed = $hasher->canHashPasswords();5455$rows[] = array(56$hasher->getHumanReadableName(),57$hasher->getHashName(),58$hasher->getHumanReadableStrength(),59($is_installed ? $yes : $no),60($is_installed ? null : $hasher->getInstallInstructions()),61);62$rowc[] = ($best_hasher_name == $hasher->getHashName())63? 'highlighted'64: null;65}6667$table = new AphrontTableView($rows);68$table->setRowClasses($rowc);69$table->setHeaders(70array(71pht('Algorithm'),72pht('Name'),73pht('Strength'),74pht('Installed'),75pht('Install Instructions'),76));7778$table->setColumnClasses(79array(80'',81'',82'',83'',84'wide',85));8687$header = id(new PHUIHeaderView())88->setHeader(pht('Password Hash Algorithms'))89->setSubheader(90pht(91'Stronger algorithms are listed first. The highlighted algorithm '.92'will be used when storing new hashes. Older hashes will be '.93'upgraded to the best algorithm over time.'));9495return id(new PHUIObjectBoxView())96->setHeader($header)97->setTable($table);98}99100public function getDescriptionForCreate() {101return pht(102'Allow users to log in or register using a username and password.');103}104105public function getAdapter() {106if (!$this->adapter) {107$adapter = new PhutilEmptyAuthAdapter();108$adapter->setAdapterType('password');109$adapter->setAdapterDomain('self');110$this->adapter = $adapter;111}112return $this->adapter;113}114115public function getLoginOrder() {116// Make sure username/password appears first if it is enabled.117return '100-'.$this->getProviderName();118}119120public function shouldAllowAccountLink() {121return false;122}123124public function shouldAllowAccountUnlink() {125return false;126}127128public function isDefaultRegistrationProvider() {129return true;130}131132public function buildLoginForm(133PhabricatorAuthStartController $controller) {134$request = $controller->getRequest();135return $this->renderPasswordLoginForm($request);136}137138public function buildInviteForm(139PhabricatorAuthStartController $controller) {140$request = $controller->getRequest();141$viewer = $request->getViewer();142143$form = id(new AphrontFormView())144->setUser($viewer)145->addHiddenInput('invite', true)146->appendChild(147id(new AphrontFormTextControl())148->setLabel(pht('Username'))149->setName('username'));150151$dialog = id(new AphrontDialogView())152->setUser($viewer)153->setTitle(pht('Register an Account'))154->appendForm($form)155->setSubmitURI('/auth/register/')156->addSubmitButton(pht('Continue'));157158return $dialog;159}160161public function buildLinkForm($controller) {162throw new Exception(pht("Password providers can't be linked."));163}164165private function renderPasswordLoginForm(166AphrontRequest $request,167$require_captcha = false,168$captcha_valid = false) {169170$viewer = $request->getUser();171172$dialog = id(new AphrontDialogView())173->setSubmitURI($this->getLoginURI())174->setUser($viewer)175->setTitle(pht('Log In'))176->addSubmitButton(pht('Log In'));177178if ($this->shouldAllowRegistration()) {179$dialog->addCancelButton(180'/auth/register/',181pht('Register New Account'));182}183184$dialog->addFooter(185phutil_tag(186'a',187array(188'href' => '/login/email/',189),190pht('Forgot your password?')));191192$v_user = nonempty(193$request->getStr('username'),194$request->getCookie(PhabricatorCookies::COOKIE_USERNAME));195196$e_user = null;197$e_pass = null;198$e_captcha = null;199200$errors = array();201if ($require_captcha && !$captcha_valid) {202if (AphrontFormRecaptchaControl::hasCaptchaResponse($request)) {203$e_captcha = pht('Invalid');204$errors[] = pht('CAPTCHA was not entered correctly.');205} else {206$e_captcha = pht('Required');207$errors[] = pht(208'Too many login failures recently. You must '.209'submit a CAPTCHA with your login request.');210}211} else if ($request->isHTTPPost()) {212// NOTE: This is intentionally vague so as not to disclose whether a213// given username or email is registered.214$e_user = pht('Invalid');215$e_pass = pht('Invalid');216$errors[] = pht('Username or password are incorrect.');217}218219if ($errors) {220$errors = id(new PHUIInfoView())->setErrors($errors);221}222223$form = id(new PHUIFormLayoutView())224->setFullWidth(true)225->appendChild($errors)226->appendChild(227id(new AphrontFormTextControl())228->setLabel(pht('Username or Email'))229->setName('username')230->setAutofocus(true)231->setValue($v_user)232->setError($e_user))233->appendChild(234id(new AphrontFormPasswordControl())235->setLabel(pht('Password'))236->setName('password')237->setError($e_pass));238239if ($require_captcha) {240$form->appendChild(241id(new AphrontFormRecaptchaControl())242->setError($e_captcha));243}244245$dialog->appendChild($form);246247return $dialog;248}249250public function processLoginRequest(251PhabricatorAuthLoginController $controller) {252253$request = $controller->getRequest();254$viewer = $request->getUser();255$content_source = PhabricatorContentSource::newFromRequest($request);256257$rate_actor = PhabricatorSystemActionEngine::newActorFromRequest($request);258259PhabricatorSystemActionEngine::willTakeAction(260array($rate_actor),261new PhabricatorAuthTryPasswordAction(),2621);263264// If the same remote address has submitted several failed login attempts265// recently, require they provide a CAPTCHA response for new attempts.266$require_captcha = false;267$captcha_valid = false;268if (AphrontFormRecaptchaControl::isRecaptchaEnabled()) {269try {270PhabricatorSystemActionEngine::willTakeAction(271array($rate_actor),272new PhabricatorAuthTryPasswordWithoutCAPTCHAAction(),2731);274} catch (PhabricatorSystemActionRateLimitException $ex) {275$require_captcha = true;276$captcha_valid = AphrontFormRecaptchaControl::processCaptcha($request);277}278}279280$response = null;281$account = null;282$log_user = null;283284if ($request->isFormPost()) {285if (!$require_captcha || $captcha_valid) {286$username_or_email = $request->getStr('username');287if (strlen($username_or_email)) {288$user = id(new PhabricatorUser())->loadOneWhere(289'username = %s',290$username_or_email);291292if (!$user) {293$user = PhabricatorUser::loadOneWithEmailAddress(294$username_or_email);295}296297if ($user) {298$envelope = new PhutilOpaqueEnvelope($request->getStr('password'));299300$engine = id(new PhabricatorAuthPasswordEngine())301->setViewer($user)302->setContentSource($content_source)303->setPasswordType(PhabricatorAuthPassword::PASSWORD_TYPE_ACCOUNT)304->setObject($user);305306if ($engine->isValidPassword($envelope)) {307$account = $this->newExternalAccountForUser($user);308$log_user = $user;309}310}311}312}313}314315if (!$account) {316if ($request->isFormPost()) {317$log = PhabricatorUserLog::initializeNewLog(318null,319$log_user ? $log_user->getPHID() : null,320PhabricatorLoginFailureUserLogType::LOGTYPE);321$log->save();322}323324$request->clearCookie(PhabricatorCookies::COOKIE_USERNAME);325326$response = $controller->buildProviderPageResponse(327$this,328$this->renderPasswordLoginForm(329$request,330$require_captcha,331$captcha_valid));332}333334return array($account, $response);335}336337public function shouldRequireRegistrationPassword() {338return true;339}340341public static function getPasswordProvider() {342$providers = self::getAllEnabledProviders();343344foreach ($providers as $provider) {345if ($provider instanceof PhabricatorPasswordAuthProvider) {346return $provider;347}348}349350return null;351}352353public function willRenderLinkedAccount(354PhabricatorUser $viewer,355PHUIObjectItemView $item,356PhabricatorExternalAccount $account) {357return;358}359360public function shouldAllowAccountRefresh() {361return false;362}363364public function shouldAllowEmailTrustConfiguration() {365return false;366}367368}369370371