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:
parent
40b0818207
commit
0278b15ceb
7 changed files with 295 additions and 0 deletions
8
resources/sql/patches/20131031.vcspassword.sql
Normal file
8
resources/sql/patches/20131031.vcspassword.sql
Normal 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;
|
|
@ -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' =>
|
||||
|
|
|
@ -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."))
|
||||
);
|
||||
}
|
||||
|
||||
|
|
209
src/applications/diffusion/panel/DiffusionSetPasswordPanel.php
Normal file
209
src/applications/diffusion/panel/DiffusionSetPasswordPanel.php
Normal 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,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
|
@ -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'),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in a new issue