diff --git a/resources/sql/patches/20131121.repocredentials.1.col.sql b/resources/sql/patches/20131121.repocredentials.1.col.sql new file mode 100644 index 0000000000..9471c7189b --- /dev/null +++ b/resources/sql/patches/20131121.repocredentials.1.col.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_repository.repository + ADD credentialPHID VARCHAR(64) COLLATE utf8_bin; diff --git a/resources/sql/patches/20131121.repocredentials.2.mig.php b/resources/sql/patches/20131121.repocredentials.2.mig.php new file mode 100644 index 0000000000..d9735d6b5b --- /dev/null +++ b/resources/sql/patches/20131121.repocredentials.2.mig.php @@ -0,0 +1,104 @@ +establishConnection('w'); +$viewer = PhabricatorUser::getOmnipotentUser(); + +$map = array(); +foreach (new LiskMigrationIterator($table) as $repository) { + $callsign = $repository->getCallsign(); + echo "Examining repository {$callsign}...\n"; + + if ($repository->getCredentialPHID()) { + echo "...already has a Credential.\n"; + continue; + } + + $uri = $repository->getRemoteURI(); + if (!$uri) { + echo "...no remote URI.\n"; + continue; + } + + $uri = new PhutilURI($uri); + + $proto = strtolower($uri->getProtocol()); + if ($proto == 'http' || $proto == 'https' || $proto == 'svn') { + $username = $repository->getDetail('http-login'); + $secret = $repository->getDetail('http-pass'); + $type = PassphraseCredentialTypePassword::CREDENTIAL_TYPE; + } else { + $username = $repository->getDetail('ssh-login'); + $file = $repository->getDetail('ssh-keyfile'); + if ($file) { + $secret = $file; + $type = PassphraseCredentialTypeSSHPrivateKeyFile::CREDENTIAL_TYPE; + } else { + $secret = $repository->getDetail('ssh-key'); + $type = PassphraseCredentialTypeSSHPrivateKeyText::CREDENTIAL_TYPE; + } + } + + if (!$username || !$secret) { + echo "...no credentials set.\n"; + continue; + } + + $map[$type][$username][$secret][] = $repository; + echo "...will migrate.\n"; +} + +$passphrase = new PassphraseSecret(); +$passphrase->openTransaction(); +$table->openTransaction(); + +foreach ($map as $credential_type => $credential_usernames) { + $type = PassphraseCredentialType::getTypeByConstant($credential_type); + foreach ($credential_usernames as $username => $credential_secrets) { + foreach ($credential_secrets as $secret_plaintext => $repositories) { + $callsigns = mpull($repositories, 'getCallsign'); + $name = pht( + 'Migrated Repository Credential (%s)', + implode(', ', $callsigns)); + + echo "Creating: {$name}...\n"; + + $secret = id(new PassphraseSecret()) + ->setSecretData($secret_plaintext) + ->save(); + + $secret_id = $secret->getID(); + + $credential = PassphraseCredential::initializeNewCredential($viewer) + ->setCredentialType($type->getCredentialType()) + ->setProvidesType($type->getProvidesType()) + ->setViewPolicy(PhabricatorPolicies::POLICY_ADMIN) + ->setEditPolicy(PhabricatorPolicies::POLICY_ADMIN) + ->setName($name) + ->setUsername($username) + ->setSecretID($secret_id) + ->save(); + + foreach ($repositories as $repository) { + queryfx( + $conn_w, + 'UPDATE %T SET credentialPHID = %s WHERE id = %d', + $table->getTableName(), + $credential->getPHID(), + $repository->getID()); + + $edge_type = PhabricatorEdgeConfig::TYPE_OBJECT_USES_CREDENTIAL; + + id(new PhabricatorEdgeEditor()) + ->setActor($viewer) + ->addEdge($repository->getPHID(), $edge_type, $credential->getPHID()) + ->save(); + } + } + } +} + +$table->saveTransaction(); +$passphrase->saveTransaction(); + +echo "Done.\n"; diff --git a/scripts/ssh/ssh-connect.php b/scripts/ssh/ssh-connect.php index 2f28778565..03e1caa77a 100755 --- a/scripts/ssh/ssh-connect.php +++ b/scripts/ssh/ssh-connect.php @@ -12,8 +12,10 @@ if (!$target_name) { throw new Exception(pht("No 'PHABRICATOR_SSH_TARGET' in environment!")); } +$viewer = PhabricatorUser::getOmnipotentUser(); + $repository = id(new PhabricatorRepositoryQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->setViewer($viewer) ->withCallsigns(array($target_name)) ->executeOne(); if (!$repository) { @@ -28,30 +30,14 @@ $pattern[] = 'ssh'; $pattern[] = '-o'; $pattern[] = 'StrictHostKeyChecking=no'; -$login = $repository->getSSHLogin(); -if (strlen($login)) { - $pattern[] = '-l'; - $pattern[] = '%P'; - $arguments[] = new PhutilOpaqueEnvelope($login); -} +$credential_phid = $repository->getCredentialPHID(); +if ($credential_phid) { + $key = PassphraseSSHKey::loadFromPHID($credential_phid, $viewer); -$ssh_identity = null; - -$key = $repository->getDetail('ssh-key'); -$keyfile = $repository->getDetail('ssh-keyfile'); -if ($keyfile) { - $ssh_identity = $keyfile; -} else if ($key) { - $tmpfile = new TempFile('phabricator-repository-ssh-key'); - chmod($tmpfile, 0600); - Filesystem::writeFile($tmpfile, $key); - $ssh_identity = (string)$tmpfile; -} - -if ($ssh_identity) { - $pattern[] = '-i'; - $pattern[] = '%P'; - $arguments[] = new PhutilOpaqueEnvelope($keyfile); + $pattern[] = '-l %P'; + $arguments[] = $key->getUsernameEnvelope(); + $pattern[] = '-i %P'; + $arguments[] = $key->getKeyfileEnvelope(); } $pattern[] = '--'; diff --git a/src/applications/diffusion/controller/DiffusionRepositoryCreateController.php b/src/applications/diffusion/controller/DiffusionRepositoryCreateController.php index 36ad1feb83..6bb3b0f70c 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryCreateController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryCreateController.php @@ -94,12 +94,8 @@ final class DiffusionRepositoryCreateController $type_activate = PhabricatorRepositoryTransaction::TYPE_ACTIVATE; $type_local_path = PhabricatorRepositoryTransaction::TYPE_LOCAL_PATH; $type_remote_uri = PhabricatorRepositoryTransaction::TYPE_REMOTE_URI; - $type_ssh_login = PhabricatorRepositoryTransaction::TYPE_SSH_LOGIN; - $type_ssh_key = PhabricatorRepositoryTransaction::TYPE_SSH_KEY; - $type_ssh_keyfile = PhabricatorRepositoryTransaction::TYPE_SSH_KEYFILE; - $type_http_login = PhabricatorRepositoryTransaction::TYPE_HTTP_LOGIN; - $type_http_pass = PhabricatorRepositoryTransaction::TYPE_HTTP_PASS; $type_hosting = PhabricatorRepositoryTransaction::TYPE_HOSTING; + $type_credential = PhabricatorRepositoryTransaction::TYPE_CREDENTIAL; $xactions = array(); @@ -159,29 +155,9 @@ final class DiffusionRepositoryCreateController ->getValue()); $xactions[] = id(clone $template) - ->setTransactionType($type_ssh_login) + ->setTransactionType($type_credential) ->setNewValue( - $form->getPage('auth')->getControl('ssh-login')->getValue()); - - $xactions[] = id(clone $template) - ->setTransactionType($type_ssh_key) - ->setNewValue( - $form->getPage('auth')->getControl('ssh-key')->getValue()); - - $xactions[] = id(clone $template) - ->setTransactionType($type_ssh_keyfile) - ->setNewValue( - $form->getPage('auth')->getControl('ssh-keyfile')->getValue()); - - $xactions[] = id(clone $template) - ->setTransactionType($type_http_login) - ->setNewValue( - $form->getPage('auth')->getControl('http-login')->getValue()); - - $xactions[] = id(clone $template) - ->setTransactionType($type_http_pass) - ->setNewValue( - $form->getPage('auth')->getControl('http-pass')->getValue()); + $form->getPage('auth')->getControl('credential')->getValue()); } id(new PhabricatorRepositoryEditor()) @@ -198,11 +174,7 @@ final class DiffusionRepositoryCreateController if ($repository) { $dict = array( 'remoteURI' => $repository->getRemoteURI(), - 'ssh-login' => $repository->getDetail('ssh-login'), - 'ssh-key' => $repository->getDetail('ssh-key'), - 'ssh-keyfile' => $repository->getDetail('ssh-keyfile'), - 'http-login' => $repository->getDetail('http-login'), - 'http-pass' => $repository->getDetail('http-pass'), + 'credential' => $repository->getCredentialPHID(), ); } $form->readFromObject($dict); @@ -550,105 +522,86 @@ final class DiffusionRepositoryCreateController ->setUser($this->getRequest()->getUser()) ->setAdjustFormPageCallback(array($this, 'adjustAuthPage')) ->addControl( - id(new AphrontFormTextControl()) - ->setName('ssh-login') - ->setLabel('SSH User')) - ->addControl( - id(new AphrontFormTextAreaControl()) - ->setName('ssh-key') - ->setLabel('SSH Private Key') - ->setHeight(AphrontFormTextAreaControl::HEIGHT_SHORT) - ->setCaption( - hsprintf('Specify the entire private key, or...'))) - ->addControl( - id(new AphrontFormTextControl()) - ->setName('ssh-keyfile') - ->setLabel('SSH Private Key Path') - ->setCaption( - '...specify a path on disk where the daemon should '. - 'look for a private key.')) - ->addControl( - id(new AphrontFormTextControl()) - ->setName('http-login') - ->setLabel('Username')) - ->addControl( - id(new AphrontFormPasswordControl()) - ->setName('http-pass') - ->setLabel('Password')); + id(new PassphraseCredentialControl()) + ->setName('credential')); } public function adjustAuthPage($page) { $form = $page->getForm(); - $remote_uri = $form->getPage('remote-uri') - ->getControl('remoteURI') - ->getValue(); - if ($this->getRepository()) { $vcs = $this->getRepository()->getVersionControlSystem(); } else { $vcs = $form->getPage('vcs')->getControl('vcs')->getValue(); } + $remote_uri = $form->getPage('remote-uri') + ->getControl('remoteURI') + ->getValue(); $proto = $this->getRemoteURIProtocol($remote_uri); $remote_user = $this->getRemoteURIUser($remote_uri); - $page->getControl('ssh-login')->setHidden(true); - $page->getControl('ssh-key')->setHidden(true); - $page->getControl('ssh-keyfile')->setHidden(true); - $page->getControl('http-login')->setHidden(true); - $page->getControl('http-pass')->setHidden(true); + $c_credential = $page->getControl('credential'); + $c_credential->setDefaultUsername($remote_user); if ($this->isSSHProtocol($proto)) { - $page->getControl('ssh-login')->setHidden(false); - $page->getControl('ssh-key')->setHidden(false); - $page->getControl('ssh-keyfile')->setHidden(false); - - $c_login = $page->getControl('ssh-login'); - if (!strlen($c_login->getValue())) { - $c_login->setValue($remote_user); - } + $c_credential->setLabel(pht('SSH Key')); + $c_credential->setCredentialType( + PassphraseCredentialTypeSSHPrivateKeyText::CREDENTIAL_TYPE); + $provides_type = PassphraseCredentialTypeSSHPrivateKey::PROVIDES_TYPE; $page->addRemarkupInstructions( pht( - 'Enter the username and private key to use to connect to the '. - 'the repository hosted at:'. + 'Choose or add the SSH credentials to use to connect to the the '. + 'repository hosted at:'. "\n\n". " lang=text\n". - " %s". - "\n\n". - 'You can either copy/paste the entire private key, or put it '. - 'somewhere on disk and provide the path to it.', + " %s", $remote_uri), - 'ssh-login'); - + 'credential'); } else if ($this->isUsernamePasswordProtocol($proto)) { - $page->getControl('http-login')->setHidden(false); - $page->getControl('http-pass')->setHidden(false); + $c_credential->setLabel(pht('Password')); + $c_credential->setAllowNull(true); + $c_credential->setCredentialType( + PassphraseCredentialTypePassword::CREDENTIAL_TYPE); + $provides_type = PassphraseCredentialTypePassword::PROVIDES_TYPE; $page->addRemarkupInstructions( pht( - 'Enter the a username and pasword used to connect to the '. + 'Choose the a username and pasword used to connect to the '. 'repository hosted at:'. "\n\n". " lang=text\n". " %s". "\n\n". "If this repository does not require a username or password, ". - "you can leave these fields blank.", + "you can continue to the next step.", $remote_uri), - 'http-login'); + 'credential'); } else if ($proto == 'file') { + $c_credential->setHidden(true); $page->addRemarkupInstructions( pht( - 'You do not need to configure any authentication options for '. - 'repositories accessed over the `file://` protocol. Continue '. - 'to the next step.'), - 'ssh-login'); + 'You do not need to configure any credentials for repositories '. + 'accessed over the `file://` protocol. Continue to the next step.'), + 'credential'); } else { throw new Exception("Unknown URI protocol!"); } + + if ($provides_type) { + $viewer = $this->getRequest()->getUser(); + + $options = id(new PassphraseCredentialQuery()) + ->setViewer($viewer) + ->withIsDestroyed(false) + ->withProvidesTypes(array($provides_type)) + ->execute(); + + $c_credential->setOptions($options); + } + } public function validateAuthPage(PHUIFormPageView $page) { @@ -656,49 +609,46 @@ final class DiffusionRepositoryCreateController $remote_uri = $form->getPage('remote')->getControl('remoteURI')->getValue(); $proto = $this->getRemoteURIProtocol($remote_uri); + $c_credential = $page->getControl('credential'); + $v_credential = $c_credential->getValue(); + + // NOTE: We're using the omnipotent user here because the viewer might be + // editing a repository they're allowed to edit which uses a credential they + // are not allowed to see. This is fine, as long as they don't change it. + $credential = id(new PassphraseCredentialQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withPHIDs(array($v_credential)) + ->executeOne(); + if ($this->isSSHProtocol($proto)) { - $c_user = $page->getControl('ssh-login'); - $c_key = $page->getControl('ssh-key'); - $c_file = $page->getControl('ssh-keyfile'); - - $v_user = $c_user->getValue(); - $v_key = $c_key->getValue(); - $v_file = $c_file->getValue(); - - if (!strlen($v_user)) { - $c_user->setError(pht('Required')); + if (!$credential) { + $c_credential->setError(pht('Required')); $page->addPageError( - pht('You must provide an SSH login username to connect over SSH.')); + pht('You must choose an SSH credential to connect over SSH.')); } - if (!strlen($v_key) && !strlen($v_file)) { - $c_key->setError(pht('No Key')); - $c_file->setError(pht('No Key')); + $ssh_type = PassphraseCredentialTypeSSHPrivateKey::PROVIDES_TYPE; + if ($credential->getProvidesType() !== $ssh_type) { + $c_credential->setError(pht('Invalid')); $page->addPageError( pht( - 'You must provide either a private key or the path to a private '. - 'key to connect over SSH.')); - } else if (strlen($v_key) && strlen($v_file)) { - $c_key->setError(pht('Ambiguous')); - $c_file->setError(pht('Ambiguous')); - $page->addPageError( - pht( - 'Provide either a private key or the path to a private key, not '. - 'both.')); - } else if (!preg_match('/PRIVATE KEY/', $v_key)) { - $c_key->setError(pht('Invalid')); - $page->addPageError( - pht( - 'The private key you provided is missing the PRIVATE KEY header. '. - 'You should include the header and footer. (Did you provide a '. - 'public key by mistake?)')); + 'You must choose an SSH credential, not some other type '. + 'of credential.')); } - return $c_user->isValid() && - $c_key->isValid() && - $c_file->isValid(); } else if ($this->isUsernamePasswordProtocol($proto)) { - return true; + if ($credential) { + $password_type = PassphraseCredentialTypePassword::PROVIDES_TYPE; + if ($credential->getProvidesType() !== $password_type) { + $c_credential->setError(pht('Invalid')); + $page->addPageError( + pht( + 'You must choose a username/password credential, not some other '. + 'type of credential.')); + } + } + + return $c_credential->isValid(); } else { return true; } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php index 5ba4423481..999ae7845f 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php @@ -481,6 +481,14 @@ final class DiffusionRepositoryEditMainController pht('Remote URI'), $repository->getHumanReadableDetail('remote-uri')); + $credential_phid = $repository->getCredentialPHID(); + if ($credential_phid) { + $this->loadHandles(array($credential_phid)); + $view->addProperty( + pht('Credential'), + $this->getHandle($credential_phid)->renderLink()); + } + return $view; } diff --git a/src/applications/passphrase/application/PhabricatorApplicationPassphrase.php b/src/applications/passphrase/application/PhabricatorApplicationPassphrase.php index 493372262b..f2951353ed 100644 --- a/src/applications/passphrase/application/PhabricatorApplicationPassphrase.php +++ b/src/applications/passphrase/application/PhabricatorApplicationPassphrase.php @@ -26,8 +26,8 @@ final class PhabricatorApplicationPassphrase extends PhabricatorApplication { return self::GROUP_UTILITIES; } - public function isBeta() { - return true; + public function canUninstall() { + return false; } public function getRoutes() { diff --git a/src/applications/passphrase/keys/PassphraseAbstractKey.php b/src/applications/passphrase/keys/PassphraseAbstractKey.php index 76cba1b39e..cf532d3fac 100644 --- a/src/applications/passphrase/keys/PassphraseAbstractKey.php +++ b/src/applications/passphrase/keys/PassphraseAbstractKey.php @@ -32,16 +32,24 @@ abstract class PassphraseAbstractKey extends Phobject { PassphraseCredential $credential, $provides_type) { - $type = $credential->getCredentialType(); - if ($type->getProvides() !== $provides_type) { + $type = $credential->getCredentialTypeImplementation(); + + if (!$type) { + throw new Exception( + pht( + 'Credential "%s" is of unknown type "%s"!', + 'K'.$credential->getID(), + $credential->getCredentialType())); + } + + if ($type->getProvidesType() !== $provides_type) { throw new Exception( pht( 'Credential "%s" must provide "%s", but provides "%s"!', 'K'.$credential->getID(), $provides_type, - $type->getProvides())); + $type->getProvidesType())); } - } protected function loadAndValidateFromPHID( diff --git a/src/applications/passphrase/storage/PassphraseCredential.php b/src/applications/passphrase/storage/PassphraseCredential.php index 880bf5f170..75c68766c0 100644 --- a/src/applications/passphrase/storage/PassphraseCredential.php +++ b/src/applications/passphrase/storage/PassphraseCredential.php @@ -45,6 +45,11 @@ final class PassphraseCredential extends PassphraseDAO return $this->assertAttached($this->secret); } + public function getCredentialTypeImplementation() { + $type = $this->getCredentialType(); + return PassphraseCredentialType::getTypeByConstant($type); + } + /* -( PhabricatorPolicyInterface )----------------------------------------- */ diff --git a/src/applications/repository/conduit/ConduitAPI_repository_create_Method.php b/src/applications/repository/conduit/ConduitAPI_repository_create_Method.php index cb6237bbd1..630e7c0d53 100644 --- a/src/applications/repository/conduit/ConduitAPI_repository_create_Method.php +++ b/src/applications/repository/conduit/ConduitAPI_repository_create_Method.php @@ -27,11 +27,7 @@ final class ConduitAPI_repository_create_Method 'encoding' => 'optional string', 'tracking' => 'optional bool', 'uri' => 'optional string', - 'sshUser' => 'optional string', - 'sshKey' => 'optional string', - 'sshKeyFile' => 'optional string', - 'httpUser' => 'optional string', - 'httpPassword' => 'optional string', + 'credentialPHID' => 'optional string', 'localPath' => 'optional string', 'svnSubpath' => 'optional string', 'branchFilter' => 'optional list', @@ -100,6 +96,8 @@ final class ConduitAPI_repository_create_Method } $repository->setVersionControlSystem($map[$vcs]); + $repository->setCredentialPHID($request->getValue('credentialPHID')); + $details = array( 'encoding' => $request->getValue('encoding'), 'description' => $request->getValue('description'), @@ -114,9 +112,6 @@ final class ConduitAPI_repository_create_Method true), 'pull-frequency' => $request->getValue('pullFrequency'), 'default-branch' => $request->getValue('defaultBranch'), - 'ssh-login' => $request->getValue('sshUser'), - 'ssh-key' => $request->getValue('sshKey'), - 'ssh-keyfile' => $request->getValue('sshKeyFile'), 'herald-disabled' => !$request->getValue('heraldEnabled', true), 'svn-subpath' => $request->getValue('svnSubpath'), 'disable-autoclose' => !$request->getValue('autocloseEnabled', true), diff --git a/src/applications/repository/editor/PhabricatorRepositoryEditor.php b/src/applications/repository/editor/PhabricatorRepositoryEditor.php index cf28febdf0..e534402391 100644 --- a/src/applications/repository/editor/PhabricatorRepositoryEditor.php +++ b/src/applications/repository/editor/PhabricatorRepositoryEditor.php @@ -29,6 +29,7 @@ final class PhabricatorRepositoryEditor $types[] = PhabricatorRepositoryTransaction::TYPE_PROTOCOL_HTTP; $types[] = PhabricatorRepositoryTransaction::TYPE_PROTOCOL_SSH; $types[] = PhabricatorRepositoryTransaction::TYPE_PUSH_POLICY; + $types[] = PhabricatorRepositoryTransaction::TYPE_CREDENTIAL; $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; @@ -67,16 +68,6 @@ final class PhabricatorRepositoryEditor return (int)!$object->getDetail('disable-autoclose'); case PhabricatorRepositoryTransaction::TYPE_REMOTE_URI: return $object->getDetail('remote-uri'); - case PhabricatorRepositoryTransaction::TYPE_SSH_LOGIN: - return $object->getDetail('ssh-login'); - case PhabricatorRepositoryTransaction::TYPE_SSH_KEY: - return $object->getDetail('ssh-key'); - case PhabricatorRepositoryTransaction::TYPE_SSH_KEYFILE: - return $object->getDetail('ssh-keyfile'); - case PhabricatorRepositoryTransaction::TYPE_HTTP_LOGIN: - return $object->getDetail('http-login'); - case PhabricatorRepositoryTransaction::TYPE_HTTP_PASS: - return $object->getDetail('http-pass'); case PhabricatorRepositoryTransaction::TYPE_LOCAL_PATH: return $object->getDetail('local-path'); case PhabricatorRepositoryTransaction::TYPE_HOSTING: @@ -87,6 +78,8 @@ final class PhabricatorRepositoryEditor return $object->getServeOverSSH(); case PhabricatorRepositoryTransaction::TYPE_PUSH_POLICY: return $object->getPushPolicy(); + case PhabricatorRepositoryTransaction::TYPE_CREDENTIAL: + return $object->getCredentialPHID(); } } @@ -116,6 +109,7 @@ final class PhabricatorRepositoryEditor case PhabricatorRepositoryTransaction::TYPE_PROTOCOL_HTTP: case PhabricatorRepositoryTransaction::TYPE_PROTOCOL_SSH: case PhabricatorRepositoryTransaction::TYPE_PUSH_POLICY: + case PhabricatorRepositoryTransaction::TYPE_CREDENTIAL: return $xaction->getNewValue(); case PhabricatorRepositoryTransaction::TYPE_NOTIFY: case PhabricatorRepositoryTransaction::TYPE_AUTOCLOSE: @@ -168,21 +162,6 @@ final class PhabricatorRepositoryEditor case PhabricatorRepositoryTransaction::TYPE_REMOTE_URI: $object->setDetail('remote-uri', $xaction->getNewValue()); break; - case PhabricatorRepositoryTransaction::TYPE_SSH_LOGIN: - $object->setDetail('ssh-login', $xaction->getNewValue()); - break; - case PhabricatorRepositoryTransaction::TYPE_SSH_KEY: - $object->setDetail('ssh-key', $xaction->getNewValue()); - break; - case PhabricatorRepositoryTransaction::TYPE_SSH_KEYFILE: - $object->setDetail('ssh-keyfile', $xaction->getNewValue()); - break; - case PhabricatorRepositoryTransaction::TYPE_HTTP_LOGIN: - $object->setDetail('http-login', $xaction->getNewValue()); - break; - case PhabricatorRepositoryTransaction::TYPE_HTTP_PASS: - $object->setDetail('http-pass', $xaction->getNewValue()); - break; case PhabricatorRepositoryTransaction::TYPE_LOCAL_PATH: $object->setDetail('local-path', $xaction->getNewValue()); break; @@ -194,6 +173,8 @@ final class PhabricatorRepositoryEditor return $object->setServeOverSSH($xaction->getNewValue()); case PhabricatorRepositoryTransaction::TYPE_PUSH_POLICY: return $object->setPushPolicy($xaction->getNewValue()); + case PhabricatorRepositoryTransaction::TYPE_CREDENTIAL: + return $object->setCredentialPHID($xaction->getNewValue()); case PhabricatorRepositoryTransaction::TYPE_ENCODING: // Make sure the encoding is valid by converting to UTF-8. This tests // that the user has mbstring installed, and also that they didn't type @@ -221,7 +202,32 @@ final class PhabricatorRepositoryEditor protected function applyCustomExternalTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { - return; + + switch ($xaction->getTransactionType()) { + case PhabricatorRepositoryTransaction::TYPE_CREDENTIAL: + // Adjust the object <-> credential edge for this repository. + + $old_phid = $xaction->getOldValue(); + $new_phid = $xaction->getNewValue(); + + $editor = id(new PhabricatorEdgeEditor()) + ->setActor($this->requireActor()); + + $edge_type = PhabricatorEdgeConfig::TYPE_OBJECT_USES_CREDENTIAL; + $src_phid = $object->getPHID(); + + if ($old_phid) { + $editor->removeEdge($src_phid, $edge_type, $old_phid); + } + + if ($new_phid) { + $editor->addEdge($src_phid, $edge_type, $new_phid); + } + + $editor->save(); + break; + } + } protected function mergeTransactions( @@ -278,6 +284,7 @@ final class PhabricatorRepositoryEditor case PhabricatorRepositoryTransaction::TYPE_PROTOCOL_HTTP: case PhabricatorRepositoryTransaction::TYPE_PROTOCOL_SSH: case PhabricatorRepositoryTransaction::TYPE_PUSH_POLICY: + case PhabricatorRepositoryTransaction::TYPE_CREDENTIAL: PhabricatorPolicyFilter::requireCapability( $this->requireActor(), $object, diff --git a/src/applications/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php index c77a6a62c7..af00bb58e8 100644 --- a/src/applications/repository/storage/PhabricatorRepository.php +++ b/src/applications/repository/storage/PhabricatorRepository.php @@ -39,8 +39,7 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO protected $versionControlSystem; protected $details = array(); - - private $sshKeyfile; + protected $credentialPHID; private $commitCount = self::ATTACHABLE; private $mostRecentCommit = self::ATTACHABLE; @@ -366,31 +365,29 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO switch ($this->getVersionControlSystem()) { case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: - if ($this->shouldUseHTTP()) { - $pattern = - "svn ". - "--non-interactive ". - "--no-auth-cache ". - "--trust-server-cert ". - "--username %P ". - "--password %P ". - $pattern; - array_unshift( - $args, - new PhutilOpaqueEnvelope($this->getDetail('http-login')), - new PhutilOpaqueEnvelope($this->getDetail('http-pass'))); - } else if ($this->shouldUseSVNProtocol()) { - $pattern = - "svn ". - "--non-interactive ". - "--no-auth-cache ". - "--username %P ". - "--password %P ". - $pattern; - array_unshift( - $args, - new PhutilOpaqueEnvelope($this->getDetail('http-login')), - new PhutilOpaqueEnvelope($this->getDetail('http-pass'))); + if ($this->shouldUseHTTP() || $this->shouldUseSVNProtocol()) { + $flags = array(); + $flag_args = array(); + $flags[] = '--non-interactive'; + $flags[] = '--no-auth-cache'; + if ($this->shouldUseHTTP()) { + $flags[] = '--trust-server-cert'; + } + + $credential_phid = $this->getCredentialPHID(); + if ($credential_phid) { + $key = PassphrasePasswordKey::loadFromPHID( + $credential_phid, + PhabricatorUser::getOmnipotentUser()); + $flags[] = '--username %P'; + $flags[] = '--password %P'; + $flag_args[] = $key->getUsernameEnvelope(); + $flag_args[] = $key->getPasswordEnvelope(); + } + + $flags = implode(' ', $flags); + $pattern = "svn {$flags} {$pattern}"; + $args = array_mergev(array($flag_args, $args)); } else { $pattern = "svn --non-interactive {$pattern}"; } @@ -687,11 +684,7 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO } $protocol = $this->getRemoteProtocol(); - if ($protocol == 'http' || $protocol == 'https') { - return (bool)$this->getDetail('http-login'); - } else { - return false; - } + return ($protocol == 'http' || $protocol == 'https'); } @@ -708,11 +701,7 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO } $protocol = $this->getRemoteProtocol(); - if ($protocol == 'svn') { - return (bool)$this->getDetail('http-login'); - } else { - return false; - } + return ($protocol == 'svn'); } diff --git a/src/applications/repository/storage/PhabricatorRepositoryTransaction.php b/src/applications/repository/storage/PhabricatorRepositoryTransaction.php index e40c139cf4..84c74276ab 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryTransaction.php +++ b/src/applications/repository/storage/PhabricatorRepositoryTransaction.php @@ -16,16 +16,19 @@ final class PhabricatorRepositoryTransaction const TYPE_NOTIFY = 'repo:notify'; const TYPE_AUTOCLOSE = 'repo:autoclose'; const TYPE_REMOTE_URI = 'repo:remote-uri'; - const TYPE_SSH_LOGIN = 'repo:ssh-login'; - const TYPE_SSH_KEY = 'repo:ssh-key'; - const TYPE_SSH_KEYFILE = 'repo:ssh-keyfile'; - const TYPE_HTTP_LOGIN = 'repo:http-login'; - const TYPE_HTTP_PASS = 'repo:http-pass'; const TYPE_LOCAL_PATH = 'repo:local-path'; const TYPE_HOSTING = 'repo:hosting'; const TYPE_PROTOCOL_HTTP = 'repo:serve-http'; const TYPE_PROTOCOL_SSH = 'repo:serve-ssh'; const TYPE_PUSH_POLICY = 'repo:push-policy'; + const TYPE_CREDENTIAL = 'repo:credential'; + + // TODO: Clean up these legacy transaction types. + const TYPE_SSH_LOGIN = 'repo:ssh-login'; + const TYPE_SSH_KEY = 'repo:ssh-key'; + const TYPE_SSH_KEYFILE = 'repo:ssh-keyfile'; + const TYPE_HTTP_LOGIN = 'repo:http-login'; + const TYPE_HTTP_PASS = 'repo:http-pass'; public function getApplicationName() { return 'repository'; diff --git a/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php b/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php index 253633fddc..95ffc58695 100644 --- a/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php +++ b/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php @@ -1776,6 +1776,14 @@ final class PhabricatorBuiltinPatchList extends PhabricatorSQLPatchList { 'type' => 'sql', 'name' => $this->getPatchPath('20131121.passphraseedge.sql'), ), + '20131121.repocredentials.1.col.sql' => array( + 'type' => 'sql', + 'name' => $this->getPatchPath('20131121.repocredentials.1.col.sql'), + ), + '20131121.repocredentials.2.mig.php' => array( + 'type' => 'php', + 'name' => $this->getPatchPath('20131121.repocredentials.2.mig.php'), + ), ); } }