Page MenuHomePhabricator

D7391.id16654.diff
No OneTemporary

D7391.id16654.diff

Index: resources/sql/patches/20131024.diffusionhost.sql
===================================================================
--- /dev/null
+++ resources/sql/patches/20131024.diffusionhost.sql
@@ -0,0 +1,5 @@
+ALTER TABLE {$NAMESPACE}_repository.repository
+ADD COLUMN pushPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin;
+
+ALTER TABLE {$NAMESPACE}_repository.repository
+ADD COLUMN hostingPath VARCHAR(255) NULL DEFAULT NULL COLLATE utf8_bin;
Index: resources/sql/patches/20131024.vcspassword.sql
===================================================================
--- /dev/null
+++ resources/sql/patches/20131024.vcspassword.sql
@@ -0,0 +1,2 @@
+ALTER TABLE {$NAMESPACE}_user.user
+ADD COLUMN vcsPassword VARCHAR(10) NULL DEFAULT NULL COLLATE utf8_bin;
Index: src/__phutil_library_map__.php
===================================================================
--- src/__phutil_library_map__.php
+++ src/__phutil_library_map__.php
@@ -18,6 +18,7 @@
'AphrontAjaxResponse' => 'aphront/response/AphrontAjaxResponse.php',
'AphrontApplicationConfiguration' => 'aphront/configuration/AphrontApplicationConfiguration.php',
'AphrontBarView' => 'view/widget/bars/AphrontBarView.php',
+ 'AphrontBasicAuthResponse' => 'aphront/response/AphrontBasicAuthResponse.php',
'AphrontCSRFException' => 'aphront/exception/AphrontCSRFException.php',
'AphrontCalendarEventView' => 'applications/calendar/view/AphrontCalendarEventView.php',
'AphrontCalendarMonthView' => 'applications/calendar/view/AphrontCalendarMonthView.php',
@@ -51,6 +52,7 @@
'AphrontFormToggleButtonsControl' => 'view/form/control/AphrontFormToggleButtonsControl.php',
'AphrontFormTokenizerControl' => 'view/form/control/AphrontFormTokenizerControl.php',
'AphrontFormView' => 'view/form/AphrontFormView.php',
+ 'AphrontGitResponse' => 'aphront/response/AphrontGitResponse.php',
'AphrontGlyphBarView' => 'view/widget/bars/AphrontGlyphBarView.php',
'AphrontHTMLResponse' => 'aphront/response/AphrontHTMLResponse.php',
'AphrontHTTPSink' => 'aphront/sink/AphrontHTTPSink.php',
@@ -475,6 +477,7 @@
'DiffusionGitFileContentQuery' => 'applications/diffusion/query/filecontent/DiffusionGitFileContentQuery.php',
'DiffusionGitRawDiffQuery' => 'applications/diffusion/query/rawdiff/DiffusionGitRawDiffQuery.php',
'DiffusionGitRequest' => 'applications/diffusion/request/DiffusionGitRequest.php',
+ 'DiffusionGitServeController' => 'applications/diffusion/controller/DiffusionGitServeController.php',
'DiffusionGitStableCommitNameQuery' => 'applications/diffusion/query/stablecommitname/DiffusionGitStableCommitNameQuery.php',
'DiffusionHistoryController' => 'applications/diffusion/controller/DiffusionHistoryController.php',
'DiffusionHistoryTableView' => 'applications/diffusion/view/DiffusionHistoryTableView.php',
@@ -619,6 +622,7 @@
'FileCreateMailReceiver' => 'applications/files/mail/FileCreateMailReceiver.php',
'FileMailReceiver' => 'applications/files/mail/FileMailReceiver.php',
'FileReplyHandler' => 'applications/files/mail/FileReplyHandler.php',
+ 'GitHTTPServer' => 'infrastructure/git/GitHTTPServer.php',
'HarbormasterBuild' => 'applications/harbormaster/storage/build/HarbormasterBuild.php',
'HarbormasterBuildArtifact' => 'applications/harbormaster/storage/build/HarbormasterBuildArtifact.php',
'HarbormasterBuildItem' => 'applications/harbormaster/storage/build/HarbormasterBuildItem.php',
@@ -1528,6 +1532,7 @@
'PhabricatorPolicyCapability' => 'applications/policy/capability/PhabricatorPolicyCapability.php',
'PhabricatorPolicyCapabilityCanEdit' => 'applications/policy/capability/PhabricatorPolicyCapabilityCanEdit.php',
'PhabricatorPolicyCapabilityCanJoin' => 'applications/policy/capability/PhabricatorPolicyCapabilityCanJoin.php',
+ 'PhabricatorPolicyCapabilityCanPush' => 'applications/policy/capability/PhabricatorPolicyCapabilityCanPush.php',
'PhabricatorPolicyCapabilityCanView' => 'applications/policy/capability/PhabricatorPolicyCapabilityCanView.php',
'PhabricatorPolicyConfigOptions' => 'applications/policy/config/PhabricatorPolicyConfigOptions.php',
'PhabricatorPolicyConstants' => 'applications/policy/constants/PhabricatorPolicyConstants.php',
@@ -1699,6 +1704,7 @@
'PhabricatorSettingsPanelPassword' => 'applications/settings/panel/PhabricatorSettingsPanelPassword.php',
'PhabricatorSettingsPanelSSHKeys' => 'applications/settings/panel/PhabricatorSettingsPanelSSHKeys.php',
'PhabricatorSettingsPanelSearchPreferences' => 'applications/settings/panel/PhabricatorSettingsPanelSearchPreferences.php',
+ 'PhabricatorSettingsPanelVCSPassword' => 'applications/settings/panel/PhabricatorSettingsPanelVCSPassword.php',
'PhabricatorSetupCheck' => 'applications/config/check/PhabricatorSetupCheck.php',
'PhabricatorSetupCheckAPC' => 'applications/config/check/PhabricatorSetupCheckAPC.php',
'PhabricatorSetupCheckAuth' => 'applications/config/check/PhabricatorSetupCheckAuth.php',
@@ -2201,6 +2207,7 @@
'AphrontAbstractAttachedFileView' => 'AphrontView',
'AphrontAjaxResponse' => 'AphrontResponse',
'AphrontBarView' => 'AphrontView',
+ 'AphrontBasicAuthResponse' => 'AphrontResponse',
'AphrontCSRFException' => 'AphrontException',
'AphrontCalendarEventView' => 'AphrontView',
'AphrontCalendarMonthView' => 'AphrontView',
@@ -2234,6 +2241,7 @@
'AphrontFormToggleButtonsControl' => 'AphrontFormControl',
'AphrontFormTokenizerControl' => 'AphrontFormControl',
'AphrontFormView' => 'AphrontView',
+ 'AphrontGitResponse' => 'AphrontResponse',
'AphrontGlyphBarView' => 'AphrontBarView',
'AphrontHTMLResponse' => 'AphrontResponse',
'AphrontHTTPSinkTestCase' => 'PhabricatorTestCase',
@@ -2646,6 +2654,7 @@
'DiffusionGitFileContentQuery' => 'DiffusionFileContentQuery',
'DiffusionGitRawDiffQuery' => 'DiffusionRawDiffQuery',
'DiffusionGitRequest' => 'DiffusionRequest',
+ 'DiffusionGitServeController' => 'DiffusionController',
'DiffusionGitStableCommitNameQuery' => 'DiffusionStableCommitNameQuery',
'DiffusionHistoryController' => 'DiffusionController',
'DiffusionHistoryTableView' => 'DiffusionView',
@@ -3817,6 +3826,7 @@
'PhabricatorPolicyCapability' => 'Phobject',
'PhabricatorPolicyCapabilityCanEdit' => 'PhabricatorPolicyCapability',
'PhabricatorPolicyCapabilityCanJoin' => 'PhabricatorPolicyCapability',
+ 'PhabricatorPolicyCapabilityCanPush' => 'PhabricatorPolicyCapability',
'PhabricatorPolicyCapabilityCanView' => 'PhabricatorPolicyCapability',
'PhabricatorPolicyConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorPolicyController' => 'PhabricatorController',
@@ -3997,6 +4007,7 @@
'PhabricatorSettingsPanelPassword' => 'PhabricatorSettingsPanel',
'PhabricatorSettingsPanelSSHKeys' => 'PhabricatorSettingsPanel',
'PhabricatorSettingsPanelSearchPreferences' => 'PhabricatorSettingsPanel',
+ 'PhabricatorSettingsPanelVCSPassword' => 'PhabricatorSettingsPanel',
'PhabricatorSetupCheckAPC' => 'PhabricatorSetupCheck',
'PhabricatorSetupCheckAuth' => 'PhabricatorSetupCheck',
'PhabricatorSetupCheckBaseURI' => 'PhabricatorSetupCheck',
Index: src/aphront/response/AphrontBasicAuthResponse.php
===================================================================
--- /dev/null
+++ src/aphront/response/AphrontBasicAuthResponse.php
@@ -0,0 +1,23 @@
+<?php
+
+final class AphrontBasicAuthResponse extends AphrontResponse {
+
+ public function buildResponseString() {
+ return "";
+ }
+
+ public function getHeaders() {
+ return array(
+ array('WWW-Authenticate', 'Basic realm="Phabricator Git Repository"'));
+ }
+
+ public function getCacheHeaders() {
+ return array();
+ }
+
+ public function getHTTPResponseCode() {
+ return 401;
+ }
+
+}
+
Index: src/aphront/response/AphrontGitResponse.php
===================================================================
--- /dev/null
+++ src/aphront/response/AphrontGitResponse.php
@@ -0,0 +1,47 @@
+<?php
+
+final class AphrontGitResponse extends AphrontResponse {
+
+ private $httpCode;
+ private $headers;
+ private $response;
+
+ public function setGitData($data) {
+ // We have to parse lines up until the HTTP double newline. First
+ // split on \r\n\r\n to get the headers and content as separate entries.
+ $headeridx = strpos($data, "\r\n\r\n");
+ $this->response = substr($data, $headeridx + 4);
+
+ // Now parse the headers, the CGI command can potentially output "Status:"
+ // to change the HTTP status code.
+ $lines = explode("\r\n", substr($data, 0, $headeridx));
+
+ $this->headers = array();
+ if (substr($lines[0], 0, 7) === "Status:") {
+ $this->httpCode = substr($lines[0], 8, 3);
+ } else {
+ $this->httpCode = 200;
+ }
+ for ($i = 1; $i < count($lines); $i++) {
+ $this->headers[] = explode(": ", $lines[$i]);
+ }
+ }
+
+ public function buildResponseString() {
+ return $this->response;
+ }
+
+ public function getHeaders() {
+ return $this->headers;
+ }
+
+ public function getCacheHeaders() {
+ return array();
+ }
+
+ public function getHTTPResponseCode() {
+ return $this->httpCode;
+ }
+
+}
+
Index: src/applications/diffusion/application/PhabricatorApplicationDiffusion.php
===================================================================
--- src/applications/diffusion/application/PhabricatorApplicationDiffusion.php
+++ src/applications/diffusion/application/PhabricatorApplicationDiffusion.php
@@ -44,6 +44,7 @@
'(?:query/(?P<queryKey>[^/]+)/)?'
=> 'DiffusionRepositoryListController',
'create/' => 'DiffusionRepositoryCreateController',
+ 'git/(?P<callsign>[A-Z]+)(?<url>.*)' => 'DiffusionGitServeController',
'(?P<callsign>[A-Z]+)/' => array(
'' => 'DiffusionRepositoryController',
Index: src/applications/diffusion/controller/DiffusionGitServeController.php
===================================================================
--- /dev/null
+++ src/applications/diffusion/controller/DiffusionGitServeController.php
@@ -0,0 +1,110 @@
+<?php
+
+final class DiffusionGitServeController extends DiffusionController {
+
+ private $callsign;
+ private $url;
+
+ public function willProcessRequest(array $data) {
+ $this->callsign = idx($data, 'callsign');
+ $this->url = idx($data, 'url');
+ }
+
+ public function shouldRequireLogin() {
+ return false;
+ }
+
+ public function shouldRequireAdmin() {
+ return false;
+ }
+
+ public function shouldRequireEnabledUser() {
+ return false;
+ }
+
+ public function shouldAllowPublic() {
+ return true;
+ }
+
+ private function isReadOnlyRequest() {
+ return $_SERVER['REQUEST_METHOD'] === 'GET';
+ }
+
+ public function processRequest() {
+ // Retrieve the associated hosted repository.
+ $repository = id(new PhabricatorRepository)
+ ->loadOneWhere("callsign = %s", $this->callsign);
+ if ($repository === null) {
+ throw new Exception(
+ "Repository not found (callsign: ".$this->callsign.")!");
+ }
+ if (!$repository->isHosted()) {
+ throw new Exception("This is not a hosted repository.");
+ }
+ $path = $repository->getHostingPath();
+
+ // Start with not authorized state.
+ $permitted = false;
+
+ // If this is a read-only operation, and the view policy of the
+ // repository is PhabricatorPolicies::POLICY_PUBLIC, then we don't
+ // need to prompt for authorization.
+ if ($this->isReadOnlyRequest() &&
+ $repository->getViewPolicy() == PhabricatorPolicies::POLICY_PUBLIC) {
+ $permitted = true;
+ }
+
+ // If we're not permitted at this point, then we do need basic HTTP
+ // authorization to be completed by the user.
+ if (!$permitted) {
+ if (!isset($_SERVER['PHP_AUTH_USER']) ||
+ !isset($_SERVER['PHP_AUTH_PW'])) {
+ return new AphrontBasicAuthResponse();
+ }
+
+ // Validate who the user is based on Git settings. We get provide users
+ // with a fake password they can send over basic HTTP; that way, even
+ // people who login with other providers such as Google.
+ $user = id(new PhabricatorUser())
+ ->loadOneWhere(
+ "userName = %s AND vcsPassword = %s",
+ $_SERVER['PHP_AUTH_USER'],
+ $_SERVER['PHP_AUTH_PW']);
+ if ($user === null) {
+ return new AphrontBasicAuthResponse();
+ }
+
+ // Check policies. viewPolicy allows GET, editPolicy allows POST / PUT.
+ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
+ $permitted = PhabricatorPolicyFilter::hasCapability(
+ $user,
+ $repository,
+ PhabricatorPolicyCapability::CAN_VIEW);
+ }
+ if ($_SERVER['REQUEST_METHOD'] === 'POST' ||
+ $_SERVER['REQUEST_METHOD'] === 'PUT') {
+ $permitted = PhabricatorPolicyFilter::hasCapability(
+ $user,
+ $repository,
+ PhabricatorPolicyCapability::CAN_PUSH);
+ }
+ }
+
+ // If not permitted, throw 403 Forbidden.
+ if (!$permitted) {
+ return new Aphront403Response();
+ }
+
+ // TODO: Switch type of server based on
+ // $repository->getVersionControlSystem()!
+ $server = new GitHTTPServer();
+ $server->setProjectRoot($path);
+ $data = $server->acceptRequest($this->url);
+
+ $response = new AphrontGitResponse();
+ $response->setGitData($data);
+ return $response;
+ }
+
+}
+
Index: src/applications/people/storage/PhabricatorUser.php
===================================================================
--- src/applications/people/storage/PhabricatorUser.php
+++ src/applications/people/storage/PhabricatorUser.php
@@ -25,6 +25,7 @@
protected $consoleTab = '';
protected $conduitCertificate;
+ protected $vcsPassword;
protected $isSystemAgent = 0;
protected $isAdmin = 0;
@@ -111,6 +112,9 @@
if (!$this->getConduitCertificate()) {
$this->setConduitCertificate($this->generateConduitCertificate());
}
+ if (!$this->getVCSPassword()) {
+ $this->setVCSPassword($this->generateVCSPassword());
+ }
$result = parent::save();
if ($this->profile) {
@@ -129,6 +133,10 @@
return Filesystem::readRandomCharacters(255);
}
+ private function generateVCSPassword() {
+ return Filesystem::readRandomCharacters(10);
+ }
+
public function comparePassword(PhutilOpaqueEnvelope $envelope) {
if (!strlen($envelope->openEnvelope())) {
return false;
Index: src/applications/policy/capability/PhabricatorPolicyCapability.php
===================================================================
--- src/applications/policy/capability/PhabricatorPolicyCapability.php
+++ src/applications/policy/capability/PhabricatorPolicyCapability.php
@@ -5,6 +5,7 @@
const CAN_VIEW = 'view';
const CAN_EDIT = 'edit';
const CAN_JOIN = 'join';
+ const CAN_PUSH = 'push';
/**
* Get the unique key identifying this capability. This key must be globally
Index: src/applications/policy/capability/PhabricatorPolicyCapabilityCanPush.php
===================================================================
--- /dev/null
+++ src/applications/policy/capability/PhabricatorPolicyCapabilityCanPush.php
@@ -0,0 +1,18 @@
+<?php
+
+final class PhabricatorPolicyCapabilityCanPush
+ extends PhabricatorPolicyCapability {
+
+ public function getCapabilityKey() {
+ return self::CAN_PUSH;
+ }
+
+ public function getCapabilityName() {
+ return pht('Can Push');
+ }
+
+ public function describeCapabilityRejection() {
+ return pht('You do not have permission to push to this object.');
+ }
+
+}
Index: src/applications/repository/controller/PhabricatorRepositoryEditController.php
===================================================================
--- src/applications/repository/controller/PhabricatorRepositoryEditController.php
+++ src/applications/repository/controller/PhabricatorRepositoryEditController.php
@@ -29,6 +29,7 @@
$views = array(
'basic' => 'Basics',
'tracking' => 'Tracking',
+ 'hosting' => 'Hosting'
);
$this->repository = $repository;
@@ -54,6 +55,8 @@
return $this->processBasicRequest();
case 'tracking':
return $this->processTrackingRequest();
+ case 'hosting':
+ return $this->processHostingRequest();
default:
throw new Exception("Unknown view.");
}
@@ -693,4 +696,242 @@
));
}
+ private function processHostingRequest() {
+ $request = $this->getRequest();
+ $user = $request->getUser();
+ $repository = $this->repository;
+ $repository_id = $repository->getID();
+
+ $errors = array();
+
+ if ($request->isFormPost()) {
+ $hosting = ($request->getStr('hosting') == 'enabled' ? true : false);
+
+ if ($hosting) {
+ // Before we go and enable any hosting, attempt to set up the directory
+ // that will hold the repository first.
+ $path = $request->getStr('path');
+ if (empty($path)) {
+ $errors[] =
+ 'The path must be set before when repository '.
+ 'hosting is enabled.';
+ }
+
+ if (count($errors) === 0) {
+ // Check if the directory exists.
+ if (is_dir($path)) {
+ // The directory already exists. This could be an existing
+ // repository, or a directory that already has contents.
+
+ // TODO: Check if the directory contains a repository and report
+ // and error if it doesn't.
+ } else {
+ // The directory does not exist. Try to create it and report
+ // on failure.
+ if (!mkdir($path, 0700)) {
+ $errors[] =
+ 'Unable to create the directory to host the repository.';
+ } else {
+ // The directory was created successfully. Now perform the
+ // source control initialization.
+ switch ($repository->getVersionControlSystem()) {
+ case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
+ // TODO: Should this be exec_manual to show a normal error
+ // when `git init` fails?
+ list($out, $err) = execx("%C init --bare %s", "git", $path);
+ break;
+ case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
+ $errors[] =
+ 'Subversion hosting is not supported.';
+ break;
+ case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
+ $errors[] =
+ 'Mercurial hosting is not supported.';
+ break;
+ default:
+ throw new Exception("Unsupported VCS!");
+ }
+
+ }
+ }
+ }
+ }
+
+ // Now modify the hosted repository information.
+ if (count($errors) === 0 || !$hosting) {
+ if ($hosting && !$repository->isHosted()) {
+ $repository->setHostingPath($request->getStr('path'));
+ } else if (!$hosting && $repository->isHosted()) {
+ $repository->setHostingPath(null);
+ } else if ($repository->isHosted()) {
+ $repository->setHostingPath($request->getStr('path'));
+ $repository->setPushPolicy($request->getStr('can_push'));
+ }
+ $repository->save();
+ }
+
+ if (!$errors) {
+ $repository->save();
+ return id(new AphrontRedirectResponse())
+ ->setURI('/repository/edit/'.$repository_id.'/hosting/?saved=true');
+ }
+ }
+
+ $error_view = null;
+ if ($errors) {
+ $error_view = new AphrontErrorView();
+ $error_view->setErrors($errors);
+ $error_view->setTitle('Form Errors');
+ } else if ($request->getStr('saved')) {
+ $error_view = new AphrontErrorView();
+ $error_view->setSeverity(AphrontErrorView::SEVERITY_NOTICE);
+ $error_view->setTitle('Changes Saved');
+ $error_view->appendChild('Hosting changes were saved.');
+ } else if (!$repository->isHosted()) {
+ $error_view = new AphrontErrorView();
+ $error_view->setSeverity(AphrontErrorView::SEVERITY_WARNING);
+ $error_view->setTitle('Repository Not Hosted');
+ $error_view->appendChild(
+ 'Hosting is currently "Disabled" for this repository, so it will '.
+ 'not be served by Phabricator. You can enable it below.');
+ }
+
+ $doc_href = PhabricatorEnv::getDoclink('article/Diffusion_User_Guide.html');
+ $user_guide_link = phutil_tag(
+ 'a',
+ array(
+ 'href' => $doc_href,
+ ),
+ 'Diffusion User Guide');
+
+ $form = new AphrontFormView();
+ $form
+ ->setUser($user)
+ ->setAction('/repository/edit/'.$repository->getID().'/hosting/')
+ ->appendChild(hsprintf(
+ '<p class="aphront-form-instructions">Phabricator can host '.
+ 'repositories. More information is available in the '.
+ '<strong>%s</strong>.</p>',
+ $user_guide_link));
+
+ $form
+ ->appendChild(
+ id(new AphrontFormInsetView())
+ ->setTitle('Basics')
+ ->appendChild(id(new AphrontFormStaticControl())
+ ->setLabel('Repository Name')
+ ->setValue($repository->getName()))
+ ->appendChild(id(new AphrontFormSelectControl())
+ ->setName('hosting')
+ ->setLabel('Hosting')
+ ->setOptions(array(
+ 'disabled' => 'Disabled',
+ 'enabled' => 'Enabled',
+ ))
+ ->setValue(
+ $repository->isHosted()
+ ? 'enabled'
+ : 'disabled')));
+
+ $inset = new AphrontFormInsetView();
+ $inset->setTitle('Local Storage');
+
+ $init_command = "unknown";
+ switch ($repository->getVersionControlSystem()) {
+ case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
+ $init_command = "git init --bare";
+ break;
+ case PhabricatorRepositoryType::REPOSITORY_TYPE_HG:
+ $init_command = "hg init";
+ break;
+ case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
+ $init_command = "svn init";
+ break;
+ }
+
+ $inset->appendChild(hsprintf(
+ '<p class="aphront-form-instructions">Select a path on the local disk '.
+ 'where the repository should be stored. If this path does not exist, '.
+ 'or the directory is empty and not already initialized, Phabricator '.
+ 'will run <tt>%s</tt> for you.</p>',
+ $init_command));
+ $inset
+ ->appendChild(
+ id(new AphrontFormTextControl())
+ ->setName('path')
+ ->setLabel('Local Path')
+ ->setID('path')
+ ->setValue($repository->getHostingPath()));
+
+ $form->appendChild($inset);
+
+ $inset = new AphrontFormInsetView();
+ $inset->setTitle('Policies');
+ $inset->appendChild(hsprintf(
+ '<p class="aphront-form-instructions">Specify which Phabricator '.
+ 'users can push new changes to the repository.</p>'));
+
+ $policies = id(new PhabricatorPolicyQuery())
+ ->setViewer($user)
+ ->setObject($repository)
+ ->execute();
+
+ $inset->appendChild(
+ id(new AphrontFormPolicyControl())
+ ->setUser($user)
+ ->setName('can_push')
+ ->setPolicyObject($repository)
+ ->setPolicies($policies)
+ ->setCapability(PhabricatorPolicyCapability::CAN_PUSH));
+
+ $form->appendChild($inset);
+
+ $inset = new AphrontFormInsetView();
+ $inset->setTitle('Repository URIs');
+ $inset->appendChild(hsprintf(
+ '<p class="aphront-form-instructions">The following URIs specify where '.
+ 'your users can clone or checkout the repository from.</p>'));
+
+ switch ($repository->getVersionControlSystem()) {
+ case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
+ $baseURI = trim(
+ PhabricatorEnv::getEnvConfig('phabricator.base-uri'),
+ '/');
+ $inset
+ ->appendChild(id(new AphrontFormStaticControl())
+ ->setLabel('Read URI')
+ ->setValue($baseURI."/diffusion/git/".$repository->getCallsign()));
+ $inset
+ ->appendChild(id(new AphrontFormStaticControl())
+ ->setLabel('Write URI')
+ ->setValue($baseURI."/diffusion/git/".$repository->getCallsign()));
+ break;
+ case PhabricatorRepositoryType::REPOSITORY_TYPE_HG:
+ break;
+ case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
+ break;
+ }
+
+ $form->appendChild($inset);
+
+ $form
+ ->appendChild(
+ id(new AphrontFormSubmitControl())
+ ->setValue('Save Configuration'));
+
+ $form_box = id(new PHUIObjectBoxView())
+ ->setHeaderText(pht('Edit Repository Hosting'))
+ ->setFormError($error_view)
+ ->setForm($form);
+
+ $nav = $this->sideNav;
+ $nav->appendChild($form_box);
+
+ return $this->buildApplicationPage(
+ $nav,
+ array(
+ 'title' => pht('Edit Repository Hosting'),
+ ));
+ }
+
}
Index: src/applications/repository/storage/PhabricatorRepository.php
===================================================================
--- src/applications/repository/storage/PhabricatorRepository.php
+++ src/applications/repository/storage/PhabricatorRepository.php
@@ -30,9 +30,11 @@
protected $uuid;
protected $viewPolicy = PhabricatorPolicies::POLICY_USER;
protected $editPolicy = PhabricatorPolicies::POLICY_ADMIN;
+ protected $pushPolicy = PhabricatorPolicies::POLICY_USER;
protected $versionControlSystem;
protected $details = array();
+ protected $hostingPath;
private $sshKeyfile;
@@ -356,6 +358,10 @@
return $this->getDetail('tracking-enabled', false);
}
+ public function isHosted() {
+ return $this->getHostingPath() !== null;
+ }
+
public function getDefaultBranch() {
$default = $this->getDetail('default-branch');
if (strlen($default)) {
@@ -684,6 +690,7 @@
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
+ PhabricatorPolicyCapability::CAN_PUSH,
);
}
@@ -693,6 +700,8 @@
return $this->getViewPolicy();
case PhabricatorPolicyCapability::CAN_EDIT:
return $this->getEditPolicy();
+ case PhabricatorPolicyCapability::CAN_PUSH:
+ return $this->getPushPolicy();
}
}
Index: src/applications/settings/panel/PhabricatorSettingsPanelVCSPassword.php
===================================================================
--- /dev/null
+++ src/applications/settings/panel/PhabricatorSettingsPanelVCSPassword.php
@@ -0,0 +1,102 @@
+<?php
+
+final class PhabricatorSettingsPanelVCSPassword
+ extends PhabricatorSettingsPanel {
+
+ public function getPanelKey() {
+ return 'vcspassword';
+ }
+
+ public function getPanelName() {
+ return pht('Version Control');
+ }
+
+ public function getPanelGroup() {
+ return pht('Authentication');
+ }
+
+ public function processRequest(AphrontRequest $request) {
+ $user = $request->getUser();
+
+ if ($request->isFormPost()) {
+ if (!$request->isDialogFormPost()) {
+ $dialog = new AphrontDialogView();
+ $dialog->setUser($user);
+ $dialog->setTitle(pht('Really regenerate password?'));
+ $dialog->setSubmitURI($this->getPanelURI());
+ $dialog->addSubmitButton(pht('Regenerate'));
+ $dialog->addCancelbutton($this->getPanelURI());
+ $dialog->appendChild(phutil_tag('p', array(), pht(
+ 'Really destroy the old password? Any existing '.
+ 'version control configurations will need to be '.
+ 'updated.')));
+
+ return id(new AphrontDialogResponse())
+ ->setDialog($dialog);
+ }
+
+ // This implicitly regenerates the version control password.
+ $user->setVCSPassword(null);
+ $user->save();
+ return id(new AphrontRedirectResponse())
+ ->setURI($this->getPanelURI('?regenerated=true'));
+ }
+
+ if ($request->getStr('regenerated')) {
+ $notice = new AphrontErrorView();
+ $notice->setSeverity(AphrontErrorView::SEVERITY_NOTICE);
+ $notice->setTitle(pht('Version Control Password Regenerated'));
+ $notice->appendChild(phutil_tag(
+ 'p',
+ array(),
+ pht('Your old password has been destroyed and you have been issued '.
+ 'a new password. Update your version control client if needed.')));
+ $notice = $notice->render();
+ } else {
+ $notice = null;
+ }
+
+ $cert_form = new AphrontFormView();
+ $cert_form
+ ->setUser($user)
+ ->appendChild(hsprintf(
+ '<p class="aphront-form-instructions">%s</p>',
+ pht(
+ 'This password is used to contribute to Phabricator '.
+ 'hosted repositories. Instead of using your normal password '.
+ 'when connecting to a host repository, provide the password '.
+ 'below.')))
+ ->appendChild(
+ id(new AphrontFormTextControl())
+ ->setLabel(pht('Password'))
+ ->setValue($user->getVCSPassword()));
+
+ $cert_form = id(new PHUIObjectBoxView())
+ ->setHeaderText(pht('Version Control Password'))
+ ->setForm($cert_form);
+
+ $regen_instruction = pht('You can regenerate this password, which '.
+ 'will invalidate the old password and issue a new one.');
+
+ $regen_form = new AphrontFormView();
+ $regen_form
+ ->setUser($user)
+ ->setAction($this->getPanelURI())
+ ->setWorkflow(true)
+ ->appendChild(hsprintf(
+ '<p class="aphront-form-instructions">%s</p>', $regen_instruction))
+ ->appendChild(
+ id(new AphrontFormSubmitControl())
+ ->setValue(pht('Regenerate Password')));
+
+ $regen_form = id(new PHUIObjectBoxView())
+ ->setHeaderText(pht('Regenerate Password'))
+ ->setForm($regen_form);
+
+ return array(
+ $notice,
+ $cert_form,
+ $regen_form,
+ );
+ }
+}
Index: src/infrastructure/git/GitHTTPServer.php
===================================================================
--- /dev/null
+++ src/infrastructure/git/GitHTTPServer.php
@@ -0,0 +1,71 @@
+<?php
+
+final class GitHTTPServer {
+
+ private $path;
+ private $projectRoot;
+
+ public function __construct($path = null) {
+ $this->path = $path;
+ if ($this->path === null) {
+ $this->path = "/usr/lib/git/git-http-backend";
+ }
+ }
+
+ public function setProjectRoot($root) {
+ $this->projectRoot = $root;
+ }
+
+ /**
+ * Accepts an Aphront request and returns the
+ * HTTP result from git-http-backend.
+ */
+ public function acceptRequest($url) {
+ if (!function_exists("proc_open")) {
+ throw new Exception("proc_open does not exist");
+ }
+
+ // Set up environment and pipes.
+ $env = array(
+ 'REQUEST_METHOD' => $_SERVER['REQUEST_METHOD'],
+ 'QUERY_STRING' => $_SERVER['QUERY_STRING'],
+ 'CONTENT_TYPE' => $_SERVER['CONTENT_TYPE'],
+ 'REMOTE_USER' => "",
+ 'REMOTE_ADDR' => $_SERVER['REMOTE_ADDR'],
+ 'GIT_PROJECT_ROOT' => $this->projectRoot,
+ 'GIT_HTTP_EXPORT_ALL' => '1',
+ 'PATH_INFO' => $url);
+ $descriptors = array(
+ 0 => array("pipe", "r"),
+ 1 => array("pipe", "w"),
+ 2 => array("pipe", "w"));
+ $pwd = getcwd();
+
+ // Execute git-http-backend. We have to use `proc_open`
+ $pipes = array();
+ $process = proc_open($this->path, $descriptors, $pipes, $pwd, $env);
+ if (!is_resource($process)) {
+ throw new Exception("proc_open did not start git-http-backend");
+ }
+
+ // Pass in the standard input, which may contain content pushed
+ // to Git.
+ fwrite($pipes[0], PhabricatorStartup::getRawInput());
+ fclose($pipes[0]);
+
+ // Read out the response, including HTTP headers.
+ $stdout = stream_get_contents($pipes[1]);
+ fclose($pipes[1]);
+ fclose($pipes[2]);
+
+ // Close the process.
+ $err = proc_close($process);
+
+ // Returns the output of git-http-backend, which
+ // is the direct HTTP response we should return (including
+ // HTTP headers).
+ return $stdout;
+ }
+
+}
+
Index: src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
===================================================================
--- src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
+++ src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
@@ -1704,6 +1704,14 @@
'type' => 'sql',
'name' => $this->getPatchPath('20131020.harbormaster.sql'),
),
+ '20131024.diffusionhost.sql' => array(
+ 'type' => 'sql',
+ 'name' => $this->getPatchPath('20131024.diffusionhost.sql'),
+ ),
+ '20131024.vcspassword.sql' => array(
+ 'type' => 'sql',
+ 'name' => $this->getPatchPath('20131024.vcspassword.sql'),
+ ),
);
}
}
Index: src/view/form/control/AphrontFormPolicyControl.php
===================================================================
--- src/view/form/control/AphrontFormPolicyControl.php
+++ src/view/form/control/AphrontFormPolicyControl.php
@@ -24,6 +24,7 @@
PhabricatorPolicyCapability::CAN_VIEW => pht('Visible To'),
PhabricatorPolicyCapability::CAN_EDIT => pht('Editable By'),
PhabricatorPolicyCapability::CAN_JOIN => pht('Joinable By'),
+ PhabricatorPolicyCapability::CAN_PUSH => pht('Writable By'),
);
$this->setLabel(idx($labels, $this->capability, pht('Unknown Policy')));

File Metadata

Mime Type
text/plain
Expires
Sun, Dec 22, 8:10 PM (13 h, 1 m)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6919503
Default Alt Text
D7391.id16654.diff (32 KB)

Event Timeline