Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F14391683
D7391.id16654.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
32 KB
Referenced Files
None
Subscribers
None
D7391.id16654.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D7391: Implemented hosted Git repositories in Phabricator over Smart HTTP.
Attached
Detach File
Event Timeline
Log In to Comment