mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-19 20:10:55 +01:00
Fill in new URI credential edit web UI interfaces
Summary: Ref T10748. Ref T10366. Allows users to set credential for new URIs. - Ref T7221. Our handling of the "git://" protocol is currently incorrect. This protocol is not authenticated, but is considered an SSH protocol. In the new UI, it is considered an anonymous/unauthenticated protocol instead. - Ref T10241. This fixes the `PassphraseCredentialControl` so it doesn't silently edit the value if the current value is not visible to you and/or not valid. Test Plan: Performed a whole lot of credential edits, removals, and adjustments. I'll give this additional vetting before cutting over to it. {F1253207} Reviewers: chad Reviewed By: chad Maniphest Tasks: T7221, T10241, T10366, T10748 Differential Revision: https://secure.phabricator.com/D15829
This commit is contained in:
parent
45ca0472a9
commit
99718b61d8
13 changed files with 551 additions and 24 deletions
|
@ -789,6 +789,7 @@ phutil_register_library_map(array(
|
||||||
'DiffusionRepositorySymbolsManagementPanel' => 'applications/diffusion/management/DiffusionRepositorySymbolsManagementPanel.php',
|
'DiffusionRepositorySymbolsManagementPanel' => 'applications/diffusion/management/DiffusionRepositorySymbolsManagementPanel.php',
|
||||||
'DiffusionRepositoryTag' => 'applications/diffusion/data/DiffusionRepositoryTag.php',
|
'DiffusionRepositoryTag' => 'applications/diffusion/data/DiffusionRepositoryTag.php',
|
||||||
'DiffusionRepositoryTestAutomationController' => 'applications/diffusion/controller/DiffusionRepositoryTestAutomationController.php',
|
'DiffusionRepositoryTestAutomationController' => 'applications/diffusion/controller/DiffusionRepositoryTestAutomationController.php',
|
||||||
|
'DiffusionRepositoryURICredentialController' => 'applications/diffusion/controller/DiffusionRepositoryURICredentialController.php',
|
||||||
'DiffusionRepositoryURIDisableController' => 'applications/diffusion/controller/DiffusionRepositoryURIDisableController.php',
|
'DiffusionRepositoryURIDisableController' => 'applications/diffusion/controller/DiffusionRepositoryURIDisableController.php',
|
||||||
'DiffusionRepositoryURIEditController' => 'applications/diffusion/controller/DiffusionRepositoryURIEditController.php',
|
'DiffusionRepositoryURIEditController' => 'applications/diffusion/controller/DiffusionRepositoryURIEditController.php',
|
||||||
'DiffusionRepositoryURIViewController' => 'applications/diffusion/controller/DiffusionRepositoryURIViewController.php',
|
'DiffusionRepositoryURIViewController' => 'applications/diffusion/controller/DiffusionRepositoryURIViewController.php',
|
||||||
|
@ -5020,6 +5021,7 @@ phutil_register_library_map(array(
|
||||||
'DiffusionRepositorySymbolsManagementPanel' => 'DiffusionRepositoryManagementPanel',
|
'DiffusionRepositorySymbolsManagementPanel' => 'DiffusionRepositoryManagementPanel',
|
||||||
'DiffusionRepositoryTag' => 'Phobject',
|
'DiffusionRepositoryTag' => 'Phobject',
|
||||||
'DiffusionRepositoryTestAutomationController' => 'DiffusionRepositoryEditController',
|
'DiffusionRepositoryTestAutomationController' => 'DiffusionRepositoryEditController',
|
||||||
|
'DiffusionRepositoryURICredentialController' => 'DiffusionController',
|
||||||
'DiffusionRepositoryURIDisableController' => 'DiffusionController',
|
'DiffusionRepositoryURIDisableController' => 'DiffusionController',
|
||||||
'DiffusionRepositoryURIEditController' => 'DiffusionController',
|
'DiffusionRepositoryURIEditController' => 'DiffusionController',
|
||||||
'DiffusionRepositoryURIViewController' => 'DiffusionController',
|
'DiffusionRepositoryURIViewController' => 'DiffusionController',
|
||||||
|
|
|
@ -97,6 +97,8 @@ final class PhabricatorDiffusionApplication extends PhabricatorApplication {
|
||||||
=> 'DiffusionRepositoryURIDisableController',
|
=> 'DiffusionRepositoryURIDisableController',
|
||||||
$this->getEditRoutePattern('edit/')
|
$this->getEditRoutePattern('edit/')
|
||||||
=> 'DiffusionRepositoryURIEditController',
|
=> 'DiffusionRepositoryURIEditController',
|
||||||
|
'credential/(?P<id>[0-9]\d*)/(?P<action>edit|remove)/'
|
||||||
|
=> 'DiffusionRepositoryURICredentialController',
|
||||||
),
|
),
|
||||||
'edit/' => array(
|
'edit/' => array(
|
||||||
'' => 'DiffusionRepositoryEditMainController',
|
'' => 'DiffusionRepositoryEditMainController',
|
||||||
|
|
|
@ -108,7 +108,7 @@ final class DiffusionMirrorEditController
|
||||||
->setName('remoteURI')
|
->setName('remoteURI')
|
||||||
->setValue($v_remote)
|
->setValue($v_remote)
|
||||||
->setError($e_remote))
|
->setError($e_remote))
|
||||||
->appendChild(
|
->appendControl(
|
||||||
id(new PassphraseCredentialControl())
|
id(new PassphraseCredentialControl())
|
||||||
->setLabel(pht('Credentials'))
|
->setLabel(pht('Credentials'))
|
||||||
->setName('credential')
|
->setName('credential')
|
||||||
|
|
|
@ -510,6 +510,7 @@ final class DiffusionRepositoryCreateController
|
||||||
->setAdjustFormPageCallback(array($this, 'adjustAuthPage'))
|
->setAdjustFormPageCallback(array($this, 'adjustAuthPage'))
|
||||||
->addControl(
|
->addControl(
|
||||||
id(new PassphraseCredentialControl())
|
id(new PassphraseCredentialControl())
|
||||||
|
->setViewer($this->getViewer())
|
||||||
->setName('credential'));
|
->setName('credential'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,159 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class DiffusionRepositoryURICredentialController
|
||||||
|
extends DiffusionController {
|
||||||
|
|
||||||
|
public function handleRequest(AphrontRequest $request) {
|
||||||
|
$response = $this->loadDiffusionContextForEdit();
|
||||||
|
if ($response) {
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
$viewer = $this->getViewer();
|
||||||
|
$drequest = $this->getDiffusionRequest();
|
||||||
|
$repository = $drequest->getRepository();
|
||||||
|
|
||||||
|
$id = $request->getURIData('id');
|
||||||
|
$uri = id(new PhabricatorRepositoryURIQuery())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->withIDs(array($id))
|
||||||
|
->withRepositories(array($repository))
|
||||||
|
->requireCapabilities(
|
||||||
|
array(
|
||||||
|
PhabricatorPolicyCapability::CAN_VIEW,
|
||||||
|
PhabricatorPolicyCapability::CAN_EDIT,
|
||||||
|
))
|
||||||
|
->executeOne();
|
||||||
|
if (!$uri) {
|
||||||
|
return new Aphront404Response();
|
||||||
|
}
|
||||||
|
|
||||||
|
$is_builtin = $uri->isBuiltin();
|
||||||
|
$has_credential = (bool)$uri->getCredentialPHID();
|
||||||
|
$view_uri = $uri->getViewURI();
|
||||||
|
$is_remove = ($request->getURIData('action') == 'remove');
|
||||||
|
|
||||||
|
if ($is_builtin) {
|
||||||
|
return $this->newDialog()
|
||||||
|
->setTitle(pht('Builtin URIs Do Not Use Credentials'))
|
||||||
|
->appendParagraph(
|
||||||
|
pht(
|
||||||
|
'You can not set a credential for builtin URIs which Phabricator '.
|
||||||
|
'hosts and serves. Phabricator does not fetch from these URIs or '.
|
||||||
|
'push to these URIs, and does not need credentials to '.
|
||||||
|
'authenticate any activity against them.'))
|
||||||
|
->addCancelButton($view_uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->isFormPost()) {
|
||||||
|
$xactions = array();
|
||||||
|
|
||||||
|
if ($is_remove) {
|
||||||
|
$new_phid = null;
|
||||||
|
} else {
|
||||||
|
$new_phid = $request->getStr('credentialPHID');
|
||||||
|
}
|
||||||
|
|
||||||
|
$type_credential = PhabricatorRepositoryURITransaction::TYPE_CREDENTIAL;
|
||||||
|
|
||||||
|
$xactions[] = id(new PhabricatorRepositoryURITransaction())
|
||||||
|
->setTransactionType($type_credential)
|
||||||
|
->setNewValue($new_phid);
|
||||||
|
|
||||||
|
$editor = id(new DiffusionURIEditor())
|
||||||
|
->setActor($viewer)
|
||||||
|
->setContinueOnNoEffect(true)
|
||||||
|
->setContinueOnMissingFields(true)
|
||||||
|
->setContentSourceFromRequest($request)
|
||||||
|
->applyTransactions($uri, $xactions);
|
||||||
|
|
||||||
|
return id(new AphrontRedirectResponse())->setURI($view_uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
$command_engine = $uri->newCommandEngine();
|
||||||
|
$is_supported = $command_engine->isCredentialSupported();
|
||||||
|
|
||||||
|
$body = null;
|
||||||
|
$form = null;
|
||||||
|
$width = AphrontDialogView::WIDTH_DEFAULT;
|
||||||
|
if ($is_remove) {
|
||||||
|
if ($has_credential) {
|
||||||
|
$title = pht('Remove Credential');
|
||||||
|
$body = pht(
|
||||||
|
'This credential will no longer be used to authenticate activity '.
|
||||||
|
'against this URI.');
|
||||||
|
$button = pht('Remove Credential');
|
||||||
|
} else {
|
||||||
|
$title = pht('No Credential');
|
||||||
|
$body = pht(
|
||||||
|
'This URI does not have an associated credential.');
|
||||||
|
$button = null;
|
||||||
|
}
|
||||||
|
} else if (!$is_supported) {
|
||||||
|
$title = pht('Unauthenticated Protocol');
|
||||||
|
$body = pht(
|
||||||
|
'The protocol for this URI ("%s") does not use authentication, so '.
|
||||||
|
'you can not provide a credential.',
|
||||||
|
$command_engine->getDisplayProtocol());
|
||||||
|
$button = null;
|
||||||
|
} else {
|
||||||
|
$effective_uri = $uri->getEffectiveURI();
|
||||||
|
|
||||||
|
$label = $command_engine->getPassphraseCredentialLabel();
|
||||||
|
$credential_type = $command_engine->getPassphraseDefaultCredentialType();
|
||||||
|
|
||||||
|
$provides_type = $command_engine->getPassphraseProvidesCredentialType();
|
||||||
|
$options = id(new PassphraseCredentialQuery())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->withIsDestroyed(false)
|
||||||
|
->withProvidesTypes(array($provides_type))
|
||||||
|
->execute();
|
||||||
|
|
||||||
|
$control = id(new PassphraseCredentialControl())
|
||||||
|
->setName('credentialPHID')
|
||||||
|
->setLabel($label)
|
||||||
|
->setValue($uri->getCredentialPHID())
|
||||||
|
->setCredentialType($credential_type)
|
||||||
|
->setOptions($options);
|
||||||
|
|
||||||
|
$default_user = $effective_uri->getUser();
|
||||||
|
if (strlen($default_user)) {
|
||||||
|
$control->setDefaultUsername($default_user);
|
||||||
|
}
|
||||||
|
|
||||||
|
$form = id(new AphrontFormView())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->appendControl($control);
|
||||||
|
|
||||||
|
if ($has_credential) {
|
||||||
|
$title = pht('Update Credential');
|
||||||
|
$button = pht('Update Credential');
|
||||||
|
} else {
|
||||||
|
$title = pht('Set Credential');
|
||||||
|
$button = pht('Set Credential');
|
||||||
|
}
|
||||||
|
|
||||||
|
$width = AphrontDialogView::WIDTH_FORM;
|
||||||
|
}
|
||||||
|
|
||||||
|
$dialog = $this->newDialog()
|
||||||
|
->setWidth($width)
|
||||||
|
->setTitle($title)
|
||||||
|
->addCancelButton($view_uri);
|
||||||
|
|
||||||
|
if ($body) {
|
||||||
|
$dialog->appendParagraph($body);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($form) {
|
||||||
|
$dialog->appendForm($form);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($button) {
|
||||||
|
$dialog->addSubmitButton($button);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $dialog;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -82,6 +82,7 @@ final class DiffusionRepositoryURIViewController
|
||||||
|
|
||||||
private function buildCurtain(PhabricatorRepositoryURI $uri) {
|
private function buildCurtain(PhabricatorRepositoryURI $uri) {
|
||||||
$viewer = $this->getViewer();
|
$viewer = $this->getViewer();
|
||||||
|
$repository = $uri->getRepository();
|
||||||
$id = $uri->getID();
|
$id = $uri->getID();
|
||||||
|
|
||||||
$can_edit = PhabricatorPolicyFilter::hasCapability(
|
$can_edit = PhabricatorPolicyFilter::hasCapability(
|
||||||
|
@ -89,7 +90,6 @@ final class DiffusionRepositoryURIViewController
|
||||||
$uri,
|
$uri,
|
||||||
PhabricatorPolicyCapability::CAN_EDIT);
|
PhabricatorPolicyCapability::CAN_EDIT);
|
||||||
|
|
||||||
|
|
||||||
$curtain = $this->newCurtainView($uri);
|
$curtain = $this->newCurtainView($uri);
|
||||||
|
|
||||||
$edit_uri = $uri->getEditURI();
|
$edit_uri = $uri->getEditURI();
|
||||||
|
@ -102,6 +102,43 @@ final class DiffusionRepositoryURIViewController
|
||||||
->setWorkflow(!$can_edit)
|
->setWorkflow(!$can_edit)
|
||||||
->setDisabled(!$can_edit));
|
->setDisabled(!$can_edit));
|
||||||
|
|
||||||
|
$credential_uri = $repository->getPathURI("uri/credential/{$id}/edit/");
|
||||||
|
$remove_uri = $repository->getPathURI("uri/credential/{$id}/remove/");
|
||||||
|
$has_credential = (bool)$uri->getCredentialPHID();
|
||||||
|
|
||||||
|
if ($uri->isBuiltin()) {
|
||||||
|
$can_credential = false;
|
||||||
|
} else if (!$uri->newCommandEngine()->isCredentialSupported()) {
|
||||||
|
$can_credential = false;
|
||||||
|
} else {
|
||||||
|
$can_credential = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$can_update = ($can_edit && $can_credential);
|
||||||
|
$can_remove = ($can_edit && $has_credential);
|
||||||
|
|
||||||
|
if ($has_credential) {
|
||||||
|
$credential_name = pht('Update Credential');
|
||||||
|
} else {
|
||||||
|
$credential_name = pht('Set Credential');
|
||||||
|
}
|
||||||
|
|
||||||
|
$curtain->addAction(
|
||||||
|
id(new PhabricatorActionView())
|
||||||
|
->setIcon('fa-key')
|
||||||
|
->setName($credential_name)
|
||||||
|
->setHref($credential_uri)
|
||||||
|
->setWorkflow(true)
|
||||||
|
->setDisabled(!$can_edit));
|
||||||
|
|
||||||
|
$curtain->addAction(
|
||||||
|
id(new PhabricatorActionView())
|
||||||
|
->setIcon('fa-times')
|
||||||
|
->setName(pht('Remove Credential'))
|
||||||
|
->setHref($remove_uri)
|
||||||
|
->setWorkflow(true)
|
||||||
|
->setDisabled(!$can_remove));
|
||||||
|
|
||||||
if ($uri->getIsDisabled()) {
|
if ($uri->getIsDisabled()) {
|
||||||
$disable_name = pht('Enable URI');
|
$disable_name = pht('Enable URI');
|
||||||
$disable_icon = 'fa-check';
|
$disable_icon = 'fa-check';
|
||||||
|
@ -110,7 +147,7 @@ final class DiffusionRepositoryURIViewController
|
||||||
$disable_icon = 'fa-ban';
|
$disable_icon = 'fa-ban';
|
||||||
}
|
}
|
||||||
|
|
||||||
$disable_uri = $uri->getRepository()->getPathURI("uri/disable/{$id}/");
|
$disable_uri = $repository->getPathURI("uri/disable/{$id}/");
|
||||||
|
|
||||||
$curtain->addAction(
|
$curtain->addAction(
|
||||||
id(new PhabricatorActionView())
|
id(new PhabricatorActionView())
|
||||||
|
@ -130,7 +167,84 @@ final class DiffusionRepositoryURIViewController
|
||||||
->setUser($viewer);
|
->setUser($viewer);
|
||||||
|
|
||||||
$properties->addProperty(pht('URI'), $uri->getDisplayURI());
|
$properties->addProperty(pht('URI'), $uri->getDisplayURI());
|
||||||
$properties->addProperty(pht('Credential'), 'TODO');
|
|
||||||
|
$credential_phid = $uri->getCredentialPHID();
|
||||||
|
$command_engine = $uri->newCommandEngine();
|
||||||
|
$is_optional = $command_engine->isCredentialOptional();
|
||||||
|
$is_supported = $command_engine->isCredentialSupported();
|
||||||
|
$is_builtin = $uri->isBuiltin();
|
||||||
|
|
||||||
|
if ($is_builtin) {
|
||||||
|
$credential_icon = 'fa-circle-o';
|
||||||
|
$credential_color = 'grey';
|
||||||
|
$credential_label = pht('Builtin');
|
||||||
|
$credential_note = pht('Builtin URIs do not use credentials.');
|
||||||
|
} else if (!$is_supported) {
|
||||||
|
$credential_icon = 'fa-circle-o';
|
||||||
|
$credential_color = 'grey';
|
||||||
|
$credential_label = pht('Not Supported');
|
||||||
|
$credential_note = pht('This protocol does not support authentication.');
|
||||||
|
} else if (!$credential_phid) {
|
||||||
|
if ($is_optional) {
|
||||||
|
$credential_icon = 'fa-circle-o';
|
||||||
|
$credential_color = 'green';
|
||||||
|
$credential_label = pht('No Credential');
|
||||||
|
$credential_note = pht('Configured for anonymous access.');
|
||||||
|
} else {
|
||||||
|
$credential_icon = 'fa-times';
|
||||||
|
$credential_color = 'red';
|
||||||
|
$credential_label = pht('Required');
|
||||||
|
$credential_note = pht('Credential required but not configured.');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Don't raise a policy exception if we can't see the credential.
|
||||||
|
$credentials = id(new PassphraseCredentialQuery())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->withPHIDs(array($credential_phid))
|
||||||
|
->execute();
|
||||||
|
$credential = head($credentials);
|
||||||
|
|
||||||
|
if (!$credential) {
|
||||||
|
$handles = $viewer->loadHandles(array($credential_phid));
|
||||||
|
$handle = $handles[$credential_phid];
|
||||||
|
if ($handle->getPolicyFiltered()) {
|
||||||
|
$credential_icon = 'fa-lock';
|
||||||
|
$credential_color = 'grey';
|
||||||
|
$credential_label = pht('Restricted');
|
||||||
|
$credential_note = pht(
|
||||||
|
'You do not have permission to view the configured '.
|
||||||
|
'credential.');
|
||||||
|
} else {
|
||||||
|
$credential_icon = 'fa-times';
|
||||||
|
$credential_color = 'red';
|
||||||
|
$credential_label = pht('Invalid');
|
||||||
|
$credential_note = pht('Configured credential is invalid.');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$provides = $credential->getProvidesType();
|
||||||
|
$needs = $command_engine->getPassphraseProvidesCredentialType();
|
||||||
|
if ($provides != $needs) {
|
||||||
|
$credential_icon = 'fa-times';
|
||||||
|
$credential_color = 'red';
|
||||||
|
$credential_label = pht('Wrong Type');
|
||||||
|
} else {
|
||||||
|
$credential_icon = 'fa-check';
|
||||||
|
$credential_color = 'green';
|
||||||
|
$credential_label = $command_engine->getPassphraseCredentialLabel();
|
||||||
|
}
|
||||||
|
$credential_note = $viewer->renderHandle($credential_phid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$credential_item = id(new PHUIStatusItemView())
|
||||||
|
->setIcon($credential_icon, $credential_color)
|
||||||
|
->setTarget(phutil_tag('strong', array(), $credential_label))
|
||||||
|
->setNote($credential_note);
|
||||||
|
|
||||||
|
$credential_view = id(new PHUIStatusListView())
|
||||||
|
->addItem($credential_item);
|
||||||
|
|
||||||
|
$properties->addProperty(pht('Credential'), $credential_view);
|
||||||
|
|
||||||
|
|
||||||
$io_type = $uri->getEffectiveIOType();
|
$io_type = $uri->getEffectiveIOType();
|
||||||
|
|
|
@ -83,6 +83,14 @@ final class DiffusionURIEditEngine
|
||||||
protected function buildCustomEditFields($object) {
|
protected function buildCustomEditFields($object) {
|
||||||
$viewer = $this->getViewer();
|
$viewer = $this->getViewer();
|
||||||
|
|
||||||
|
if ($object->isBuiltin()) {
|
||||||
|
$is_builtin = true;
|
||||||
|
$uri_value = (string)$object->getDisplayURI();
|
||||||
|
} else {
|
||||||
|
$is_builtin = false;
|
||||||
|
$uri_value = $object->getURI();
|
||||||
|
}
|
||||||
|
|
||||||
return array(
|
return array(
|
||||||
id(new PhabricatorHandlesEditField())
|
id(new PhabricatorHandlesEditField())
|
||||||
->setKey('repository')
|
->setKey('repository')
|
||||||
|
@ -104,12 +112,13 @@ final class DiffusionURIEditEngine
|
||||||
id(new PhabricatorTextEditField())
|
id(new PhabricatorTextEditField())
|
||||||
->setKey('uri')
|
->setKey('uri')
|
||||||
->setLabel(pht('URI'))
|
->setLabel(pht('URI'))
|
||||||
->setIsRequired(true)
|
|
||||||
->setTransactionType(PhabricatorRepositoryURITransaction::TYPE_URI)
|
->setTransactionType(PhabricatorRepositoryURITransaction::TYPE_URI)
|
||||||
->setDescription(pht('The repository URI.'))
|
->setDescription(pht('The repository URI.'))
|
||||||
->setConduitDescription(pht('Change the repository URI.'))
|
->setConduitDescription(pht('Change the repository URI.'))
|
||||||
->setConduitTypeDescription(pht('New repository URI.'))
|
->setConduitTypeDescription(pht('New repository URI.'))
|
||||||
->setValue($object->getURI()),
|
->setIsRequired(!$is_builtin)
|
||||||
|
->setIsLocked($is_builtin)
|
||||||
|
->setValue($uri_value),
|
||||||
id(new PhabricatorSelectEditField())
|
id(new PhabricatorSelectEditField())
|
||||||
->setKey('io')
|
->setKey('io')
|
||||||
->setLabel(pht('I/O Type'))
|
->setLabel(pht('I/O Type'))
|
||||||
|
|
|
@ -70,7 +70,42 @@ final class DiffusionURIEditor
|
||||||
|
|
||||||
switch ($xaction->getTransactionType()) {
|
switch ($xaction->getTransactionType()) {
|
||||||
case PhabricatorRepositoryURITransaction::TYPE_URI:
|
case PhabricatorRepositoryURITransaction::TYPE_URI:
|
||||||
|
if (!$this->getIsNewObject()) {
|
||||||
|
$old_uri = $object->getEffectiveURI();
|
||||||
|
} else {
|
||||||
|
$old_uri = null;
|
||||||
|
}
|
||||||
|
|
||||||
$object->setURI($xaction->getNewValue());
|
$object->setURI($xaction->getNewValue());
|
||||||
|
|
||||||
|
// If we've changed the domain or protocol of the URI, remove the
|
||||||
|
// current credential. This improves behavior in several cases:
|
||||||
|
|
||||||
|
// If a user switches between protocols with different credential
|
||||||
|
// types, like HTTP and SSH, the old credential won't be valid anyway.
|
||||||
|
// It's cleaner to remove it than leave a bad credential in place.
|
||||||
|
|
||||||
|
// If a user switches hosts, the old credential is probably not
|
||||||
|
// correct (and potentially confusing/misleading). Removing it forces
|
||||||
|
// users to double check that they have the correct credentials.
|
||||||
|
|
||||||
|
// If an attacker can't see a symmetric credential like a username and
|
||||||
|
// password, they could still potentially capture it by changing the
|
||||||
|
// host for a URI that uses it to `evil.com`, a server they control,
|
||||||
|
// then observing the requests. Removing the credential prevents this
|
||||||
|
// kind of escalation.
|
||||||
|
|
||||||
|
// Since port and path changes are less likely to fall among these
|
||||||
|
// cases, they don't trigger a credential wipe.
|
||||||
|
|
||||||
|
$new_uri = $object->getEffectiveURI();
|
||||||
|
if ($old_uri) {
|
||||||
|
$new_proto = ($old_uri->getProtocol() != $new_uri->getProtocol());
|
||||||
|
$new_domain = ($old_uri->getDomain() != $new_uri->getDomain());
|
||||||
|
if ($new_proto || $new_domain) {
|
||||||
|
$object->setCredentialPHID(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case PhabricatorRepositoryURITransaction::TYPE_IO:
|
case PhabricatorRepositoryURITransaction::TYPE_IO:
|
||||||
$object->setIOType($xaction->getNewValue());
|
$object->setIOType($xaction->getNewValue());
|
||||||
|
@ -184,6 +219,11 @@ final class DiffusionURIEditor
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Anyone who can edit a URI can remove the credential.
|
||||||
|
if ($credential_phid === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
$credential = id(new PassphraseCredentialQuery())
|
$credential = id(new PassphraseCredentialQuery())
|
||||||
->setViewer($viewer)
|
->setViewer($viewer)
|
||||||
->withPHIDs(array($credential_phid))
|
->withPHIDs(array($credential_phid))
|
||||||
|
|
|
@ -57,6 +57,10 @@ abstract class DiffusionCommandEngine extends Phobject {
|
||||||
return $this->protocol;
|
return $this->protocol;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getDisplayProtocol() {
|
||||||
|
return $this->getProtocol().'://';
|
||||||
|
}
|
||||||
|
|
||||||
public function setCredentialPHID($credential_phid) {
|
public function setCredentialPHID($credential_phid) {
|
||||||
$this->credentialPHID = $credential_phid;
|
$this->credentialPHID = $credential_phid;
|
||||||
return $this;
|
return $this;
|
||||||
|
@ -197,34 +201,82 @@ abstract class DiffusionCommandEngine extends Phobject {
|
||||||
return $env;
|
return $env;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function isSSHProtocol() {
|
public function isSSHProtocol() {
|
||||||
return ($this->getProtocol() == 'ssh');
|
return ($this->getProtocol() == 'ssh');
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function isSVNProtocol() {
|
public function isSVNProtocol() {
|
||||||
return ($this->getProtocol() == 'svn');
|
return ($this->getProtocol() == 'svn');
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function isSVNSSHProtocol() {
|
public function isSVNSSHProtocol() {
|
||||||
return ($this->getProtocol() == 'svn+ssh');
|
return ($this->getProtocol() == 'svn+ssh');
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function isHTTPProtocol() {
|
public function isHTTPProtocol() {
|
||||||
return ($this->getProtocol() == 'http');
|
return ($this->getProtocol() == 'http');
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function isHTTPSProtocol() {
|
public function isHTTPSProtocol() {
|
||||||
return ($this->getProtocol() == 'https');
|
return ($this->getProtocol() == 'https');
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function isAnyHTTPProtocol() {
|
public function isAnyHTTPProtocol() {
|
||||||
return ($this->isHTTPProtocol() || $this->isHTTPSProtocol());
|
return ($this->isHTTPProtocol() || $this->isHTTPSProtocol());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function isAnySSHProtocol() {
|
public function isAnySSHProtocol() {
|
||||||
return ($this->isSSHProtocol() || $this->isSVNSSHProtocol());
|
return ($this->isSSHProtocol() || $this->isSVNSSHProtocol());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isCredentialSupported() {
|
||||||
|
return ($this->getPassphraseProvidesCredentialType() !== null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isCredentialOptional() {
|
||||||
|
if ($this->isAnySSHProtocol()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPassphraseCredentialLabel() {
|
||||||
|
if ($this->isAnySSHProtocol()) {
|
||||||
|
return pht('SSH Key');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->isAnyHTTPProtocol() || $this->isSVNProtocol()) {
|
||||||
|
return pht('Password');
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPassphraseDefaultCredentialType() {
|
||||||
|
if ($this->isAnySSHProtocol()) {
|
||||||
|
return PassphraseSSHPrivateKeyTextCredentialType::CREDENTIAL_TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->isAnyHTTPProtocol() || $this->isSVNProtocol()) {
|
||||||
|
return PassphrasePasswordCredentialType::CREDENTIAL_TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPassphraseProvidesCredentialType() {
|
||||||
|
if ($this->isAnySSHProtocol()) {
|
||||||
|
return PassphraseSSHPrivateKeyCredentialType::PROVIDES_TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->isAnyHTTPProtocol() || $this->isSVNProtocol()) {
|
||||||
|
return PassphrasePasswordCredentialType::PROVIDES_TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
protected function getSSHWrapper() {
|
protected function getSSHWrapper() {
|
||||||
$root = dirname(phutil_get_library_root('phabricator'));
|
$root = dirname(phutil_get_library_root('phabricator'));
|
||||||
return $root.'/bin/ssh-connect';
|
return $root.'/bin/ssh-connect';
|
||||||
|
|
|
@ -42,10 +42,50 @@ final class PassphraseCredentialControl extends AphrontFormControl {
|
||||||
foreach ($this->options as $option) {
|
foreach ($this->options as $option) {
|
||||||
$options_map[$option->getPHID()] = pht(
|
$options_map[$option->getPHID()] = pht(
|
||||||
'%s %s',
|
'%s %s',
|
||||||
'K'.$option->getID(),
|
$option->getMonogram(),
|
||||||
$option->getName());
|
$option->getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The user editing the form may not have permission to see the current
|
||||||
|
// credential. Populate it into the menu to allow them to save the form
|
||||||
|
// without making any changes.
|
||||||
|
$current_phid = $this->getValue();
|
||||||
|
if (strlen($current_phid) && empty($options_map[$current_phid])) {
|
||||||
|
$viewer = $this->getViewer();
|
||||||
|
|
||||||
|
$user_credential = id(new PassphraseCredentialQuery())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->withPHIDs(array($current_phid))
|
||||||
|
->executeOne();
|
||||||
|
if (!$user_credential) {
|
||||||
|
// Pull the credential with the ominipotent viewer so we can look up
|
||||||
|
// the ID and tell if it's restricted or invalid.
|
||||||
|
$omnipotent_credential = id(new PassphraseCredentialQuery())
|
||||||
|
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||||
|
->withPHIDs(array($current_phid))
|
||||||
|
->executeOne();
|
||||||
|
if ($omnipotent_credential) {
|
||||||
|
$current_name = pht(
|
||||||
|
'%s (Restricted Credential)',
|
||||||
|
$omnipotent_credential->getMonogram());
|
||||||
|
} else {
|
||||||
|
$current_name = pht(
|
||||||
|
'Invalid Credential ("%s")',
|
||||||
|
$current_phid);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$current_name = pht(
|
||||||
|
'%s %s',
|
||||||
|
$user_credential->getMonogram(),
|
||||||
|
$user_credential->getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
$options_map = array(
|
||||||
|
$current_phid => $current_name,
|
||||||
|
) + $options_map;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
$disabled = $this->getDisabled();
|
$disabled = $this->getDisabled();
|
||||||
if ($this->allowNull) {
|
if ($this->allowNull) {
|
||||||
$options_map = array('' => pht('(No Credentials)')) + $options_map;
|
$options_map = array('' => pht('(No Credentials)')) + $options_map;
|
||||||
|
|
|
@ -193,13 +193,75 @@ final class PhabricatorRepositoryURI
|
||||||
|
|
||||||
|
|
||||||
public function getDisplayURI() {
|
public function getDisplayURI() {
|
||||||
$uri = new PhutilURI($this->getURI());
|
return $this->getURIObject(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEffectiveURI() {
|
||||||
|
return $this->getURIObject(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getURIObject($normalize) {
|
||||||
|
// Users can provide Git/SCP-style URIs in the form "user@host:path".
|
||||||
|
// These are equivalent to "ssh://user@host/path". We use the more standard
|
||||||
|
// form internally, and convert to it if we need to specify a port number,
|
||||||
|
// but try to preserve what the user typed when displaying the URI.
|
||||||
|
|
||||||
|
if ($this->isBuiltin()) {
|
||||||
|
$builtin_protocol = $this->getForcedProtocol();
|
||||||
|
$builtin_domain = $this->getForcedHost();
|
||||||
|
$raw_uri = "{$builtin_protocol}://{$builtin_domain}";
|
||||||
|
} else {
|
||||||
|
$raw_uri = $this->getURI();
|
||||||
|
}
|
||||||
|
|
||||||
|
$port = $this->getForcedPort();
|
||||||
|
|
||||||
|
$default_ports = array(
|
||||||
|
'ssh' => 22,
|
||||||
|
'http' => 80,
|
||||||
|
'https' => 443,
|
||||||
|
);
|
||||||
|
|
||||||
|
$uri = new PhutilURI($raw_uri);
|
||||||
|
if (!$uri->getProtocol()) {
|
||||||
|
$git_uri = new PhutilGitURI($raw_uri);
|
||||||
|
|
||||||
|
// We must normalize this Git-style URI into a normal URI
|
||||||
|
$must_normalize = ($port && ($port != $default_ports['ssh']));
|
||||||
|
if ($must_normalize || $normalize) {
|
||||||
|
$domain = $git_uri->getDomain();
|
||||||
|
|
||||||
|
|
||||||
|
$uri = id(new PhutilURI("ssh://{$domain}"))
|
||||||
|
->setUser($git_uri->getUser())
|
||||||
|
->setPath($git_uri->getPath());
|
||||||
|
} else {
|
||||||
|
$uri = $git_uri;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$is_normal = ($uri instanceof PhutilURI);
|
||||||
|
|
||||||
|
if ($is_normal) {
|
||||||
$protocol = $this->getForcedProtocol();
|
$protocol = $this->getForcedProtocol();
|
||||||
if ($protocol) {
|
if ($protocol) {
|
||||||
$uri->setProtocol($protocol);
|
$uri->setProtocol($protocol);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($port) {
|
||||||
|
$uri->setPort($port);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove any explicitly set default ports.
|
||||||
|
$uri_port = $uri->getPort();
|
||||||
|
$uri_protocol = $uri->getProtocol();
|
||||||
|
|
||||||
|
$uri_default = idx($default_ports, $uri_protocol);
|
||||||
|
if ($uri_default && ($uri_default == $uri_port)) {
|
||||||
|
$uri->setPort(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$user = $this->getForcedUser();
|
$user = $this->getForcedUser();
|
||||||
if ($user) {
|
if ($user) {
|
||||||
$uri->setUser($user);
|
$uri->setUser($user);
|
||||||
|
@ -210,11 +272,6 @@ final class PhabricatorRepositoryURI
|
||||||
$uri->setDomain($host);
|
$uri->setDomain($host);
|
||||||
}
|
}
|
||||||
|
|
||||||
$port = $this->getForcedPort();
|
|
||||||
if ($port) {
|
|
||||||
$uri->setPort($port);
|
|
||||||
}
|
|
||||||
|
|
||||||
$path = $this->getForcedPath();
|
$path = $this->getForcedPath();
|
||||||
if ($path) {
|
if ($path) {
|
||||||
$uri->setPath($path);
|
$uri->setPath($path);
|
||||||
|
@ -223,6 +280,7 @@ final class PhabricatorRepositoryURI
|
||||||
return $uri;
|
return $uri;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private function getForcedProtocol() {
|
private function getForcedProtocol() {
|
||||||
switch ($this->getBuiltinProtocol()) {
|
switch ($this->getBuiltinProtocol()) {
|
||||||
case self::BUILTIN_PROTOCOL_SSH:
|
case self::BUILTIN_PROTOCOL_SSH:
|
||||||
|
@ -446,6 +504,16 @@ final class PhabricatorRepositoryURI
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function newCommandEngine() {
|
||||||
|
$repository = $this->getRepository();
|
||||||
|
$protocol = $this->getEffectiveURI()->getProtocol();
|
||||||
|
|
||||||
|
return DiffusionCommandEngine::newCommandEngine($repository)
|
||||||
|
->setCredentialPHID($this->getCredentialPHID())
|
||||||
|
->setProtocol($protocol);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
|
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
|
||||||
|
|
||||||
|
@ -569,7 +637,8 @@ final class PhabricatorRepositoryURI
|
||||||
'repositoryPHID' => $this->getRepositoryPHID(),
|
'repositoryPHID' => $this->getRepositoryPHID(),
|
||||||
'uri' => array(
|
'uri' => array(
|
||||||
'raw' => $this->getURI(),
|
'raw' => $this->getURI(),
|
||||||
'effective' => (string)$this->getDisplayURI(),
|
'display' => (string)$this->getDisplayURI(),
|
||||||
|
'effective' => (string)$this->getEffectiveURI(),
|
||||||
),
|
),
|
||||||
'io' => array(
|
'io' => array(
|
||||||
'raw' => $this->getIOType(),
|
'raw' => $this->getIOType(),
|
||||||
|
|
|
@ -18,6 +18,26 @@ final class PhabricatorRepositoryURITransaction
|
||||||
return PhabricatorRepositoryURIPHIDType::TYPECONST;
|
return PhabricatorRepositoryURIPHIDType::TYPECONST;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getRequiredHandlePHIDs() {
|
||||||
|
$phids = parent::getRequiredHandlePHIDs();
|
||||||
|
|
||||||
|
$old = $this->getOldValue();
|
||||||
|
$new = $this->getNewValue();
|
||||||
|
|
||||||
|
switch ($this->getTransactionType()) {
|
||||||
|
case self::TYPE_CREDENTIAL:
|
||||||
|
if ($old) {
|
||||||
|
$phids[] = $old;
|
||||||
|
}
|
||||||
|
if ($new) {
|
||||||
|
$phids[] = $new;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $phids;
|
||||||
|
}
|
||||||
|
|
||||||
public function getTitle() {
|
public function getTitle() {
|
||||||
$author_phid = $this->getAuthorPHID();
|
$author_phid = $this->getAuthorPHID();
|
||||||
|
|
||||||
|
@ -61,6 +81,24 @@ final class PhabricatorRepositoryURITransaction
|
||||||
'%s enabled this URI.',
|
'%s enabled this URI.',
|
||||||
$this->renderHandleLink($author_phid));
|
$this->renderHandleLink($author_phid));
|
||||||
}
|
}
|
||||||
|
case self::TYPE_CREDENTIAL:
|
||||||
|
if ($old && $new) {
|
||||||
|
return pht(
|
||||||
|
'%s changed the credential for this URI from %s to %s.',
|
||||||
|
$this->renderHandleLink($author_phid),
|
||||||
|
$this->renderHandleLink($old),
|
||||||
|
$this->renderHandleLink($new));
|
||||||
|
} else if ($old) {
|
||||||
|
return pht(
|
||||||
|
'%s removed %s as the credential for this URI.',
|
||||||
|
$this->renderHandleLink($author_phid),
|
||||||
|
$this->renderHandleLink($old));
|
||||||
|
} else if ($new) {
|
||||||
|
return pht(
|
||||||
|
'%s set the credential for this URI to %s.',
|
||||||
|
$this->renderHandleLink($author_phid),
|
||||||
|
$this->renderHandleLink($new));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,7 @@ final class PhabricatorStandardCustomFieldCredential
|
||||||
->execute();
|
->execute();
|
||||||
|
|
||||||
return id(new PassphraseCredentialControl())
|
return id(new PassphraseCredentialControl())
|
||||||
|
->setViewer($this->getViewer())
|
||||||
->setLabel($this->getFieldName())
|
->setLabel($this->getFieldName())
|
||||||
->setName($this->getFieldKey())
|
->setName($this->getFieldKey())
|
||||||
->setCaption($this->getCaption())
|
->setCaption($this->getCaption())
|
||||||
|
|
Loading…
Reference in a new issue