Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F14475977
D8513.id20194.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
14 KB
Referenced Files
None
Subscribers
None
D8513.id20194.diff
View Options
diff --git a/resources/celerity/map.php b/resources/celerity/map.php
--- a/resources/celerity/map.php
+++ b/resources/celerity/map.php
@@ -7,14 +7,14 @@
return array(
'names' =>
array(
- 'core.pkg.css' => 'c650ab0d',
+ 'core.pkg.css' => '4b8e980c',
'core.pkg.js' => '264721e1',
'darkconsole.pkg.js' => 'ca8671ce',
- 'differential.pkg.css' => 'd1b3a605',
+ 'differential.pkg.css' => 'cb97e095',
'differential.pkg.js' => '11a5b750',
'diffusion.pkg.css' => '3783278d',
'diffusion.pkg.js' => '5b4010f4',
- 'javelin.pkg.js' => '5b0f988e',
+ 'javelin.pkg.js' => '65fa3049',
'maniphest.pkg.css' => 'f1887d71',
'maniphest.pkg.js' => '2fe8af22',
'rsrc/css/aphront/aphront-bars.css' => '231ac33c',
@@ -31,7 +31,7 @@
'rsrc/css/aphront/panel-view.css' => '5846dfa2',
'rsrc/css/aphront/phabricator-nav-view.css' => '80e60fc1',
'rsrc/css/aphront/request-failure-view.css' => 'da14df31',
- 'rsrc/css/aphront/table-view.css' => '92a719ca',
+ 'rsrc/css/aphront/table-view.css' => 'de599000',
'rsrc/css/aphront/tokenizer.css' => '36903077',
'rsrc/css/aphront/tooltip.css' => '9c90229d',
'rsrc/css/aphront/transaction.css' => 'ce491938',
@@ -60,7 +60,7 @@
'rsrc/css/application/differential/local-commits-view.css' => '19649019',
'rsrc/css/application/differential/results-table.css' => '239924f9',
'rsrc/css/application/differential/revision-comment.css' => '48186045',
- 'rsrc/css/application/differential/revision-history.css' => 'f37aee8f',
+ 'rsrc/css/application/differential/revision-history.css' => '0e8eb855',
'rsrc/css/application/differential/revision-list.css' => 'f3c47d33',
'rsrc/css/application/differential/table-of-contents.css' => '19566f76',
'rsrc/css/application/diffusion/commit-view.css' => '92d1e8f9',
@@ -208,7 +208,7 @@
'rsrc/externals/javelin/lib/Resource.js' => '356de121',
'rsrc/externals/javelin/lib/URI.js' => 'd9a9b862',
'rsrc/externals/javelin/lib/Vector.js' => '403a3dce',
- 'rsrc/externals/javelin/lib/Workflow.js' => 'd16edeae',
+ 'rsrc/externals/javelin/lib/Workflow.js' => 'f28bf201',
'rsrc/externals/javelin/lib/__tests__/Cookie.js' => '5ed109e8',
'rsrc/externals/javelin/lib/__tests__/DOM.js' => 'c984504b',
'rsrc/externals/javelin/lib/__tests__/JSON.js' => '2295d074',
@@ -494,7 +494,7 @@
'aphront-pager-view-css' => '2e3539af',
'aphront-panel-view-css' => '5846dfa2',
'aphront-request-failure-view-css' => 'da14df31',
- 'aphront-table-view-css' => '92a719ca',
+ 'aphront-table-view-css' => 'de599000',
'aphront-tokenizer-control-css' => '36903077',
'aphront-tooltip-css' => '9c90229d',
'aphront-two-column-view-css' => '16ab3ad2',
@@ -513,7 +513,7 @@
'differential-results-table-css' => '239924f9',
'differential-revision-add-comment-css' => 'c478bcaa',
'differential-revision-comment-css' => '48186045',
- 'differential-revision-history-css' => 'f37aee8f',
+ 'differential-revision-history-css' => '0e8eb855',
'differential-revision-list-css' => 'f3c47d33',
'differential-table-of-contents-css' => '19566f76',
'diffusion-commit-view-css' => '92d1e8f9',
@@ -663,7 +663,7 @@
'javelin-view-interpreter' => '0c33c1a0',
'javelin-view-renderer' => '6c2b09a2',
'javelin-view-visitor' => 'efe49472',
- 'javelin-workflow' => 'd16edeae',
+ 'javelin-workflow' => 'f28bf201',
'legalpad-document-css' => 'cd275275',
'lightbox-attachment-css' => '7acac05d',
'maniphest-batch-editor' => '8f380ebc',
@@ -1742,17 +1742,6 @@
4 => 'javelin-fx',
5 => 'javelin-util',
),
- 'd16edeae' =>
- array(
- 0 => 'javelin-stratcom',
- 1 => 'javelin-request',
- 2 => 'javelin-dom',
- 3 => 'javelin-vector',
- 4 => 'javelin-install',
- 5 => 'javelin-util',
- 6 => 'javelin-mask',
- 7 => 'javelin-uri',
- ),
'd254d646' =>
array(
0 => 'javelin-util',
@@ -1880,6 +1869,17 @@
4 => 'javelin-request',
5 => 'javelin-workflow',
),
+ 'f28bf201' =>
+ array(
+ 0 => 'javelin-stratcom',
+ 1 => 'javelin-request',
+ 2 => 'javelin-dom',
+ 3 => 'javelin-vector',
+ 4 => 'javelin-install',
+ 5 => 'javelin-util',
+ 6 => 'javelin-mask',
+ 7 => 'javelin-uri',
+ ),
'f42bb8c6' =>
array(
0 => 'javelin-stratcom',
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
@@ -1958,6 +1958,7 @@
'PhabricatorRepositoryVCSPassword' => 'applications/repository/storage/PhabricatorRepositoryVCSPassword.php',
'PhabricatorS3FileStorageEngine' => 'applications/files/engine/PhabricatorS3FileStorageEngine.php',
'PhabricatorSQLPatchList' => 'infrastructure/storage/patch/PhabricatorSQLPatchList.php',
+ 'PhabricatorSSHKeyGenerator' => 'infrastructure/util/PhabricatorSSHKeyGenerator.php',
'PhabricatorSSHLog' => 'infrastructure/log/PhabricatorSSHLog.php',
'PhabricatorSSHPassthruCommand' => 'infrastructure/ssh/PhabricatorSSHPassthruCommand.php',
'PhabricatorSSHWorkflow' => 'infrastructure/ssh/PhabricatorSSHWorkflow.php',
@@ -4748,6 +4749,7 @@
'PhabricatorRepositoryURITestCase' => 'PhabricatorTestCase',
'PhabricatorRepositoryVCSPassword' => 'PhabricatorRepositoryDAO',
'PhabricatorS3FileStorageEngine' => 'PhabricatorFileStorageEngine',
+ 'PhabricatorSSHKeyGenerator' => 'Phobject',
'PhabricatorSSHLog' => 'Phobject',
'PhabricatorSSHPassthruCommand' => 'Phobject',
'PhabricatorSSHWorkflow' => 'PhabricatorManagementWorkflow',
diff --git a/src/applications/settings/panel/PhabricatorSettingsPanelSSHKeys.php b/src/applications/settings/panel/PhabricatorSettingsPanelSSHKeys.php
--- a/src/applications/settings/panel/PhabricatorSettingsPanelSSHKeys.php
+++ b/src/applications/settings/panel/PhabricatorSettingsPanelSSHKeys.php
@@ -23,6 +23,11 @@
$user = $request->getUser();
+ $generate = $request->getStr('generate');
+ if ($generate) {
+ return $this->processGenerate($request);
+ }
+
$edit = $request->getStr('edit');
$delete = $request->getStr('delete');
if (!$edit && !$delete) {
@@ -220,18 +225,36 @@
$panel = new PHUIObjectBoxView();
$header = new PHUIHeaderView();
- $icon = id(new PHUIIconView())
- ->setSpriteSheet(PHUIIconView::SPRITE_ICONS)
- ->setSpriteIcon('new');
+ $upload_icon = id(new PHUIIconView())
+ ->setSpriteSheet(PHUIIconView::SPRITE_ICONS)
+ ->setSpriteIcon('upload');
+ $upload_button = id(new PHUIButtonView())
+ ->setText(pht('Upload Public Key'))
+ ->setHref($this->getPanelURI('?edit=true'))
+ ->setTag('a')
+ ->setIcon($upload_icon);
+
+ try {
+ PhabricatorSSHKeyGenerator::assertCanGenerateKeypair();
+ $can_generate = true;
+ } catch (Exception $ex) {
+ $can_generate = false;
+ }
- $button = new PHUIButtonView();
- $button->setText(pht('Add New Public Key'));
- $button->setHref($this->getPanelURI('?edit=true'));
- $button->setTag('a');
- $button->setIcon($icon);
+ $generate_icon = id(new PHUIIconView())
+ ->setSpriteSheet(PHUIIconView::SPRITE_ICONS)
+ ->setSpriteIcon('lock');
+ $generate_button = id(new PHUIButtonView())
+ ->setText(pht('Generate Keypair'))
+ ->setHref($this->getPanelURI('?generate=true'))
+ ->setTag('a')
+ ->setWorkflow(true)
+ ->setDisabled(!$can_generate)
+ ->setIcon($generate_icon);
$header->setHeader(pht('SSH Public Keys'));
- $header->addActionLink($button);
+ $header->addActionLink($generate_button);
+ $header->addActionLink($upload_button);
$panel->setHeader($header);
$panel->appendChild($table);
@@ -268,4 +291,84 @@
->setDialog($dialog);
}
+ private function processGenerate(
+ AphrontRequest $request) {
+ $viewer = $request->getUser();
+
+ if ($request->isFormPost()) {
+ $keys = PhabricatorSSHKeyGenerator::generateKeypair();
+ list($public_key, $private_key) = $keys;
+
+ $file = PhabricatorFile::buildFromFileDataOrHash(
+ $private_key,
+ array(
+ 'name' => 'id_rsa_phabricator.key',
+ 'ttl' => time() + (60 * 10),
+ 'viewPolicy' => PhabricatorPolicies::POLICY_NOONE,
+ ));
+
+ $key = id(new PhabricatorUserSSHKey())
+ ->setUserPHID($viewer->getPHID())
+ ->setName('id_rsa_phabricator')
+ ->setKeyType('rsa')
+ ->setKeyBody($public_key)
+ ->setKeyHash(md5($public_key))
+ ->setKeyComment(pht('Generated Key'))
+ ->save();
+
+ // NOTE: We're disabling workflow on submit so the download works. We're
+ // disabling workflow on cancel so the page reloads, showing the new
+ // key.
+
+ $dialog = id(new AphrontDialogView())
+ ->setTitle(pht('Download Private Key'))
+ ->setUser($viewer)
+ ->setDisableWorkflowOnCancel(true)
+ ->setDisableWorkflowOnSubmit(true)
+ ->setSubmitURI($file->getDownloadURI())
+ ->appendParagraph(
+ pht(
+ 'Successfully generated a new keypair.'))
+ ->appendParagraph(
+ pht(
+ 'The public key has been associated with your Phabricator '.
+ 'account. Use the button below to download the private key.'))
+ ->appendParagraph(
+ pht(
+ 'After you download the private key, it will be destroyed. '.
+ 'You will not be able to retrieve it if you lose your copy.'))
+ ->addSubmitButton(pht('Download Private Key'))
+ ->addCancelButton($this->getPanelURI(), pht('Done'));
+
+ return id(new AphrontDialogResponse())
+ ->setDialog($dialog);
+ }
+
+ $dialog = id(new AphrontDialogView())
+ ->setUser($viewer)
+ ->addCancelButton($this->getPanelURI());
+
+ try {
+ PhabricatorSSHKeyGenerator::assertCanGenerateKeypair();
+ $dialog
+ ->addHiddenInput('generate', true)
+ ->setTitle(pht('Generate New Keypair'))
+ ->appendParagraph(
+ pht(
+ "This will generate an SSH keypair, associate the public key ".
+ "with your account, and let you download the private key."))
+ ->appendParagraph(
+ pht(
+ "Phabricator will not retain a copy of the private key."))
+ ->addSubmitButton(pht('Generate Keypair'));
+ } catch (Exception $ex) {
+ $dialog
+ ->setTitle(pht('Unable to Generate Keys'))
+ ->appendParagraph($ex->getMessage());
+ }
+
+ return id(new AphrontDialogResponse())
+ ->setDialog($dialog);
+ }
+
}
diff --git a/src/infrastructure/util/PhabricatorSSHKeyGenerator.php b/src/infrastructure/util/PhabricatorSSHKeyGenerator.php
new file mode 100644
--- /dev/null
+++ b/src/infrastructure/util/PhabricatorSSHKeyGenerator.php
@@ -0,0 +1,32 @@
+<?php
+
+final class PhabricatorSSHKeyGenerator extends Phobject {
+
+ public static function assertCanGenerateKeypair() {
+ $binary = 'ssh-keygen';
+ if (!Filesystem::resolveBinary($binary)) {
+ throw new Exception(
+ pht(
+ 'Can not generate keys: unable to find "%s" in PATH!',
+ $binary));
+ }
+ }
+
+ public static function generateKeypair() {
+ self::assertCanGenerateKeypair();
+
+ $tempfile = new TempFile();
+ $keyfile = dirname($tempfile).DIRECTORY_SEPARATOR.'keytext';
+
+ execx(
+ 'ssh-keygen -t rsa -N %s -f %s',
+ '',
+ $keyfile);
+
+ $public_key = Filesystem::readFile($keyfile.'.pub');
+ $private_key = Filesystem::readFile($keyfile);
+
+ return array($public_key, $private_key);
+ }
+
+}
diff --git a/src/view/AphrontDialogView.php b/src/view/AphrontDialogView.php
--- a/src/view/AphrontDialogView.php
+++ b/src/view/AphrontDialogView.php
@@ -15,7 +15,13 @@
private $footers = array();
private $isStandalone;
private $method = 'POST';
+ private $disableWorkflowOnSubmit;
+ private $disableWorkflowOnCancel;
+ private $width = 'default';
+ const WIDTH_DEFAULT = 'default';
+ const WIDTH_FORM = 'form';
+ const WIDTH_FULL = 'full';
public function setMethod($method) {
$this->method = $method;
@@ -31,11 +37,6 @@
return $this->isStandalone;
}
- private $width = 'default';
- const WIDTH_DEFAULT = 'default';
- const WIDTH_FORM = 'form';
- const WIDTH_FULL = 'full';
-
public function setSubmitURI($uri) {
$this->submitURI = $uri;
return $this;
@@ -121,22 +122,51 @@
$paragraph));
}
+ public function setDisableWorkflowOnSubmit($disable_workflow_on_submit) {
+ $this->disableWorkflowOnSubmit = $disable_workflow_on_submit;
+ return $this;
+ }
+
+ public function getDisableWorkflowOnSubmit() {
+ return $this->disableWorkflowOnSubmit;
+ }
+
+ public function setDisableWorkflowOnCancel($disable_workflow_on_cancel) {
+ $this->disableWorkflowOnCancel = $disable_workflow_on_cancel;
+ return $this;
+ }
+
+ public function getDisableWorkflowOnCancel() {
+ return $this->disableWorkflowOnCancel;
+ }
+
final public function render() {
require_celerity_resource('aphront-dialog-view-css');
$buttons = array();
if ($this->submitButton) {
+ $meta = array();
+ if ($this->disableWorkflowOnSubmit) {
+ $meta['disableWorkflow'] = true;
+ }
+
$buttons[] = javelin_tag(
'button',
array(
'name' => '__submit__',
'sigil' => '__default__',
'type' => 'submit',
+ 'meta' => $meta,
),
$this->submitButton);
}
if ($this->cancelURI) {
+ $meta = array();
+ if ($this->disableWorkflowOnCancel) {
+ $meta['disableWorkflow'] = true;
+ }
+
$buttons[] = javelin_tag(
'a',
array(
@@ -144,6 +174,7 @@
'class' => 'button grey',
'name' => '__cancel__',
'sigil' => 'jx-workflow-button',
+ 'meta' => $meta,
),
$this->cancelText);
}
diff --git a/webroot/rsrc/externals/javelin/lib/Workflow.js b/webroot/rsrc/externals/javelin/lib/Workflow.js
--- a/webroot/rsrc/externals/javelin/lib/Workflow.js
+++ b/webroot/rsrc/externals/javelin/lib/Workflow.js
@@ -88,14 +88,21 @@
return;
}
- event.prevent();
-
// Get the button (which is sometimes actually another tag, like an <a />)
// which triggered the event. In particular, this makes sure we get the
// right node if there is a <button> with an <img /> inside it or
// or something similar.
var t = event.getNode('jx-workflow-button') ||
event.getNode('tag:button');
+
+ // If this button disables workflow (normally, because it is a file
+ // download button) let the event through without modification.
+ if (JX.Stratcom.getData(t).disableWorkflow) {
+ return;
+ }
+
+ event.prevent();
+
if (t.name == '__cancel__' || t.name == '__close__') {
JX.Workflow._pop();
} else {
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sat, Dec 28, 10:20 PM (6 h, 30 m)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6941269
Default Alt Text
D8513.id20194.diff (14 KB)
Attached To
Mode
D8513: Add a "Generate Keypair" option on the SSH Keys panel
Attached
Detach File
Event Timeline
Log In to Comment