Page MenuHomePhabricator

D7462.diff

diff --git a/resources/sql/patches/20131031.vcspassword.sql b/resources/sql/patches/20131031.vcspassword.sql
new file mode 100644
--- /dev/null
+++ b/resources/sql/patches/20131031.vcspassword.sql
@@ -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;
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -535,6 +535,7 @@
'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 @@
'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 @@
'DiffusionSSHGitUploadPackWorkflow' => 'DiffusionSSHGitWorkflow',
'DiffusionSSHGitWorkflow' => 'DiffusionSSHWorkflow',
'DiffusionSSHWorkflow' => 'PhabricatorSSHWorkflow',
+ 'DiffusionSetPasswordPanel' => 'PhabricatorSettingsPanel',
'DiffusionSetupException' => 'AphrontUsageException',
'DiffusionStableCommitNameQuery' => 'DiffusionQuery',
'DiffusionSvnCommitParentsQuery' => 'DiffusionCommitParentsQuery',
@@ -4015,6 +4018,7 @@
'PhabricatorRepositoryTestCase' => 'PhabricatorTestCase',
'PhabricatorRepositoryTransaction' => 'PhabricatorApplicationTransaction',
'PhabricatorRepositoryTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
+ 'PhabricatorRepositoryVCSPassword' => 'PhabricatorRepositoryDAO',
'PhabricatorS3FileStorageEngine' => 'PhabricatorFileStorageEngine',
'PhabricatorSSHWorkflow' => 'PhutilArgumentWorkflow',
'PhabricatorSavedQuery' =>
diff --git a/src/applications/diffusion/config/PhabricatorDiffusionConfigOptions.php b/src/applications/diffusion/config/PhabricatorDiffusionConfigOptions.php
--- a/src/applications/diffusion/config/PhabricatorDiffusionConfigOptions.php
+++ b/src/applications/diffusion/config/PhabricatorDiffusionConfigOptions.php
@@ -84,6 +84,24 @@
'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."))
);
}
diff --git a/src/applications/diffusion/panel/DiffusionSetPasswordPanel.php b/src/applications/diffusion/panel/DiffusionSetPasswordPanel.php
new file mode 100644
--- /dev/null
+++ b/src/applications/diffusion/panel/DiffusionSetPasswordPanel.php
@@ -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,
+ );
+ }
+
+}
diff --git a/src/applications/repository/storage/PhabricatorRepositoryVCSPassword.php b/src/applications/repository/storage/PhabricatorRepositoryVCSPassword.php
new file mode 100644
--- /dev/null
+++ b/src/applications/repository/storage/PhabricatorRepositoryVCSPassword.php
@@ -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());
+ }
+
+}
diff --git a/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php b/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
--- a/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
+++ b/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
@@ -1716,6 +1716,10 @@
'type' => 'sql',
'name' => $this->getPatchPath('20131030.repostatusmessage.sql'),
),
+ '20131031.vcspassword.sql' => array(
+ 'type' => 'sql',
+ 'name' => $this->getPatchPath('20131031.vcspassword.sql'),
+ ),
);
}
}
diff --git a/src/infrastructure/util/PhabricatorHash.php b/src/infrastructure/util/PhabricatorHash.php
--- a/src/infrastructure/util/PhabricatorHash.php
+++ b/src/infrastructure/util/PhabricatorHash.php
@@ -24,6 +24,24 @@
/**
+ * 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,
* which is generally safe in most contexts (notably, URLs).

File Metadata

Mime Type
text/x-diff
Storage Engine
amazon-s3
Storage Format
Raw Data
Storage Handle
phabricator/vq/gj/mgntpdqfl3wujyhm
Default Alt Text
D7462.diff (13 KB)

Event Timeline