1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-18 21:02:41 +01:00

Add ViewController and SearchEngine for SSH Public Keys

Summary:
Ref T10917. This primarily prepares these for transactions by giving us a place to:

  - review old deactivated keys; and
  - review changes to keys.

Future changes will add transactions and a timeline so key changes are recorded exhaustively and can be more easily audited.

Test Plan:
{F1652089}

{F1652090}

{F1652091}

{F1652092}

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T10917

Differential Revision: https://secure.phabricator.com/D15946
This commit is contained in:
epriestley 2016-05-19 06:03:06 -07:00
parent 36006bcb8f
commit 08bea1d363
14 changed files with 409 additions and 91 deletions

View file

@ -1876,12 +1876,15 @@ phutil_register_library_map(array(
'PhabricatorAuthRevokeTokenController' => 'applications/auth/controller/PhabricatorAuthRevokeTokenController.php',
'PhabricatorAuthSSHKey' => 'applications/auth/storage/PhabricatorAuthSSHKey.php',
'PhabricatorAuthSSHKeyController' => 'applications/auth/controller/PhabricatorAuthSSHKeyController.php',
'PhabricatorAuthSSHKeyDeleteController' => 'applications/auth/controller/PhabricatorAuthSSHKeyDeleteController.php',
'PhabricatorAuthSSHKeyDeactivateController' => 'applications/auth/controller/PhabricatorAuthSSHKeyDeactivateController.php',
'PhabricatorAuthSSHKeyEditController' => 'applications/auth/controller/PhabricatorAuthSSHKeyEditController.php',
'PhabricatorAuthSSHKeyGenerateController' => 'applications/auth/controller/PhabricatorAuthSSHKeyGenerateController.php',
'PhabricatorAuthSSHKeyListController' => 'applications/auth/controller/PhabricatorAuthSSHKeyListController.php',
'PhabricatorAuthSSHKeyPHIDType' => 'applications/auth/phid/PhabricatorAuthSSHKeyPHIDType.php',
'PhabricatorAuthSSHKeyQuery' => 'applications/auth/query/PhabricatorAuthSSHKeyQuery.php',
'PhabricatorAuthSSHKeySearchEngine' => 'applications/auth/query/PhabricatorAuthSSHKeySearchEngine.php',
'PhabricatorAuthSSHKeyTableView' => 'applications/auth/view/PhabricatorAuthSSHKeyTableView.php',
'PhabricatorAuthSSHKeyViewController' => 'applications/auth/controller/PhabricatorAuthSSHKeyViewController.php',
'PhabricatorAuthSSHPublicKey' => 'applications/auth/sshkey/PhabricatorAuthSSHPublicKey.php',
'PhabricatorAuthSession' => 'applications/auth/storage/PhabricatorAuthSession.php',
'PhabricatorAuthSessionEngine' => 'applications/auth/engine/PhabricatorAuthSessionEngine.php',
@ -6304,12 +6307,15 @@ phutil_register_library_map(array(
'PhabricatorDestructibleInterface',
),
'PhabricatorAuthSSHKeyController' => 'PhabricatorAuthController',
'PhabricatorAuthSSHKeyDeleteController' => 'PhabricatorAuthSSHKeyController',
'PhabricatorAuthSSHKeyDeactivateController' => 'PhabricatorAuthSSHKeyController',
'PhabricatorAuthSSHKeyEditController' => 'PhabricatorAuthSSHKeyController',
'PhabricatorAuthSSHKeyGenerateController' => 'PhabricatorAuthSSHKeyController',
'PhabricatorAuthSSHKeyListController' => 'PhabricatorAuthSSHKeyController',
'PhabricatorAuthSSHKeyPHIDType' => 'PhabricatorPHIDType',
'PhabricatorAuthSSHKeyQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorAuthSSHKeySearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorAuthSSHKeyTableView' => 'AphrontView',
'PhabricatorAuthSSHKeyViewController' => 'PhabricatorAuthSSHKeyController',
'PhabricatorAuthSSHPublicKey' => 'Phobject',
'PhabricatorAuthSession' => array(
'PhabricatorAuthDAO',

View file

@ -157,38 +157,13 @@ final class AlmanacDeviceViewController
->setShowTrusted(true)
->setNoDataString(pht('This device has no associated SSH public keys.'));
try {
PhabricatorSSHKeyGenerator::assertCanGenerateKeypair();
$can_generate = true;
} catch (Exception $ex) {
$can_generate = false;
}
$generate_uri = '/auth/sshkey/generate/?objectPHID='.$device_phid;
$upload_uri = '/auth/sshkey/upload/?objectPHID='.$device_phid;
$menu_button = PhabricatorAuthSSHKeyTableView::newKeyActionsMenu(
$viewer,
$device);
$header = id(new PHUIHeaderView())
->setHeader(pht('SSH Public Keys'))
->addActionLink(
id(new PHUIButtonView())
->setTag('a')
->setHref($generate_uri)
->setWorkflow(true)
->setDisabled(!$can_edit || !$can_generate)
->setText(pht('Generate Keypair'))
->setIcon(
id(new PHUIIconView())
->setIcon('fa-lock')))
->addActionLink(
id(new PHUIButtonView())
->setTag('a')
->setHref($upload_uri)
->setWorkflow(true)
->setDisabled(!$can_edit)
->setText(pht('Upload Public Key'))
->setIcon(
id(new PHUIIconView())
->setIcon('fa-upload')));
->addActionLink($menu_button);
return id(new PHUIObjectBoxView())
->setHeader($header)

View file

@ -75,10 +75,14 @@ final class PhabricatorAuthApplication extends PhabricatorApplication {
'multifactor/'
=> 'PhabricatorAuthNeedsMultiFactorController',
'sshkey/' => array(
$this->getQueryRoutePattern('for/(?P<forPHID>[^/]+)/')
=> 'PhabricatorAuthSSHKeyListController',
'generate/' => 'PhabricatorAuthSSHKeyGenerateController',
'upload/' => 'PhabricatorAuthSSHKeyEditController',
'edit/(?P<id>\d+)/' => 'PhabricatorAuthSSHKeyEditController',
'delete/(?P<id>\d+)/' => 'PhabricatorAuthSSHKeyDeleteController',
'deactivate/(?P<id>\d+)/'
=> 'PhabricatorAuthSSHKeyDeactivateController',
'view/(?P<id>\d+)/' => 'PhabricatorAuthSSHKeyViewController',
),
),

View file

@ -3,18 +3,34 @@
abstract class PhabricatorAuthSSHKeyController
extends PhabricatorAuthController {
protected function newKeyForObjectPHID($object_phid) {
private $keyObject;
public function setSSHKeyObject(PhabricatorSSHPublicKeyInterface $object) {
$this->keyObject = $object;
return $this;
}
public function getSSHKeyObject() {
return $this->keyObject;
}
protected function loadSSHKeyObject($object_phid, $need_edit) {
$viewer = $this->getViewer();
$object = id(new PhabricatorObjectQuery())
$query = id(new PhabricatorObjectQuery())
->setViewer($viewer)
->withPHIDs(array($object_phid))
->requireCapabilities(
->withPHIDs(array($object_phid));
if ($need_edit) {
$query->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
));
}
$object = $query->executeOne();
if (!$object) {
return null;
}
@ -25,7 +41,38 @@ abstract class PhabricatorAuthSSHKeyController
return null;
}
$this->keyObject = $object;
return $object;
}
protected function newKeyForObjectPHID($object_phid) {
$viewer = $this->getViewer();
$object = $this->loadSSHKeyObject($object_phid, true);
if (!$object) {
return null;
}
return PhabricatorAuthSSHKey::initializeNewSSHKey($viewer, $object);
}
protected function buildApplicationCrumbs() {
$crumbs = parent::buildApplicationCrumbs();
$viewer = $this->getViewer();
$key_object = $this->getSSHKeyObject();
if ($key_object) {
$object_phid = $key_object->getPHID();
$handles = $viewer->loadHandles(array($object_phid));
$handle = $handles[$object_phid];
$uri = $key_object->getSSHPublicKeyManagementURI($viewer);
$crumbs->addTextCrumb($handle->getObjectName(), $uri);
}
return $crumbs;
}
}

View file

@ -1,6 +1,6 @@
<?php
final class PhabricatorAuthSSHKeyDeleteController
final class PhabricatorAuthSSHKeyDeactivateController
extends PhabricatorAuthSSHKeyController {
public function handleRequest(AphrontRequest $request) {
@ -9,7 +9,6 @@ final class PhabricatorAuthSSHKeyDeleteController
$key = id(new PhabricatorAuthSSHKeyQuery())
->setViewer($viewer)
->withIDs(array($request->getURIData('id')))
->withIsActive(true)
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
@ -20,7 +19,7 @@ final class PhabricatorAuthSSHKeyDeleteController
return new Aphront404Response();
}
$cancel_uri = $key->getObject()->getSSHPublicKeyManagementURI($viewer);
$cancel_uri = $key->getURI();
$token = id(new PhabricatorAuthSessionEngine())->requireHighSecuritySession(
$viewer,
@ -39,13 +38,14 @@ final class PhabricatorAuthSSHKeyDeleteController
$name = phutil_tag('strong', array(), $key->getName());
return $this->newDialog()
->setTitle(pht('Really delete SSH Public Key?'))
->setTitle(pht('Deactivate SSH Public Key'))
->appendParagraph(
pht(
'The key "%s" will be permanently deleted, and you will not longer '.
'be able to use the corresponding private key to authenticate.',
'The key "%s" will be permanently deactivated, and you will no '.
'longer be able to use the corresponding private key to '.
'authenticate.',
$name))
->addSubmitButton(pht('Delete Public Key'))
->addSubmitButton(pht('Deactivate Public Key'))
->addCancelButton($cancel_uri);
}

View file

@ -11,7 +11,6 @@ final class PhabricatorAuthSSHKeyEditController
$key = id(new PhabricatorAuthSSHKeyQuery())
->setViewer($viewer)
->withIDs(array($id))
->withIsActive(true)
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
@ -97,7 +96,7 @@ final class PhabricatorAuthSSHKeyEditController
if (!$errors) {
try {
$key->save();
return id(new AphrontRedirectResponse())->setURI($cancel_uri);
return id(new AphrontRedirectResponse())->setURI($key->getURI());
} catch (Exception $ex) {
$e_key = pht('Duplicate');
$errors[] = pht(

View file

@ -0,0 +1,25 @@
<?php
final class PhabricatorAuthSSHKeyListController
extends PhabricatorAuthSSHKeyController {
public function shouldAllowPublic() {
return true;
}
public function handleRequest(AphrontRequest $request) {
$object_phid = $request->getURIData('forPHID');
$object = $this->loadSSHKeyObject($object_phid, false);
if (!$object) {
return new Aphront404Response();
}
$engine = id(new PhabricatorAuthSSHKeySearchEngine())
->setSSHKeyObject($object);
return id($engine)
->setController($this)
->buildResponse();
}
}

View file

@ -0,0 +1,123 @@
<?php
final class PhabricatorAuthSSHKeyViewController
extends PhabricatorAuthSSHKeyController {
public function shouldAllowPublic() {
return true;
}
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$id = $request->getURIData('id');
$ssh_key = id(new PhabricatorAuthSSHKeyQuery())
->setViewer($viewer)
->withIDs(array($id))
->executeOne();
if (!$ssh_key) {
return new Aphront404Response();
}
$this->setSSHKeyObject($ssh_key->getObject());
$title = pht('SSH Key %d', $ssh_key->getID());
$curtain = $this->buildCurtain($ssh_key);
$details = $this->buildPropertySection($ssh_key);
$header = id(new PHUIHeaderView())
->setUser($viewer)
->setHeader($ssh_key->getName())
->setHeaderIcon('fa-key');
if ($ssh_key->getIsActive()) {
$header->setStatus('fa-check', 'bluegrey', pht('Active'));
} else {
$header->setStatus('fa-ban', 'dark', pht('Deactivated'));
}
$header->addActionLink(
id(new PHUIButtonView())
->setTag('a')
->setText(pht('View Active Keys'))
->setHref($ssh_key->getObject()->getSSHPublicKeyManagementURI($viewer))
->setIcon('fa-list-ul'));
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb($title);
$crumbs->setBorder(true);
// TODO: This doesn't exist yet, build it.
// $timeline = $this->buildTransactionTimeline(
// $ssh_key,
// new PhabricatorAuthSSHKeyTransactionQuery());
// $timeline->setShouldTerminate(true);
$timeline = null;
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setCurtain($curtain)
->setMainColumn(
array(
$details,
$timeline,
));
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->appendChild($view);
}
private function buildCurtain(PhabricatorAuthSSHKey $ssh_key) {
$viewer = $this->getViewer();
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$ssh_key,
PhabricatorPolicyCapability::CAN_EDIT);
$id = $ssh_key->getID();
$edit_uri = $this->getApplicationURI("sshkey/edit/{$id}/");
$deactivate_uri = $this->getApplicationURI("sshkey/deactivate/{$id}/");
$curtain = $this->newCurtainView($ssh_key);
$curtain->addAction(
id(new PhabricatorActionView())
->setIcon('fa-pencil')
->setName(pht('Edit SSH Key'))
->setHref($edit_uri)
->setWorkflow(true)
->setDisabled(!$can_edit));
$curtain->addAction(
id(new PhabricatorActionView())
->setIcon('fa-times')
->setName(pht('Deactivate SSH Key'))
->setHref($deactivate_uri)
->setWorkflow(true)
->setDisabled(!$can_edit));
return $curtain;
}
private function buildPropertySection(
PhabricatorAuthSSHKey $ssh_key) {
$viewer = $this->getViewer();
$properties = id(new PHUIPropertyListView())
->setUser($viewer);
$properties->addProperty(pht('SSH Key Type'), $ssh_key->getKeyType());
return id(new PHUIObjectBoxView())
->setHeaderText(pht('Details'))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->appendChild($properties);
}
}

View file

@ -34,7 +34,7 @@ final class PhabricatorAuthSSHKeyPHIDType
$handle->setName(pht('SSH Key %d', $key->getID()));
if (!$key->getIsActive()) {
$handle->setClosed(pht('Inactive'));
$handle->setStatus(PhabricatorObjectHandle::STATUS_CLOSED);
}
}
}

View file

@ -0,0 +1,105 @@
<?php
final class PhabricatorAuthSSHKeySearchEngine
extends PhabricatorApplicationSearchEngine {
private $sshKeyObject;
public function setSSHKeyObject(PhabricatorSSHPublicKeyInterface $object) {
$this->sshKeyObject = $object;
return $this;
}
public function getSSHKeyObject() {
return $this->sshKeyObject;
}
public function canUseInPanelContext() {
return false;
}
public function getResultTypeDescription() {
return pht('SSH Keys');
}
public function getApplicationClassName() {
return 'PhabricatorAuthApplication';
}
public function newQuery() {
$object = $this->getSSHKeyObject();
$object_phid = $object->getPHID();
return id(new PhabricatorAuthSSHKeyQuery())
->withObjectPHIDs(array($object_phid));
}
protected function buildQueryFromParameters(array $map) {
$query = $this->newQuery();
return $query;
}
protected function buildCustomSearchFields() {
return array();
}
protected function getURI($path) {
$object = $this->getSSHKeyObject();
$object_phid = $object->getPHID();
return "/auth/sshkey/for/{$object_phid}/{$path}";
}
protected function getBuiltinQueryNames() {
$names = array(
'all' => pht('All Keys'),
);
return $names;
}
public function buildSavedQueryFromBuiltin($query_key) {
$query = $this->newSavedQuery();
$query->setQueryKey($query_key);
switch ($query_key) {
case 'all':
return $query;
}
return parent::buildSavedQueryFromBuiltin($query_key);
}
protected function renderResultList(
array $keys,
PhabricatorSavedQuery $query,
array $handles) {
assert_instances_of($keys, 'PhabricatorAuthSSHKey');
$viewer = $this->requireViewer();
$list = new PHUIObjectItemListView();
$list->setUser($viewer);
foreach ($keys as $key) {
$item = id(new PHUIObjectItemView())
->setObjectName(pht('SSH Key %d', $key->getID()))
->setHeader($key->getName())
->setHref($key->getURI());
if (!$key->getIsActive()) {
$item->setDisabled(true);
}
$list->addItem($item);
}
$result = new PhabricatorApplicationSearchResultView();
$result->setObjectList($list);
$result->setNoDataString(pht('No matching SSH keys.'));
return $result;
}
}

View file

@ -96,6 +96,11 @@ final class PhabricatorAuthSSHKey
PhabricatorAuthSSHKeyPHIDType::TYPECONST);
}
public function getURI() {
$id = $this->getID();
return "/auth/sshkey/view/{$id}/";
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
@ -107,14 +112,29 @@ final class PhabricatorAuthSSHKey
}
public function getPolicy($capability) {
if (!$this->getIsActive()) {
if ($capability == PhabricatorPolicyCapability::CAN_EDIT) {
return PhabricatorPolicies::POLICY_NOONE;
}
}
return $this->getObject()->getPolicy($capability);
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
if (!$this->getIsActive()) {
return false;
}
return $this->getObject()->hasAutomaticCapability($capability, $viewer);
}
public function describeAutomaticCapability($capability) {
if (!$this->getIsACtive()) {
return pht(
'Deactivated SSH keys can not be edited or reactivated.');
}
return pht(
'SSH keys inherit the policies of the user or object they authenticate.');
}

View file

@ -8,6 +8,58 @@ final class PhabricatorAuthSSHKeyTableView extends AphrontView {
private $showTrusted;
private $showID;
public static function newKeyActionsMenu(
PhabricatorUser $viewer,
PhabricatorSSHPublicKeyInterface $object) {
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$object,
PhabricatorPolicyCapability::CAN_EDIT);
try {
PhabricatorSSHKeyGenerator::assertCanGenerateKeypair();
$can_generate = true;
} catch (Exception $ex) {
$can_generate = false;
}
$object_phid = $object->getPHID();
$generate_uri = "/auth/sshkey/generate/?objectPHID={$object_phid}";
$upload_uri = "/auth/sshkey/upload/?objectPHID={$object_phid}";
$view_uri = "/auth/sshkey/for/{$object_phid}/";
$action_view = id(new PhabricatorActionListView())
->setUser($viewer)
->addAction(
id(new PhabricatorActionView())
->setHref($upload_uri)
->setWorkflow(true)
->setDisabled(!$can_edit)
->setName(pht('Upload Public Key'))
->setIcon('fa-upload'))
->addAction(
id(new PhabricatorActionView())
->setHref($generate_uri)
->setWorkflow(true)
->setDisabled(!$can_edit || !$can_generate)
->setName(pht('Generate Keypair'))
->setIcon('fa-lock'))
->addAction(
id(new PhabricatorActionView())
->setHref($view_uri)
->setName(pht('View History'))
->setIcon('fa-list-ul'));
return id(new PHUIButtonView())
->setTag('a')
->setText(pht('SSH Key Actions'))
->setHref('#')
->setIcon('fa-gear')
->setDropdownMenu($action_view);
}
public function setNoDataString($no_data_string) {
$this->noDataString = $no_data_string;
return $this;
@ -38,12 +90,6 @@ final class PhabricatorAuthSSHKeyTableView extends AphrontView {
$keys = $this->keys;
$viewer = $this->getUser();
if ($this->canEdit) {
$delete_class = 'small grey button';
} else {
$delete_class = 'small grey button disabled';
}
$trusted_icon = id(new PHUIIconView())
->setIcon('fa-star blue');
$untrusted_icon = id(new PHUIIconView())
@ -56,22 +102,13 @@ final class PhabricatorAuthSSHKeyTableView extends AphrontView {
javelin_tag(
'a',
array(
'href' => '/auth/sshkey/edit/'.$key->getID().'/',
'sigil' => 'workflow',
'href' => $key->getURI(),
),
$key->getName()),
$key->getIsTrusted() ? $trusted_icon : $untrusted_icon,
$key->getKeyComment(),
$key->getKeyType(),
phabricator_datetime($key->getDateCreated(), $viewer),
javelin_tag(
'a',
array(
'href' => '/auth/sshkey/delete/'.$key->getID().'/',
'class' => $delete_class,
'sigil' => 'workflow',
),
pht('Delete')),
);
}
@ -85,7 +122,6 @@ final class PhabricatorAuthSSHKeyTableView extends AphrontView {
pht('Comment'),
pht('Type'),
pht('Added'),
null,
))
->setColumnVisibility(
array(
@ -101,7 +137,6 @@ final class PhabricatorAuthSSHKeyTableView extends AphrontView {
'',
'',
'right',
'action',
));
return $table;

View file

@ -98,8 +98,6 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject {
return $this->navigationItems;
}
public function canUseInPanelContext() {
return true;
}

View file

@ -45,31 +45,12 @@ final class PhabricatorSSHKeysSettingsPanel extends PhabricatorSettingsPanel {
$panel = new PHUIObjectBoxView();
$header = new PHUIHeaderView();
$upload_button = id(new PHUIButtonView())
->setText(pht('Upload Public Key'))
->setHref('/auth/sshkey/upload/?objectPHID='.$user->getPHID())
->setWorkflow(true)
->setTag('a')
->setIcon('fa-upload');
try {
PhabricatorSSHKeyGenerator::assertCanGenerateKeypair();
$can_generate = true;
} catch (Exception $ex) {
$can_generate = false;
}
$generate_button = id(new PHUIButtonView())
->setText(pht('Generate Keypair'))
->setHref('/auth/sshkey/generate/?objectPHID='.$user->getPHID())
->setTag('a')
->setWorkflow(true)
->setDisabled(!$can_generate)
->setIcon('fa-lock');
$ssh_actions = PhabricatorAuthSSHKeyTableView::newKeyActionsMenu(
$viewer,
$user);
$header->setHeader(pht('SSH Public Keys'));
$header->addActionLink($generate_button);
$header->addActionLink($upload_button);
$header->addActionLink($ssh_actions);
$panel->setHeader($header);
$panel->setTable($table);