1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-17 20:32:41 +01:00

Guarantee repositories have unique local paths

Summary:
Ref T4039. Long ago these were more freely editable and there were some security concerns around creating a repository, then setting its local path to point somewhere it shouldn't.

Local paths are no longer editable so there's no real reason we need to provide a uniqueness guarantee anymore, but you could still make a mistake with `bin/repository move-paths` by accident, and it's a little cleaner to pull them out into their own column with a key.

(We still don't -- and, largely can't -- guarantee that two paths aren't //equivalent// since one might be symlinked to the other, or symlinked only on some hosts, or whatever, but the primary value here is as a sanity check that you aren't goofing things up and pointing a bunch of repositories at the same working copy by mistake.)

Test Plan:
  - Ran migrations.
  - Grepped for `local-path`.
  - Listed and moved paths with `bin/repository`.
  - Created a new repository, verified its local path populated correctly.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T4039

Differential Revision: https://secure.phabricator.com/D15837
This commit is contained in:
epriestley 2016-05-03 12:22:40 -07:00
parent d85386488b
commit dd2b10b8f8
13 changed files with 89 additions and 37 deletions

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_repository.repository
ADD localPath VARCHAR(128) COLLATE {$COLLATE_TEXT};

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_repository.repository
ADD UNIQUE KEY `key_local` (localPath);

View file

@ -0,0 +1,57 @@
<?php
$table = new PhabricatorRepository();
$conn_w = $table->establishConnection('w');
$default_path = PhabricatorEnv::getEnvConfig('repository.default-local-path');
$default_path = rtrim($default_path, '/');
foreach (new LiskMigrationIterator($table) as $repository) {
$local_path = $repository->getLocalPath();
if (strlen($local_path)) {
// Repository already has a modern, unique local path.
continue;
}
$local_path = $repository->getDetail('local-path');
if (!strlen($local_path)) {
// Repository does not have a local path using the older format.
continue;
}
$random = Filesystem::readRandomCharacters(8);
// Try the configured path first, then a default path, then a path with some
// random noise.
$paths = array(
$local_path,
$default_path.'/'.$repository->getID().'/',
$default_path.'/'.$repository->getID().'-'.$random.'/',
);
foreach ($paths as $path) {
// Set, then get the path to normalize it.
$repository->setLocalPath($path);
$path = $repository->getLocalPath();
try {
queryfx(
$conn_w,
'UPDATE %T SET localPath = %s WHERE id = %d',
$table->getTableName(),
$path,
$repository->getID());
echo tsprintf(
"%s\n",
pht(
'Assigned repository "%s" to local path "%s".',
$repository->getDisplayName(),
$path));
break;
} catch (AphrontDuplicateKeyQueryException $ex) {
// Ignore, try the next one.
}
}
}

View file

@ -631,9 +631,7 @@ final class DiffusionRepositoryEditMainController
pht('Storage Service'), pht('Storage Service'),
$v_service); $v_service);
$view->addProperty( $view->addProperty(pht('Storage Path'), $repository->getLocalPath());
pht('Storage Path'),
$repository->getHumanReadableDetail('local-path'));
return $view; return $view;
} }

View file

@ -15,7 +15,7 @@ final class DiffusionRepositoryEditStorageController
$edit_uri = $this->getRepositoryControllerURI($repository, 'edit/'); $edit_uri = $this->getRepositoryControllerURI($repository, 'edit/');
$v_local = $repository->getHumanReadableDetail('local-path'); $v_local = $repository->getLocalPath();
$errors = array(); $errors = array();
$crumbs = $this->buildApplicationCrumbs(); $crumbs = $this->buildApplicationCrumbs();
@ -51,11 +51,7 @@ final class DiffusionRepositoryEditStorageController
->appendRemarkupInstructions( ->appendRemarkupInstructions(
pht( pht(
"You can not adjust the local path for this repository from the ". "You can not adjust the local path for this repository from the ".
"web interface. To edit it, run this command:\n\n %s", 'web interface.'))
sprintf(
'phabricator/ $ ./bin/repository edit %s --as %s --local-path ...',
$repository->getMonogram(),
$viewer->getUsername())))
->appendChild( ->appendChild(
id(new AphrontFormSubmitControl()) id(new AphrontFormSubmitControl())
->addCancelButton($edit_uri, pht('Done'))); ->addCancelButton($edit_uri, pht('Done')));

View file

@ -40,7 +40,7 @@ final class DiffusionRepositoryStorageManagementPanel
->setViewer($viewer); ->setViewer($viewer);
if ($repository->usesLocalWorkingCopy()) { if ($repository->usesLocalWorkingCopy()) {
$storage_path = $repository->getHumanReadableDetail('local-path'); $storage_path = $repository->getLocalPath();
} else { } else {
$storage_path = phutil_tag('em', array(), pht('No Local Working Copy')); $storage_path = phutil_tag('em', array(), pht('No Local Working Copy'));
} }

