1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-24 06:20:56 +01:00

Allow users to associate SSH Public Keys with their accounts

Summary:
With the sshd-vcs thing I hacked together, this will enable Phabricator to host
repositories without requiring users to have SSH accounts.

I also fixed "subporjects" and added an explicit ENGINE to it.

Test Plan: Created, edited and deleted public keys. Attempted to add the same
public key twice. Attempted to add invalid and unnamed public keys.
Reviewed By: aran
Reviewers: jungejason, tuomaspelkonen, aran, cadamo, codeblock
CC: aran, epriestley
Differential Revision: 711
This commit is contained in:
epriestley 2011-07-22 10:17:57 -07:00
parent ffae7b19e6
commit 8df62d5352
9 changed files with 366 additions and 2 deletions

View file

@ -8,4 +8,4 @@ CREATE TABLE phabricator_project.project_subproject (
subprojectPHID varchar(64) BINARY NOT NULL, subprojectPHID varchar(64) BINARY NOT NULL,
PRIMARY KEY (subprojectPHID, projectPHID), PRIMARY KEY (subprojectPHID, projectPHID),
UNIQUE KEY (projectPHID, subprojectPHID) UNIQUE KEY (projectPHID, subprojectPHID)
); ) ENGINE=InnoDB;

View file

@ -0,0 +1,12 @@
CREATE TABLE phabricator_user.user_sshkey (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
userPHID varchar(64) BINARY NOT NULL,
key (userPHID),
name varchar(255),
keyType varchar(255),
keyBody varchar(32768) BINARY,
unique key (keyBody(512)),
keyComment varchar(255),
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL
) ENGINE=InnoDB;

View file

