1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-28 16:30:59 +01:00

Implementation of VCS passwords against user.

Summary: This allows users to set their HTTP access passwords via Diffusion interface.

Test Plan: Clicked the "Set HTTP Access Password" link, set a password and saw it appear in the DB.

Reviewers: #blessed_reviewers, hach-que, btrahan

Reviewed By: hach-que

CC: Korvin, epriestley, aran, jamesr

Maniphest Tasks: T2230

Differential Revision: https://secure.phabricator.com/D7462
This commit is contained in:
epriestley 2013-11-01 08:34:11 -07:00
parent 40b0818207
commit 0278b15ceb
7 changed files with 295 additions and 0 deletions

View file

@ -0,0 +1,8 @@
CREATE TABLE {$NAMESPACE}_repository.repository_vcspassword (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
userPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
passwordHash VARCHAR(50) NOT NULL COLLATE utf8_bin,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL,
UNIQUE KEY `key_phid` (userPHID)
) ENGINE=InnoDB, CHARSET utf8;

View file

@ -535,6 +535,7 @@ phutil_register_library_map(array(
'DiffusionSSHGitUploadPackWorkflow' => 'applications/diffusion/ssh/DiffusionSSHGitUploadPackWorkflow.php',
'DiffusionSSHGitWorkflow' => 'applications/diffusion/ssh/DiffusionSSHGitWorkflow.php',
'DiffusionSSHWorkflow' => 'applications/diffusion/ssh/DiffusionSSHWorkflow.php',
'DiffusionSetPasswordPanel' => 'applications/diffusion/panel/DiffusionSetPasswordPanel.php',
'DiffusionSetupException' => 'applications/diffusion/exception/DiffusionSetupException.php',
'DiffusionStableCommitNameQuery' => 'applications/diffusion/query/stablecommitname/DiffusionStableCommitNameQuery.php',
'DiffusionSvnCommitParentsQuery' => 'applications/diffusion/query/parents/DiffusionSvnCommitParentsQuery.php',
@ -1674,6 +1675,7 @@ phutil_register_library_map(array(
'PhabricatorRepositoryTransaction' => 'applications/repository/storage/PhabricatorRepositoryTransaction.php',
'PhabricatorRepositoryTransactionQuery' => 'applications/repository/query/PhabricatorRepositoryTransactionQuery.php',
'PhabricatorRepositoryType' => 'applications/repository/constants/PhabricatorRepositoryType.php',
'PhabricatorRepositoryVCSPassword' => 'applications/repository/storage/PhabricatorRepositoryVCSPassword.php',
'PhabricatorS3FileStorageEngine' => 'applications/files/engine/PhabricatorS3FileStorageEngine.php',
'PhabricatorSQLPatchList' => 'infrastructure/storage/patch/PhabricatorSQLPatchList.php',
'PhabricatorSSHWorkflow' => 'infrastructure/ssh/PhabricatorSSHWorkflow.php',
@ -2729,6 +2731,7 @@ phutil_register_library_map(array(
'DiffusionSSHGitUploadPackWorkflow' => 'DiffusionSSHGitWorkflow',
'DiffusionSSHGitWorkflow' => 'DiffusionSSHWorkflow',
'DiffusionSSHWorkflow' => 'PhabricatorSSHWorkflow',
'DiffusionSetPasswordPanel' => 'PhabricatorSettingsPanel',
'DiffusionSetupException' => 'AphrontUsageException',
'DiffusionStableCommitNameQuery' => 'DiffusionQuery',
'DiffusionSvnCommitParentsQuery' => 'DiffusionCommitParentsQuery',
@ -4015,6 +4018,7 @@ phutil_register_library_map(array(
'PhabricatorRepositoryTestCase' => 'PhabricatorTestCase',
'PhabricatorRepositoryTransaction' => 'PhabricatorApplicationTransaction',
'PhabricatorRepositoryTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorRepositoryVCSPassword' => 'PhabricatorRepositoryDAO',
'PhabricatorS3FileStorageEngine' => 'PhabricatorFileStorageEngine',
'PhabricatorSSHWorkflow' => 'PhutilArgumentWorkflow',
'PhabricatorSavedQuery' =>

View file

@ -84,6 +84,24 @@ final class PhabricatorDiffusionConfigOptions
'Regular expression to link external bug tracker. See '.
'http://tortoisesvn.net/docs/release/TortoiseSVN_en/'.
'tsvn-dug-bugtracker.html for further explanation.')),
$this->newOption('diffusion.allow-http-auth', 'bool', false)
->setBoolOptions(
array(
pht('Allow HTTP Basic Auth'),
pht('Disable HTTP Basic Auth'),
))
->setSummary(pht('Enable HTTP Basic Auth for repositories.'))
->setDescription(
pht(
"Phabricator can serve repositories over HTTP, using HTTP basic ".
"auth.\n\n".
"Because HTTP basic auth is less secure than SSH auth, it is ".
"disabled by default. You can enable it here if you'd like to use ".
"it anyway. There's nothing fundamentally insecure about it as ".
"long as Phabricator uses HTTPS, but it presents a much lower ".
"barrier to attackers than SSH does.\n\n".
"Consider using SSH for authenticated access to repositories ".
"instead of HTTP."))
);
}

View file

@ -0,0 +1,209 @@
<?php
final class DiffusionSetPasswordPanel extends PhabricatorSettingsPanel {
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) {
$user = $request->getUser();
$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);
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 ($user->comparePassword($envelope)) {
$e_password = pht('Not Unique');
$e_confirm = pht('Not Unique');
$errors[] = pht(
'This password is not unique. You must use a unique password.');
}
if (!$errors) {
$vcspassword->setPassword($envelope, $user);
$vcspassword->save();
return id(new AphrontRedirectResponse())->setURI($panel_uri);
}
}
}
$title = pht('Set VCS Password');
$error_view = null;
if ($errors) {
$error_view = id(new AphrontErrorView())
->setTitle(pht('Form Errors'))
->setErrors($errors);
}
$form = id(new AphrontFormView())
->setUser($user)
->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())
->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())
->setName('password')
->setLabel(pht('New VCS Password'))
->setError($e_password))
->appendChild(
id(new AphrontFormPasswordControl())
->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));
}
}
$object_box = id(new PHUIObjectBoxView())
->setHeaderText($title)
->setForm($form)
->setFormError($error_view);
$remove_form = id(new AphrontFormView())
->setUser($user);
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,
);
}
}

