diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1876,12 +1876,15 @@ '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 @@ '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', diff --git a/src/applications/almanac/controller/AlmanacDeviceViewController.php b/src/applications/almanac/controller/AlmanacDeviceViewController.php --- a/src/applications/almanac/controller/AlmanacDeviceViewController.php +++ b/src/applications/almanac/controller/AlmanacDeviceViewController.php @@ -157,38 +157,13 @@ ->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) diff --git a/src/applications/auth/application/PhabricatorAuthApplication.php b/src/applications/auth/application/PhabricatorAuthApplication.php --- a/src/applications/auth/application/PhabricatorAuthApplication.php +++ b/src/applications/auth/application/PhabricatorAuthApplication.php @@ -75,10 +75,14 @@ 'multifactor/' => 'PhabricatorAuthNeedsMultiFactorController', 'sshkey/' => array( + $this->getQueryRoutePattern('for/(?P[^/]+)/') + => 'PhabricatorAuthSSHKeyListController', 'generate/' => 'PhabricatorAuthSSHKeyGenerateController', 'upload/' => 'PhabricatorAuthSSHKeyEditController', 'edit/(?P\d+)/' => 'PhabricatorAuthSSHKeyEditController', - 'delete/(?P\d+)/' => 'PhabricatorAuthSSHKeyDeleteController', + 'deactivate/(?P\d+)/' + => 'PhabricatorAuthSSHKeyDeactivateController', + 'view/(?P\d+)/' => 'PhabricatorAuthSSHKeyViewController', ), ), diff --git a/src/applications/auth/controller/PhabricatorAuthSSHKeyController.php b/src/applications/auth/controller/PhabricatorAuthSSHKeyController.php --- a/src/applications/auth/controller/PhabricatorAuthSSHKeyController.php +++ b/src/applications/auth/controller/PhabricatorAuthSSHKeyController.php @@ -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 @@ 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; + } + } diff --git a/src/applications/auth/controller/PhabricatorAuthSSHKeyDeleteController.php b/src/applications/auth/controller/PhabricatorAuthSSHKeyDeactivateController.php rename from src/applications/auth/controller/PhabricatorAuthSSHKeyDeleteController.php rename to src/applications/auth/controller/PhabricatorAuthSSHKeyDeactivateController.php --- a/src/applications/auth/controller/PhabricatorAuthSSHKeyDeleteController.php +++ b/src/applications/auth/controller/PhabricatorAuthSSHKeyDeactivateController.php @@ -1,6 +1,6 @@ setViewer($viewer) ->withIDs(array($request->getURIData('id'))) - ->withIsActive(true) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, @@ -20,7 +19,7 @@ return new Aphront404Response(); } - $cancel_uri = $key->getObject()->getSSHPublicKeyManagementURI($viewer); + $cancel_uri = $key->getURI(); $token = id(new PhabricatorAuthSessionEngine())->requireHighSecuritySession( $viewer, @@ -39,13 +38,14 @@ $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); } diff --git a/src/applications/auth/controller/PhabricatorAuthSSHKeyEditController.php b/src/applications/auth/controller/PhabricatorAuthSSHKeyEditController.php --- a/src/applications/auth/controller/PhabricatorAuthSSHKeyEditController.php +++ b/src/applications/auth/controller/PhabricatorAuthSSHKeyEditController.php @@ -11,7 +11,6 @@ $key = id(new PhabricatorAuthSSHKeyQuery()) ->setViewer($viewer) ->withIDs(array($id)) - ->withIsActive(true) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, @@ -97,7 +96,7 @@ 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( diff --git a/src/applications/auth/controller/PhabricatorAuthSSHKeyListController.php b/src/applications/auth/controller/PhabricatorAuthSSHKeyListController.php new file mode 100644 --- /dev/null +++ b/src/applications/auth/controller/PhabricatorAuthSSHKeyListController.php @@ -0,0 +1,25 @@ +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(); + } + +} diff --git a/src/applications/auth/controller/PhabricatorAuthSSHKeyViewController.php b/src/applications/auth/controller/PhabricatorAuthSSHKeyViewController.php new file mode 100644 --- /dev/null +++ b/src/applications/auth/controller/PhabricatorAuthSSHKeyViewController.php @@ -0,0 +1,123 @@ +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); + } + +} diff --git a/src/applications/auth/phid/PhabricatorAuthSSHKeyPHIDType.php b/src/applications/auth/phid/PhabricatorAuthSSHKeyPHIDType.php --- a/src/applications/auth/phid/PhabricatorAuthSSHKeyPHIDType.php +++ b/src/applications/auth/phid/PhabricatorAuthSSHKeyPHIDType.php @@ -34,7 +34,7 @@ $handle->setName(pht('SSH Key %d', $key->getID())); if (!$key->getIsActive()) { - $handle->setClosed(pht('Inactive')); + $handle->setStatus(PhabricatorObjectHandle::STATUS_CLOSED); } } } diff --git a/src/applications/auth/query/PhabricatorAuthSSHKeySearchEngine.php b/src/applications/auth/query/PhabricatorAuthSSHKeySearchEngine.php new file mode 100644 --- /dev/null +++ b/src/applications/auth/query/PhabricatorAuthSSHKeySearchEngine.php @@ -0,0 +1,105 @@ +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; + } +} diff --git a/src/applications/auth/storage/PhabricatorAuthSSHKey.php b/src/applications/auth/storage/PhabricatorAuthSSHKey.php --- a/src/applications/auth/storage/PhabricatorAuthSSHKey.php +++ b/src/applications/auth/storage/PhabricatorAuthSSHKey.php @@ -96,6 +96,11 @@ PhabricatorAuthSSHKeyPHIDType::TYPECONST); } + public function getURI() { + $id = $this->getID(); + return "/auth/sshkey/view/{$id}/"; + } + /* -( PhabricatorPolicyInterface )----------------------------------------- */ @@ -107,14 +112,29 @@ } 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.'); } diff --git a/src/applications/auth/view/PhabricatorAuthSSHKeyTableView.php b/src/applications/auth/view/PhabricatorAuthSSHKeyTableView.php --- a/src/applications/auth/view/PhabricatorAuthSSHKeyTableView.php +++ b/src/applications/auth/view/PhabricatorAuthSSHKeyTableView.php @@ -8,6 +8,58 @@ 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 @@ $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 @@ 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 @@ pht('Comment'), pht('Type'), pht('Added'), - null, )) ->setColumnVisibility( array( @@ -101,7 +137,6 @@ '', '', 'right', - 'action', )); return $table; diff --git a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php --- a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php +++ b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php @@ -98,8 +98,6 @@ return $this->navigationItems; } - - public function canUseInPanelContext() { return true; } diff --git a/src/applications/settings/panel/PhabricatorSSHKeysSettingsPanel.php b/src/applications/settings/panel/PhabricatorSSHKeysSettingsPanel.php --- a/src/applications/settings/panel/PhabricatorSSHKeysSettingsPanel.php +++ b/src/applications/settings/panel/PhabricatorSSHKeysSettingsPanel.php @@ -45,31 +45,12 @@ $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);