Changeset View
Changeset View
Standalone View
Standalone View
src/applications/settings/panel/PhabricatorPasswordSettingsPanel.php
Show All 20 Lines | if (!PhabricatorPasswordAuthProvider::getPasswordProvider()) { | ||||
return false; | return false; | ||||
} | } | ||||
return true; | return true; | ||||
} | } | ||||
public function processRequest(AphrontRequest $request) { | public function processRequest(AphrontRequest $request) { | ||||
$user = $request->getUser(); | $user = $request->getUser(); | ||||
$content_source = PhabricatorContentSource::newFromRequest($request); | |||||
$token = id(new PhabricatorAuthSessionEngine())->requireHighSecuritySession( | $token = id(new PhabricatorAuthSessionEngine())->requireHighSecuritySession( | ||||
$user, | $user, | ||||
$request, | $request, | ||||
'/settings/'); | '/settings/'); | ||||
$min_len = PhabricatorEnv::getEnvConfig('account.minimum-password-length'); | $min_len = PhabricatorEnv::getEnvConfig('account.minimum-password-length'); | ||||
$min_len = (int)$min_len; | $min_len = (int)$min_len; | ||||
// NOTE: Users can also change passwords through the separate "set/reset" | // NOTE: Users can also change passwords through the separate "set/reset" | ||||
// interface which is reached by logging in with a one-time token after | // interface which is reached by logging in with a one-time token after | ||||
// registration or password reset. If this flow changes, that flow may | // registration or password reset. If this flow changes, that flow may | ||||
// also need to change. | // also need to change. | ||||
$account_type = PhabricatorAuthPassword::PASSWORD_TYPE_ACCOUNT; | |||||
$password_objects = id(new PhabricatorAuthPasswordQuery()) | |||||
->setViewer($user) | |||||
->withObjectPHIDs(array($user->getPHID())) | |||||
->withPasswordTypes(array($account_type)) | |||||
->withIsRevoked(false) | |||||
->execute(); | |||||
if ($password_objects) { | |||||
$password_object = head($password_objects); | |||||
} else { | |||||
$password_object = PhabricatorAuthPassword::initializeNewPassword( | |||||
$user, | |||||
$account_type); | |||||
} | |||||
$e_old = true; | $e_old = true; | ||||
$e_new = true; | $e_new = true; | ||||
$e_conf = true; | $e_conf = true; | ||||
$errors = array(); | $errors = array(); | ||||
if ($request->isFormPost()) { | if ($request->isFormPost()) { | ||||
$envelope = new PhutilOpaqueEnvelope($request->getStr('old_pw')); | $envelope = new PhutilOpaqueEnvelope($request->getStr('old_pw')); | ||||
if (!$user->comparePassword($envelope)) { | |||||
$engine = id(new PhabricatorAuthPasswordEngine()) | |||||
->setViewer($user) | |||||
->setContentSource($content_source) | |||||
->setPasswordType($account_type) | |||||
->setObject($user); | |||||
if (!strlen($envelope->openEnvelope())) { | |||||
$errors[] = pht('You must enter your current password.'); | |||||
$e_old = pht('Required'); | |||||
} else if (!$engine->isValidPassword($envelope)) { | |||||
$errors[] = pht('The old password you entered is incorrect.'); | $errors[] = pht('The old password you entered is incorrect.'); | ||||
$e_old = pht('Invalid'); | $e_old = pht('Invalid'); | ||||
} else { | |||||
$e_old = null; | |||||
} | } | ||||
$pass = $request->getStr('new_pw'); | $pass = $request->getStr('new_pw'); | ||||
$conf = $request->getStr('conf_pw'); | $conf = $request->getStr('conf_pw'); | ||||
$password_envelope = new PhutilOpaqueEnvelope($pass); | |||||
$confirm_envelope = new PhutilOpaqueEnvelope($conf); | |||||
if (strlen($pass) < $min_len) { | try { | ||||
$errors[] = pht('Your new password is too short.'); | $engine->checkNewPassword($password_envelope, $confirm_envelope); | ||||
$e_new = pht('Too Short'); | $e_new = null; | ||||
} else if ($pass !== $conf) { | $e_conf = null; | ||||
$errors[] = pht('New password and confirmation do not match.'); | } catch (PhabricatorAuthPasswordException $ex) { | ||||
$e_conf = pht('Invalid'); | $errors[] = $ex->getMessage(); | ||||
} else if (PhabricatorCommonPasswords::isCommonPassword($pass)) { | $e_new = $ex->getPasswordError(); | ||||
$e_new = pht('Very Weak'); | $e_conf = $ex->getConfirmError(); | ||||
$e_conf = pht('Very Weak'); | |||||
$errors[] = pht( | |||||
'Your new password is very weak: it is one of the most common '. | |||||
'passwords in use. Choose a stronger password.'); | |||||
} | } | ||||
if (!$errors) { | if (!$errors) { | ||||
// This write is unguarded because the CSRF token has already | $password_object | ||||
// been checked in the call to $request->isFormPost() and | ->setPassword($password_envelope, $user) | ||||
// the CSRF token depends on the password hash, so when it | ->save(); | ||||
// is changed here the CSRF token check will fail. | |||||
epriestley: It turns out this comment is out of date; CSRF tokens haven't depended on any secret shared… | |||||
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); | |||||
$envelope = new PhutilOpaqueEnvelope($pass); | |||||
id(new PhabricatorUserEditor()) | |||||
->setActor($user) | |||||
->changePassword($user, $envelope); | |||||
unset($unguarded); | |||||
$next = $this->getPanelURI('?saved=true'); | $next = $this->getPanelURI('?saved=true'); | ||||
id(new PhabricatorAuthSessionEngine())->terminateLoginSessions( | id(new PhabricatorAuthSessionEngine())->terminateLoginSessions( | ||||
$user, | $user, | ||||
$request->getCookie(PhabricatorCookies::COOKIE_SESSION)); | $request->getCookie(PhabricatorCookies::COOKIE_SESSION)); | ||||
return id(new AphrontRedirectResponse())->setURI($next); | return id(new AphrontRedirectResponse())->setURI($next); | ||||
} | } | ||||
} | } | ||||
$hash_envelope = new PhutilOpaqueEnvelope($user->getPasswordHash()); | if ($password_object->getID()) { | ||||
if (strlen($hash_envelope->openEnvelope())) { | |||||
try { | try { | ||||
$can_upgrade = PhabricatorPasswordHasher::canUpgradeHash( | $can_upgrade = $password_object->canUpgrade(); | ||||
$hash_envelope); | |||||
} catch (PhabricatorPasswordHasherUnavailableException $ex) { | } catch (PhabricatorPasswordHasherUnavailableException $ex) { | ||||
$can_upgrade = false; | $can_upgrade = false; | ||||
$errors[] = pht( | $errors[] = pht( | ||||
'Your password is currently hashed using an algorithm which is '. | 'Your password is currently hashed using an algorithm which is '. | ||||
'no longer available on this install.'); | 'no longer available on this install.'); | ||||
$errors[] = pht( | $errors[] = pht( | ||||
'Because the algorithm implementation is missing, your password '. | 'Because the algorithm implementation is missing, your password '. | ||||
Show All 40 Lines | $form = id(new AphrontFormView()) | ||||
id(new AphrontFormSubmitControl()) | id(new AphrontFormSubmitControl()) | ||||
->setValue(pht('Change Password'))); | ->setValue(pht('Change Password'))); | ||||
$properties = id(new PHUIPropertyListView()); | $properties = id(new PHUIPropertyListView()); | ||||
$properties->addProperty( | $properties->addProperty( | ||||
pht('Current Algorithm'), | pht('Current Algorithm'), | ||||
PhabricatorPasswordHasher::getCurrentAlgorithmName( | PhabricatorPasswordHasher::getCurrentAlgorithmName( | ||||
new PhutilOpaqueEnvelope($user->getPasswordHash()))); | $password_object->newPasswordEnvelope())); | ||||
$properties->addProperty( | $properties->addProperty( | ||||
pht('Best Available Algorithm'), | pht('Best Available Algorithm'), | ||||
PhabricatorPasswordHasher::getBestAlgorithmName()); | PhabricatorPasswordHasher::getBestAlgorithmName()); | ||||
$info_view = id(new PHUIInfoView()) | $info_view = id(new PHUIInfoView()) | ||||
->setSeverity(PHUIInfoView::SEVERITY_NOTICE) | ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) | ||||
->appendChild( | ->appendChild( | ||||
Show All 20 Lines |
It turns out this comment is out of date; CSRF tokens haven't depended on any secret shared with passwords in a long time.