View file

@ -0,0 +1,34 @@
<?php
final class PhabricatorRepositoryVCSPassword extends PhabricatorRepositoryDAO {
protected $id;
protected $userPHID;
protected $passwordHash;
public function setPassword(
PhutilOpaqueEnvelope $password,
PhabricatorUser $user) {
return $this->setPasswordHash($this->hashPassword($password, $user));
}
public function comparePassword(
PhutilOpaqueEnvelope $password,
PhabricatorUser $user) {
$hash = $this->hashPassword($password, $user);
return ($hash == $this->getPasswordHash());
}
private function hashPassword(
PhutilOpaqueEnvelope $password,
PhabricatorUser $user) {
if ($user->getPHID() != $this->getUserPHID()) {
throw new Exception("User does not match password user PHID!");
}
return PhabricatorHash::digestPassword($password, $user->getPHID());
}
}

View file

@ -1716,6 +1716,10 @@ final class PhabricatorBuiltinPatchList extends PhabricatorSQLPatchList {
'type' => 'sql',
'name' => $this->getPatchPath('20131030.repostatusmessage.sql'),
),
'20131031.vcspassword.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20131031.vcspassword.sql'),
),
);
}
}

View file

@ -23,6 +23,24 @@ final class PhabricatorHash {
}
/**
* Digest a string into a password hash. This is similar to @{method:digest},
* but requires a salt and iterates the hash to increase cost.
*/
public static function digestPassword(PhutilOpaqueEnvelope $envelope, $salt) {
$result = $envelope->openEnvelope();
if (!$result) {
throw new Exception("Trying to digest empty password!");
}
for ($ii = 0; $ii < 1000; $ii++) {
$result = PhabricatorHash::digest($result, $salt);
}
return $result;
}
/**
* Digest a string for use in, e.g., a MySQL index. This produces a short
* (12-byte), case-sensitive alphanumeric string with 72 bits of entropy,