@ -576,6 +576,8 @@ phutil_register_library_map(array(
'PhabricatorUserOAuthSettingsPanelController' => 'applications/people/controller/settings/panels/oauth', 'PhabricatorUserOAuthSettingsPanelController' => 'applications/people/controller/settings/panels/oauth',
'PhabricatorUserPreferences' => 'applications/people/storage/preferences', 'PhabricatorUserPreferences' => 'applications/people/storage/preferences',
'PhabricatorUserProfile' => 'applications/people/storage/profile', 'PhabricatorUserProfile' => 'applications/people/storage/profile',
'PhabricatorUserSSHKey' => 'applications/people/storage/usersshkey',
'PhabricatorUserSSHKeysSettingsPanelController' => 'applications/people/controller/settings/panels/sshkeys',
'PhabricatorUserSettingsController' => 'applications/people/controller/settings', 'PhabricatorUserSettingsController' => 'applications/people/controller/settings',
'PhabricatorUserSettingsPanelController' => 'applications/people/controller/settings/panels/base', 'PhabricatorUserSettingsPanelController' => 'applications/people/controller/settings/panels/base',
'PhabricatorWorker' => 'infrastructure/daemon/workers/worker', 'PhabricatorWorker' => 'infrastructure/daemon/workers/worker',
@ -1094,6 +1096,8 @@ phutil_register_library_map(array(
'PhabricatorUserOAuthSettingsPanelController' => 'PhabricatorUserSettingsPanelController', 'PhabricatorUserOAuthSettingsPanelController' => 'PhabricatorUserSettingsPanelController',
'PhabricatorUserPreferences' => 'PhabricatorUserDAO', 'PhabricatorUserPreferences' => 'PhabricatorUserDAO',
'PhabricatorUserProfile' => 'PhabricatorUserDAO', 'PhabricatorUserProfile' => 'PhabricatorUserDAO',
'PhabricatorUserSSHKey' => 'PhabricatorUserDAO',
'PhabricatorUserSSHKeysSettingsPanelController' => 'PhabricatorUserSettingsPanelController',
'PhabricatorUserSettingsController' => 'PhabricatorPeopleController', 'PhabricatorUserSettingsController' => 'PhabricatorPeopleController',
'PhabricatorUserSettingsPanelController' => 'PhabricatorPeopleController', 'PhabricatorUserSettingsPanelController' => 'PhabricatorPeopleController',
'PhabricatorWorkerDAO' => 'PhabricatorLiskDAO', 'PhabricatorWorkerDAO' => 'PhabricatorLiskDAO',

View file

@ -34,6 +34,7 @@ class PhabricatorUserSettingsController extends PhabricatorPeopleController {
'email' => 'Email', 'email' => 'Email',
// 'password' => 'Password', // 'password' => 'Password',
'conduit' => 'Conduit Certificate', 'conduit' => 'Conduit Certificate',
'sshkeys' => 'SSH Public Keys',
); );
$oauth_providers = PhabricatorOAuthProvider::getAllProviders(); $oauth_providers = PhabricatorOAuthProvider::getAllProviders();
@ -60,6 +61,9 @@ class PhabricatorUserSettingsController extends PhabricatorPeopleController {
case 'conduit': case 'conduit':
$delegate = new PhabricatorUserConduitSettingsPanelController($request); $delegate = new PhabricatorUserConduitSettingsPanelController($request);
break; break;
case 'sshkeys':
$delegate = new PhabricatorUserSSHKeysSettingsPanelController($request);
break;
default: default:
if (empty($this->pages[$this->page])) { if (empty($this->pages[$this->page])) {
return new Aphront404Response(); return new Aphront404Response();

View file

@ -13,6 +13,7 @@ phutil_require_module('phabricator', 'applications/people/controller/settings/pa
phutil_require_module('phabricator', 'applications/people/controller/settings/panels/conduit'); phutil_require_module('phabricator', 'applications/people/controller/settings/panels/conduit');
phutil_require_module('phabricator', 'applications/people/controller/settings/panels/email'); phutil_require_module('phabricator', 'applications/people/controller/settings/panels/email');
phutil_require_module('phabricator', 'applications/people/controller/settings/panels/oauth'); phutil_require_module('phabricator', 'applications/people/controller/settings/panels/oauth');
phutil_require_module('phabricator', 'applications/people/controller/settings/panels/sshkeys');
phutil_require_module('phabricator', 'view/layout/sidenav'); phutil_require_module('phabricator', 'view/layout/sidenav');
phutil_require_module('phutil', 'markup'); phutil_require_module('phutil', 'markup');

View file

@ -0,0 +1,264 @@
<?php
/*
* Copyright 2011 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
class PhabricatorUserSSHKeysSettingsPanelController
extends PhabricatorUserSettingsPanelController {
const PANEL_BASE_URI = '/settings/page/sshkeys/';
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$edit = $request->getStr('edit');
$delete = $request->getStr('delete');
if (!$edit && !$delete) {
return $this->renderKeyListView();
}
$id = nonempty($edit, $delete);
if ($id && is_numeric($id)) {
// NOTE: Prevent editing/deleting of keys you don't own.
$key = id(new PhabricatorUserSSHKey())->loadOneWhere(
'userPHID = %s AND id = %d',
$user->getPHID(),
$id);
if (!$key) {
return new Aphront404Response();
}
} else {
$key = new PhabricatorUserSSHKey();
$key->setUserPHID($user->getPHID());
}
if ($delete) {
return $this->processDelete($key);
}
$e_name = true;
$e_key = true;
$errors = array();
$entire_key = $key->getEntireKey();
if ($request->isFormPost()) {
$key->setName($request->getStr('name'));
$entire_key = $request->getStr('key');
if (!strlen($entire_key)) {
$errors[] = 'You must provide an SSH Public Key.';
$e_key = 'Required';
} else {
$parts = str_replace("\n", '', trim($entire_key));
$parts = preg_split('/\s+/', $parts);
if (count($parts) == 2) {
$parts[] = ''; // Add an empty comment part.
} else if (count($parts) == 3) {
// This is the expected case.
} else {
if (preg_match('/private\s*key/i', $entire_key)) {
// Try to give the user a better error message if it looks like
// they uploaded a private key.
$e_key = 'Invalid';
$errors[] = 'Provide your public key, not your private key!';
} else {
$e_key = 'Invalid';
$errors[] = 'Provided public key is not properly formatted.';
}
}
if (!$errors) {
list($type, $body, $comment) = $parts;
if (!preg_match('/^ssh-dsa|ssh-rsa$/', $type)) {
$e_key = 'Invalid';
$errors[] = 'Public key should be "ssh-dsa" or "ssh-rsa".';
} else {
$key->setKeyType($type);
$key->setKeyBody($body);
$key->setKeyComment($comment);
$e_key = null;
}
}
}
if (!strlen($key->getName())) {
$errors[] = 'You must name this public key.';
$e_name = 'Required';
} else {
$e_name = null;
}
if (!$errors) {
try {
$key->save();
return id(new AphrontRedirectResponse())
->setURI(self::PANEL_BASE_URI);
} catch (AphrontQueryDuplicateKeyException $ex) {
$e_key = 'Duplicate';
$errors[] = 'This public key is already associated with a user '.
'account.';
}
}
}
$error_view = null;
if ($errors) {
$error_view = new AphrontErrorView();
$error_view->setTitle('Form Errors');
$error_view->setErrors($errors);
}
$is_new = !$key->getID();
if ($is_new) {
$header = 'Add New SSH Public Key';
$save = 'Add Key';
} else {
$header = 'Edit SSH Public Key';
$save = 'Save Changes';
}
$form = id(new AphrontFormView())
->setUser($user)
->addHiddenInput('edit', $is_new ? 'true' : $key->getID())
->appendChild(
id(new AphrontFormTextControl())
->setLabel('Name')
->setName('name')
->setValue($key->getName())
->setError($e_name))
->appendChild(
id(new AphrontFormTextAreaControl())
->setLabel('Public Key')
->setName('key')
->setValue($entire_key)
->setError($e_key))
->appendChild(
id(new AphrontFormSubmitControl())
->addCancelButton(self::PANEL_BASE_URI)
->setValue($save));
$panel = new AphrontPanelView();
$panel->setHeader($header);
$panel->setWidth(AphrontPanelView::WIDTH_FORM);
$panel->appendChild($form);
return id(new AphrontNullView())
->appendChild(
array(
$error_view,
$panel,
));
}
private function renderKeyListView() {
$request = $this->getRequest();
$user = $request->getUser();
$keys = id(new PhabricatorUserSSHKey())->loadAllWhere(
'userPHID = %s',
$user->getPHID());
$rows = array();
foreach ($keys as $key) {
$rows[] = array(
phutil_render_tag(
'a',
array(
'href' => '/settings/page/sshkeys/?edit='.$key->getID(),
),
phutil_escape_html($key->getName())),
phutil_escape_html($key->getKeyComment()),
phutil_escape_html($key->getKeyType()),
phabricator_date($key->getDateCreated(), $user),
phabricator_time($key->getDateCreated(), $user),
javelin_render_tag(
'a',
array(
'href' => '/settings/page/sshkeys/?delete='.$key->getID(),
'class' => 'small grey button',
'sigil' => 'workflow',
),
'Delete'),
);
}
$table = new AphrontTableView($rows);
$table->setNoDataString("You haven't added any SSH Public Keys.");
$table->setHeaders(
array(
'Name',
'Comment',
'Type',
'Created',
'Time',
'',
));
$table->setColumnClasses(
array(
'wide pri',
'',
'',
'',
'right',
'action',
));
$panel = new AphrontPanelView();
$panel->addButton(
phutil_render_tag(
'a',
array(
'href' => '/settings/page/sshkeys/?edit=true',
'class' => 'green button',
),
'Add New Public Key'));
$panel->setHeader('SSH Public Keys');
$panel->appendChild($table);
return $panel;
}
private function processDelete(PhabricatorUserSSHKey $key) {
$request = $this->getRequest();
$user = $request->getUser();
$name = phutil_escape_html($key->getName());
if ($request->isDialogFormPost()) {
$key->delete();
return id(new AphrontReloadResponse())
->setURI(self::PANEL_BASE_URI);
}
$dialog = id(new AphrontDialogView())
->setUser($user)
->addHiddenInput('delete', $key->getID())
->setTitle('Really delete SSH Public Key?')
->appendChild(
'<p>The key "<strong>'.$name.'</strong>" will be permanently deleted, '.
'and you will not longer be able to use the corresponding private key '.
'to authenticate.</p>')
->addSubmitButton('Delete Public Key')
->addCancelButton(self::PANEL_BASE_URI);
return id(new AphrontDialogResponse())
->setDialog($dialog);
}
}

View file

@ -0,0 +1,31 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'aphront/response/404');
phutil_require_module('phabricator', 'aphront/response/dialog');
phutil_require_module('phabricator', 'aphront/response/redirect');
phutil_require_module('phabricator', 'aphront/response/reload');
phutil_require_module('phabricator', 'applications/people/controller/settings/panels/base');
phutil_require_module('phabricator', 'applications/people/storage/usersshkey');
phutil_require_module('phabricator', 'infrastructure/javelin/markup');
phutil_require_module('phabricator', 'view/control/table');
phutil_require_module('phabricator', 'view/dialog');
phutil_require_module('phabricator', 'view/form/base');
phutil_require_module('phabricator', 'view/form/control/submit');
phutil_require_module('phabricator', 'view/form/control/text');
phutil_require_module('phabricator', 'view/form/control/textarea');
phutil_require_module('phabricator', 'view/form/error');
phutil_require_module('phabricator', 'view/layout/panel');
phutil_require_module('phabricator', 'view/null');
phutil_require_module('phabricator', 'view/utils');
phutil_require_module('phutil', 'markup');
phutil_require_module('phutil', 'utils');
phutil_require_source('PhabricatorUserSSHKeysSettingsPanelController.php');

View file

@ -0,0 +1,36 @@
<?php
/*
* Copyright 2011 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
class PhabricatorUserSSHKey extends PhabricatorUserDAO {
protected $userPHID;
protected $name;
protected $keyType;
protected $keyBody;
protected $keyComment;
public function getEntireKey() {
$parts = array(
$this->getKeyType(),
$this->getKeyBody(),
$this->getKeyComment(),
);
return trim(implode(' ', $parts));
}
}

View file

@ -0,0 +1,12 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'applications/people/storage/base');
phutil_require_source('PhabricatorUserSSHKey.php');