View file

@ -110,7 +110,6 @@ final class RepositoryCreateConduitAPIMethod
'description' => $request->getValue('description'), 'description' => $request->getValue('description'),
'tracking-enabled' => (bool)$request->getValue('tracking', true), 'tracking-enabled' => (bool)$request->getValue('tracking', true),
'remote-uri' => $remote_uri, 'remote-uri' => $remote_uri,
'local-path' => $local_path,
'branch-filter' => array_fill_keys( 'branch-filter' => array_fill_keys(
$request->getValue('branchFilter', array()), $request->getValue('branchFilter', array()),
true), true),
@ -128,6 +127,8 @@ final class RepositoryCreateConduitAPIMethod
$repository->setDetail($key, $value); $repository->setDetail($key, $value);
} }
$repository->setLocalPath($local_path);
try { try {
$repository->save(); $repository->save();
} catch (AphrontDuplicateKeyQueryException $ex) { } catch (AphrontDuplicateKeyQueryException $ex) {

View file

@ -86,7 +86,7 @@ final class PhabricatorRepositoryEditor
case PhabricatorRepositoryTransaction::TYPE_REMOTE_URI: case PhabricatorRepositoryTransaction::TYPE_REMOTE_URI:
return $object->getDetail('remote-uri'); return $object->getDetail('remote-uri');
case PhabricatorRepositoryTransaction::TYPE_LOCAL_PATH: case PhabricatorRepositoryTransaction::TYPE_LOCAL_PATH:
return $object->getDetail('local-path'); return $object->getLocalPath();
case PhabricatorRepositoryTransaction::TYPE_HOSTING: case PhabricatorRepositoryTransaction::TYPE_HOSTING:
return $object->isHosted(); return $object->isHosted();
case PhabricatorRepositoryTransaction::TYPE_PROTOCOL_HTTP: case PhabricatorRepositoryTransaction::TYPE_PROTOCOL_HTTP:
@ -209,7 +209,7 @@ final class PhabricatorRepositoryEditor
$object->setDetail('remote-uri', $xaction->getNewValue()); $object->setDetail('remote-uri', $xaction->getNewValue());
break; break;
case PhabricatorRepositoryTransaction::TYPE_LOCAL_PATH: case PhabricatorRepositoryTransaction::TYPE_LOCAL_PATH:
$object->setDetail('local-path', $xaction->getNewValue()); $object->setLocalPath($xaction->getNewValue());
break; break;
case PhabricatorRepositoryTransaction::TYPE_HOSTING: case PhabricatorRepositoryTransaction::TYPE_HOSTING:
return $object->setHosted($xaction->getNewValue()); return $object->setHosted($xaction->getNewValue());
@ -706,7 +706,7 @@ final class PhabricatorRepositoryEditor
// If the repository does not have a local path yet, assign it one based // If the repository does not have a local path yet, assign it one based
// on its ID. We can't do this earlier because we won't have an ID yet. // on its ID. We can't do this earlier because we won't have an ID yet.
$local_path = $object->getDetail('local-path'); $local_path = $object->getLocalPath();
if (!strlen($local_path)) { if (!strlen($local_path)) {
$local_key = 'repository.default-local-path'; $local_key = 'repository.default-local-path';
@ -716,7 +716,7 @@ final class PhabricatorRepositoryEditor
$id = $object->getID(); $id = $object->getID();
$local_path = "{$local_root}/{$id}/"; $local_path = "{$local_root}/{$id}/";
$object->setDetail('local-path', $local_path); $object->setLocalPath($local_path);
$object->save(); $object->save();
} }

View file

@ -65,7 +65,7 @@ abstract class PhabricatorWorkingCopyTestCase extends PhabricatorTestCase {
->setCallsign($callsign) ->setCallsign($callsign)
->setName(pht('Test Repo "%s"', $callsign)) ->setName(pht('Test Repo "%s"', $callsign))
->setVersionControlSystem($vcs_type) ->setVersionControlSystem($vcs_type)
->setDetail('local-path', dirname($local).'/'.$callsign) ->setLocalPath(dirname($local).'/'.$callsign)
->setDetail('remote-uri', 'file://'.$dir->getPath().'/'); ->setDetail('remote-uri', 'file://'.$dir->getPath().'/');
$this->didConstructRepository($repo); $this->didConstructRepository($repo);

View file

@ -21,11 +21,6 @@ final class PhabricatorRepositoryManagementEditWorkflow
'param' => 'user', 'param' => 'user',
'help' => pht('Edit as user.'), 'help' => pht('Edit as user.'),
), ),
array(
'name' => 'local-path',
'param' => 'path',
'help' => pht('Edit the local path.'),
),
array( array(
'name' => 'serve-http', 'name' => 'serve-http',
'param' => 'string', 'param' => 'string',
@ -83,7 +78,6 @@ final class PhabricatorRepositoryManagementEditWorkflow
$xactions = array(); $xactions = array();
$type_local_path = PhabricatorRepositoryTransaction::TYPE_LOCAL_PATH;
$type_protocol_http = $type_protocol_http =
PhabricatorRepositoryTransaction::TYPE_PROTOCOL_HTTP; PhabricatorRepositoryTransaction::TYPE_PROTOCOL_HTTP;
$type_protocol_ssh = PhabricatorRepositoryTransaction::TYPE_PROTOCOL_SSH; $type_protocol_ssh = PhabricatorRepositoryTransaction::TYPE_PROTOCOL_SSH;
@ -93,11 +87,6 @@ final class PhabricatorRepositoryManagementEditWorkflow
PhabricatorRepository::SERVE_READWRITE, PhabricatorRepository::SERVE_READWRITE,
); );
if ($args->getArg('local-path')) {
$xactions[] = id(new PhabricatorRepositoryTransaction())
->setTransactionType($type_local_path)
->setNewValue($args->getArg('local-path'));
}
$serve_http = $args->getArg('serve-http'); $serve_http = $args->getArg('serve-http');
if ($serve_http && in_array($serve_http, $allowed_serve_modes)) { if ($serve_http && in_array($serve_http, $allowed_serve_modes)) {
$xactions[] = id(new PhabricatorRepositoryTransaction()) $xactions[] = id(new PhabricatorRepositoryTransaction())

View file

@ -128,14 +128,12 @@ final class PhabricatorRepositoryManagementMovePathsWorkflow
} }
$repo = $row['repository']; $repo = $row['repository'];
$details = $repo->getDetails();
$details['local-path'] = $row['dst'];
queryfx( queryfx(
$repo->establishConnection('w'), $repo->establishConnection('w'),
'UPDATE %T SET details = %s WHERE id = %d', 'UPDATE %T SET localPath = %s WHERE id = %d',
$repo->getTableName(), $repo->getTableName(),
phutil_json_encode($details), $row['dst'],
$repo->getID()); $repo->getID());
} }

