1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2025-01-05 20:31:03 +01:00
phorge-phorge/src/applications/diffusion/panel/DiffusionSetPasswordPanel.php
epriestley d122d9ec86 Allow users to recover from a missing password hasher
Summary:
Fixes T5934. If you hash a password with, e.g., bcrypt, and then lose the bcrypt hasher for some reason, we currently fatal when trying to figure out if we can upgrade.

Instead, detect that the current hasher implementation has vanished and let the user reset their password (for account passwords) or choose a new one (for VCS passwords)>

Test Plan:
Account password:

  - Artifically disabled bcrypt hasher.
  - Viewed password panel, saw warnings about missing hasher.
  - Used password reset workflow to change password, saw iterated MD5 hashed password get set.
  - Enabled bcrypt hasher again.
  - Saw upgrade warning.
  - Upgraded password to bcrypt.

VCS password:

  - Artificially disabled bcrypt hasher.
  - Viewed password panel, saw warnings about missing hasher.
  - Reset password.
  - Saw iterated md5 password.
  - Reenabled bcrypt.
  - Upgraded to bcrypt.

Reviewers: btrahan

Reviewed By: btrahan

Subscribers: epriestley

Maniphest Tasks: T5934

Differential Revision: https://secure.phabricator.com/D10325
2014-08-21 11:30:05 -07:00

274 lines
8.4 KiB
PHP

