mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-30 01:10:58 +01:00
Implement writes over HTTP for Git.
Summary: Depends on D7642. This updates the authentication logic so that HTTP writes can be made to Git repositories hosted by Phabricator. Test Plan: Set the policy to allow me to push and I was able to. Changed the policy to disallow push and I was no longer able to push. Reviewers: #blessed_reviewers, hach-que Reviewed By: hach-que CC: Korvin, epriestley, aran Maniphest Tasks: T2230 Differential Revision: https://secure.phabricator.com/D7468
This commit is contained in:
parent
0278b15ceb
commit
43fd567ef4
1 changed files with 119 additions and 19 deletions
|
@ -35,6 +35,9 @@ abstract class DiffusionController extends PhabricatorController {
|
||||||
} else if ($content_type == 'application/x-git-upload-pack-request') {
|
} else if ($content_type == 'application/x-git-upload-pack-request') {
|
||||||
// We get this for `git-upload-pack`.
|
// We get this for `git-upload-pack`.
|
||||||
$vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT;
|
$vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT;
|
||||||
|
} else if ($content_type == 'application/x-git-receive-pack-request') {
|
||||||
|
// We get this for `git-receive-pack`.
|
||||||
|
$vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT;
|
||||||
} else if ($request->getExists('cmd')) {
|
} else if ($request->getExists('cmd')) {
|
||||||
// Mercurial also sends an Accept header like
|
// Mercurial also sends an Accept header like
|
||||||
// "application/mercurial-0.1", and a User-Agent like
|
// "application/mercurial-0.1", and a User-Agent like
|
||||||
|
@ -61,16 +64,37 @@ abstract class DiffusionController extends PhabricatorController {
|
||||||
|
|
||||||
private function processVCSRequest($callsign) {
|
private function processVCSRequest($callsign) {
|
||||||
|
|
||||||
// TODO: Authenticate user.
|
// If authentication credentials have been provided, try to find a user
|
||||||
|
// that actually matches those credentials.
|
||||||
|
if (isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW'])) {
|
||||||
|
$username = $_SERVER['PHP_AUTH_USER'];
|
||||||
|
$password = new PhutilOpaqueEnvelope($_SERVER['PHP_AUTH_PW']);
|
||||||
|
|
||||||
$viewer = new PhabricatorUser();
|
$viewer = $this->authenticateHTTPRepositoryUser($username, $password);
|
||||||
|
if (!$viewer) {
|
||||||
$allow_public = PhabricatorEnv::getEnvConfig('policy.allow-public');
|
|
||||||
if (!$allow_public) {
|
|
||||||
if (!$viewer->isLoggedIn()) {
|
|
||||||
return new PhabricatorVCSResponse(
|
return new PhabricatorVCSResponse(
|
||||||
403,
|
403,
|
||||||
pht('You must log in to access repositories.'));
|
pht('Invalid credentials.'));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// User hasn't provided credentials, which means we count them as
|
||||||
|
// being "not logged in".
|
||||||
|
$viewer = new PhabricatorUser();
|
||||||
|
}
|
||||||
|
|
||||||
|
$allow_public = PhabricatorEnv::getEnvConfig('policy.allow-public');
|
||||||
|
$allow_auth = PhabricatorEnv::getEnvConfig('diffusion.allow-http-auth');
|
||||||
|
if (!$allow_public) {
|
||||||
|
if (!$viewer->isLoggedIn()) {
|
||||||
|
if ($allow_auth) {
|
||||||
|
return new PhabricatorVCSResponse(
|
||||||
|
401,
|
||||||
|
pht('You must log in to access repositories.'));
|
||||||
|
} else {
|
||||||
|
return new PhabricatorVCSResponse(
|
||||||
|
403,
|
||||||
|
pht('Public and authenticated HTTP access are both forbidden.'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,9 +114,17 @@ abstract class DiffusionController extends PhabricatorController {
|
||||||
403,
|
403,
|
||||||
pht('You do not have permission to access this repository.'));
|
pht('You do not have permission to access this repository.'));
|
||||||
} else {
|
} else {
|
||||||
return new PhabricatorVCSResponse(
|
if ($allow_auth) {
|
||||||
401,
|
return new PhabricatorVCSResponse(
|
||||||
pht('You must log in to access this repository.'));
|
401,
|
||||||
|
pht('You must log in to access this repository.'));
|
||||||
|
} else {
|
||||||
|
return new PhabricatorVCSResponse(
|
||||||
|
403,
|
||||||
|
pht(
|
||||||
|
'This repository requires authentication, which is forbidden '.
|
||||||
|
'over HTTP.'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,9 +156,17 @@ abstract class DiffusionController extends PhabricatorController {
|
||||||
403,
|
403,
|
||||||
pht('You do not have permission to push to this repository.'));
|
pht('You do not have permission to push to this repository.'));
|
||||||
} else {
|
} else {
|
||||||
return new PhabricatorVCSResponse(
|
if ($allow_auth) {
|
||||||
401,
|
return new PhabricatorVCSResponse(
|
||||||
pht('You must log in to push to this repository.'));
|
401,
|
||||||
|
pht('You must log in to push to this repository.'));
|
||||||
|
} else {
|
||||||
|
return new PhabricatorVCSResponse(
|
||||||
|
403,
|
||||||
|
pht(
|
||||||
|
'Pushing to this repository requires authentication, '.
|
||||||
|
'which is forbidden over HTTP.'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -422,7 +462,16 @@ abstract class DiffusionController extends PhabricatorController {
|
||||||
// Rebuild the query string to strip `__magic__` parameters and prevent
|
// Rebuild the query string to strip `__magic__` parameters and prevent
|
||||||
// issues where we might interpret inputs like "service=read&service=write"
|
// issues where we might interpret inputs like "service=read&service=write"
|
||||||
// differently than the server does and pass it an unsafe command.
|
// differently than the server does and pass it an unsafe command.
|
||||||
$query_data = $request->getPassthroughRequestParameters();
|
|
||||||
|
// NOTE: This does not use getPassthroughRequestParameters() because
|
||||||
|
// that code is HTTP-method agnostic and will encode POST data.
|
||||||
|
|
||||||
|
$query_data = $_GET;
|
||||||
|
foreach ($query_data as $key => $value) {
|
||||||
|
if (!strncmp($key, '__', 2)) {
|
||||||
|
unset($query_data[$key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
$query_string = http_build_query($query_data, '', '&');
|
$query_string = http_build_query($query_data, '', '&');
|
||||||
|
|
||||||
// We're about to wipe out PATH with the rest of the environment, so
|
// We're about to wipe out PATH with the rest of the environment, so
|
||||||
|
@ -435,18 +484,31 @@ abstract class DiffusionController extends PhabricatorController {
|
||||||
$env = array(
|
$env = array(
|
||||||
'REQUEST_METHOD' => $_SERVER['REQUEST_METHOD'],
|
'REQUEST_METHOD' => $_SERVER['REQUEST_METHOD'],
|
||||||
'QUERY_STRING' => $query_string,
|
'QUERY_STRING' => $query_string,
|
||||||
'CONTENT_TYPE' => $_SERVER['CONTENT_TYPE'],
|
'CONTENT_TYPE' => $request->getHTTPHeader('Content-Type'),
|
||||||
'REMOTE_USER' => '',
|
'HTTP_CONTENT_ENCODING' => $request->getHTTPHeader('Content-Encoding'),
|
||||||
'REMOTE_ADDR' => $_SERVER['REMOTE_ADDR'],
|
'REMOTE_ADDR' => $_SERVER['REMOTE_ADDR'],
|
||||||
'GIT_PROJECT_ROOT' => $repository_root,
|
'GIT_PROJECT_ROOT' => $repository_root,
|
||||||
'GIT_HTTP_EXPORT_ALL' => '1',
|
'GIT_HTTP_EXPORT_ALL' => '1',
|
||||||
'PATH_INFO' => $request_path,
|
'PATH_INFO' => $request_path,
|
||||||
|
|
||||||
|
// TODO: Set these correctly.
|
||||||
|
'REMOTE_USER' => '',
|
||||||
|
// GIT_COMMITTER_NAME
|
||||||
|
// GIT_COMMITTER_EMAIL
|
||||||
);
|
);
|
||||||
|
|
||||||
list($stdout) = id(new ExecFuture('%s', $bin))
|
$input = PhabricatorStartup::getRawInput();
|
||||||
|
|
||||||
|
list($err, $stdout, $stderr) = id(new ExecFuture('%s', $bin))
|
||||||
->setEnv($env, true)
|
->setEnv($env, true)
|
||||||
->write(PhabricatorStartup::getRawInput())
|
->write($input)
|
||||||
->resolvex();
|
->resolve();
|
||||||
|
|
||||||
|
if ($err) {
|
||||||
|
return new PhabricatorVCSResponse(
|
||||||
|
500,
|
||||||
|
pht('Error %d: %s', $err, $stderr));
|
||||||
|
}
|
||||||
|
|
||||||
return id(new DiffusionGitResponse())->setGitData($stdout);
|
return id(new DiffusionGitResponse())->setGitData($stdout);
|
||||||
}
|
}
|
||||||
|
@ -463,5 +525,43 @@ abstract class DiffusionController extends PhabricatorController {
|
||||||
->setTitle($title)
|
->setTitle($title)
|
||||||
->appendChild($body);
|
->appendChild($body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function authenticateHTTPRepositoryUser(
|
||||||
|
$username,
|
||||||
|
PhutilOpaqueEnvelope $password) {
|
||||||
|
|
||||||
|
if (!PhabricatorEnv::getEnvConfig('diffusion.allow-http-auth')) {
|
||||||
|
// No HTTP auth permitted.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = id(new PhabricatorPeopleQuery())
|
||||||
|
->setViewer(new PhabricatorUser())
|
||||||
|
->withUsernames(array($username))
|
||||||
|
->executeOne();
|
||||||
|
if (!$user) {
|
||||||
|
// Username doesn't match anything.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$password_entry = id(new PhabricatorRepositoryVCSPassword())
|
||||||
|
->loadOneWhere('userPHID = %s', $user->getPHID());
|
||||||
|
if (!$password_entry) {
|
||||||
|
// User doesn't have a password set.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$password_entry->comparePassword($password, $user)) {
|
||||||
|
// Password doesn't match.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($user->getIsDisabled()) {
|
||||||
|
// User is disabled.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $user;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue