Page MenuHomePhabricator

D8513.id20194.diff
No OneTemporary

D8513.id20194.diff

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

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)

Event Timeline