Index: src/applications/diffusion/controller/DiffusionController.php =================================================================== --- src/applications/diffusion/controller/DiffusionController.php +++ src/applications/diffusion/controller/DiffusionController.php @@ -35,6 +35,9 @@ } else if ($content_type == 'application/x-git-upload-pack-request') { // We get this for `git-upload-pack`. $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')) { // Mercurial also sends an Accept header like // "application/mercurial-0.1", and a User-Agent like @@ -61,16 +64,37 @@ 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) { + return new PhabricatorVCSResponse( + 403, + 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()) { - return new PhabricatorVCSResponse( - 403, - pht('You must log in to access repositories.')); + 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 @@ 403, pht('You do not have permission to access this repository.')); } else { - return new PhabricatorVCSResponse( - 401, - pht('You must log in to access this repository.')); + if ($allow_auth) { + return new PhabricatorVCSResponse( + 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 @@ 403, pht('You do not have permission to push to this repository.')); } else { - return new PhabricatorVCSResponse( - 401, - pht('You must log in to push to this repository.')); + if ($allow_auth) { + return new PhabricatorVCSResponse( + 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 @@ // Rebuild the query string to strip `__magic__` parameters and prevent // issues where we might interpret inputs like "service=read&service=write" // 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, '', '&'); // We're about to wipe out PATH with the rest of the environment, so @@ -435,18 +484,31 @@ $env = array( 'REQUEST_METHOD' => $_SERVER['REQUEST_METHOD'], 'QUERY_STRING' => $query_string, - 'CONTENT_TYPE' => $_SERVER['CONTENT_TYPE'], - 'REMOTE_USER' => '', + 'CONTENT_TYPE' => $request->getHTTPHeader('Content-Type'), + 'HTTP_CONTENT_ENCODING' => $request->getHTTPHeader('Content-Encoding'), 'REMOTE_ADDR' => $_SERVER['REMOTE_ADDR'], 'GIT_PROJECT_ROOT' => $repository_root, 'GIT_HTTP_EXPORT_ALL' => '1', '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) - ->write(PhabricatorStartup::getRawInput()) - ->resolvex(); + ->write($input) + ->resolve(); + + if ($err) { + return new PhabricatorVCSResponse( + 500, + pht('Error %d: %s', $err, $stderr)); + } return id(new DiffusionGitResponse())->setGitData($stdout); } @@ -463,5 +525,43 @@ ->setTitle($title) ->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; + } }