View file

@ -62,6 +62,7 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
protected $credentialPHID; protected $credentialPHID;
protected $almanacServicePHID; protected $almanacServicePHID;
protected $spacePHID; protected $spacePHID;
protected $localPath;
private $commitCount = self::ATTACHABLE; private $commitCount = self::ATTACHABLE;
private $mostRecentCommit = self::ATTACHABLE; private $mostRecentCommit = self::ATTACHABLE;
@ -107,6 +108,7 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
'pushPolicy' => 'policy', 'pushPolicy' => 'policy',
'credentialPHID' => 'phid?', 'credentialPHID' => 'phid?',
'almanacServicePHID' => 'phid?', 'almanacServicePHID' => 'phid?',
'localPath' => 'text128?',
), ),
self::CONFIG_KEY_SCHEMA => array( self::CONFIG_KEY_SCHEMA => array(
'callsign' => array( 'callsign' => array(
@ -123,6 +125,10 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
'columns' => array('repositorySlug'), 'columns' => array('repositorySlug'),
'unique' => true, 'unique' => true,
), ),
'key_local' => array(
'columns' => array('localPath'),
'unique' => true,
),
), ),
) + parent::getConfiguration(); ) + parent::getConfiguration();
} }
@ -216,6 +222,13 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
return $monograms; return $monograms;
} }
public function setLocalPath($path) {
// Convert any extra slashes ("//") in the path to a single slash ("/").
$path = preg_replace('(//+)', '/', $path);
return parent::setLocalPath($path);
}
public function getDetail($key, $default = null) { public function getDetail($key, $default = null) {
return idx($this->details, $key, $default); return idx($this->details, $key, $default);
} }
@ -279,10 +292,6 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
)); ));
} }
public function getLocalPath() {
return $this->getDetail('local-path');
}
public function getSubversionBaseURI($commit = null) { public function getSubversionBaseURI($commit = null) {
$subpath = $this->getDetail('svn-subpath'); $subpath = $this->getDetail('svn-subpath');
if (!strlen($subpath)) { if (!strlen($subpath)) {

View file

@ -70,12 +70,12 @@ final class PhabricatorRepositoryTestCase
$repo->setDetail('hosting-enabled', true); $repo->setDetail('hosting-enabled', true);
$repo->setDetail('local-path', '/var/repo/SVN'); $repo->setLocalPath('/var/repo/SVN');
$this->assertEqual( $this->assertEqual(
'file:///var/repo/SVN', 'file:///var/repo/SVN',
$repo->getSubversionPathURI()); $repo->getSubversionPathURI());
$repo->setDetail('local-path', '/var/repo/SVN/'); $repo->setLocalPath('/var/repo/SVN/');
$this->assertEqual( $this->assertEqual(
'file:///var/repo/SVN', 'file:///var/repo/SVN',
$repo->getSubversionPathURI()); $repo->getSubversionPathURI());