<?php
final class DiffusionSetPasswordPanel extends PhabricatorSettingsPanel {
public function isEditableByAdministrators() {
return true;
}
public function getPanelKey() {
return 'vcspassword';
}
public function getPanelName() {
return pht('VCS Password');
}
public function getPanelGroup() {
return pht('Authentication');
}
public function isEnabled() {
return PhabricatorEnv::getEnvConfig('diffusion.allow-http-auth');
}
public function processRequest(AphrontRequest $request) {
$viewer = $request->getUser();
$user = $this->getUser();
$token = id(new PhabricatorAuthSessionEngine())->requireHighSecuritySession(
$viewer,
$request,
'/settings/');
$vcspassword = id(new PhabricatorRepositoryVCSPassword())
->loadOneWhere(
'userPHID = %s',
$user->getPHID());
if (!$vcspassword) {
$vcspassword = id(new PhabricatorRepositoryVCSPassword());
$vcspassword->setUserPHID($user->getPHID());
}
$panel_uri = $this->getPanelURI('?saved=true');
$errors = array();
$e_password = true;
$e_confirm = true;
if ($request->isFormPost()) {
if ($request->getBool('remove')) {
if ($vcspassword->getID()) {
$vcspassword->delete();
return id(new AphrontRedirectResponse())->setURI($panel_uri);
}
}
$new_password = $request->getStr('password');
$confirm = $request->getStr('confirm');
if (!strlen($new_password)) {
$e_password = pht('Required');
$errors[] = pht('Password is required.');
} else {
$e_password = null;
}
if (!strlen($confirm)) {
$e_confirm = pht('Required');
$errors[] = pht('You must confirm the new password.');
} else {
$e_confirm = null;
}
if (!$errors) {
$envelope = new PhutilOpaqueEnvelope($new_password);
try {
// NOTE: This test is against $viewer (not $user), so that the error
// message below makes sense in the case that the two are different,
// and because an admin reusing their own password is bad, while
// system agents generally do not have passwords anyway.
$same_password = $viewer->comparePassword($envelope);
} catch (PhabricatorPasswordHasherUnavailableException $ex) {
// If we're missing the hasher, just let the user continue.
$same_password = false;
}
if ($new_password !== $confirm) {
$e_password = pht('Does Not Match');
$e_confirm = pht('Does Not Match');
$errors[] = pht('Password and confirmation do not match.');
} else if ($same_password) {
$e_password = pht('Not Unique');
$e_confirm = pht('Not Unique');
$errors[] = pht(
'This password is the same as another password associated '.
'with your account. You must use a unique password for '.
'VCS access.');
} else if (
PhabricatorCommonPasswords::isCommonPassword($new_password)) {
$e_password = pht('Very Weak');
$e_confirm = pht('Very Weak');
$errors[] = pht(
'This password is extremely weak: it is one of the most common '.
'passwords in use. Choose a stronger password.');
}
if (!$errors) {
$vcspassword->setPassword($envelope, $user);
$vcspassword->save();
return id(new AphrontRedirectResponse())->setURI($panel_uri);
}
}
}
$title = pht('Set VCS Password');
$form = id(new AphrontFormView())
->setUser($viewer)
->appendRemarkupInstructions(
pht(
'To access repositories hosted by Phabricator over HTTP, you must '.
'set a version control password. This password should be unique.'.
"\n\n".
"This password applies to all repositories available over ".
"HTTP."));
if ($vcspassword->getID()) {
$form
->appendChild(
id(new AphrontFormPasswordControl())
->setDisableAutocomplete(true)
->setLabel(pht('Current Password'))
->setDisabled(true)
->setValue('********************'));
} else {
$form
->appendChild(
id(new AphrontFormMarkupControl())
->setLabel(pht('Current Password'))
->setValue(phutil_tag('em', array(), pht('No Password Set'))));
}
$form
->appendChild(
id(new AphrontFormPasswordControl())
->setDisableAutocomplete(true)
->setName('password')
->setLabel(pht('New VCS Password'))
->setError($e_password))
->appendChild(
id(new AphrontFormPasswordControl())
->setDisableAutocomplete(true)
->setName('confirm')
->setLabel(pht('Confirm VCS Password'))
->setError($e_confirm))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Change Password')));
if (!$vcspassword->getID()) {
$is_serious = PhabricatorEnv::getEnvConfig(
'phabricator.serious-business');
$suggest = Filesystem::readRandomBytes(128);
$suggest = preg_replace('([^A-Za-z0-9/!().,;{}^&*%~])', '', $suggest);
$suggest = substr($suggest, 0, 20);
if ($is_serious) {
$form->appendRemarkupInstructions(
pht(
'Having trouble coming up with a good password? Try this randomly '.
'generated one, made by a computer:'.
"\n\n".
"`%s`",
$suggest));
} else {
$form->appendRemarkupInstructions(
pht(
'Having trouble coming up with a good password? Try this '.
'artisinal password, hand made in small batches by our expert '.
'craftspeople: '.
"\n\n".
"`%s`",
$suggest));
}
}
$hash_envelope = new PhutilOpaqueEnvelope($vcspassword->getPasswordHash());
$form->appendChild(
id(new AphrontFormStaticControl())
->setLabel(pht('Current Algorithm'))
->setValue(
PhabricatorPasswordHasher::getCurrentAlgorithmName($hash_envelope)));
$form->appendChild(
id(new AphrontFormStaticControl())
->setLabel(pht('Best Available Algorithm'))
->setValue(PhabricatorPasswordHasher::getBestAlgorithmName()));
if (strlen($hash_envelope->openEnvelope())) {
try {
$can_upgrade = PhabricatorPasswordHasher::canUpgradeHash(
$hash_envelope);
} catch (PhabricatorPasswordHasherUnavailableException $ex) {
$can_upgrade = false;
$errors[] = pht(
'Your VCS password is currently hashed using an algorithm which is '.
'no longer available on this install.');
$errors[] = pht(
'Because the algorithm implementation is missing, your password '.
'can not be used.');
$errors[] = pht(
'You can set a new password to replace the old password.');
}
if ($can_upgrade) {
$errors[] = pht(
'The strength of your stored VCS password hash can be upgraded. '.
'To upgrade, either: use the password to authenticate with a '.
'repository; or change your password.');
}
}
$object_box = id(new PHUIObjectBoxView())
->setHeaderText($title)
->setForm($form)
->setFormErrors($errors);
$remove_form = id(new AphrontFormView())
->setUser($viewer);
if ($vcspassword->getID()) {
$remove_form
->addHiddenInput('remove', true)
->appendRemarkupInstructions(
pht(
'You can remove your VCS password, which will prevent your '.
'account from accessing repositories.'))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Remove Password')));
} else {
$remove_form->appendRemarkupInstructions(
pht(
'You do not currently have a VCS password set. If you set one, you '.
'can remove it here later.'));
}
$remove_box = id(new PHUIObjectBoxView())
->setHeaderText(pht('Remove VCS Password'))
->setForm($remove_form);
$saved = null;
if ($request->getBool('saved')) {
$saved = id(new AphrontErrorView())
->setSeverity(AphrontErrorView::SEVERITY_NOTICE)
->setTitle(pht('Password Updated'))
->appendChild(pht('Your VCS password has been updated.'));
}
return array(
$saved,
$object_box,
$remove_box,
);
}
}