mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-29 10:12:41 +01:00
Add a query/policy layer on top of SSH keys for Almanac
Summary: Ref T5833. Currently, SSH keys are associated only with users, and are a bit un-modern. I want to let Almanac Devices have SSH keys so devices in a cluster can identify to one another. For example, with hosted installs, initialization will go something like this: - A request comes in for `company.phacility.com`. - A SiteSource (from D10787) makes a Conduit call to Almanac on the master install to check if `company` is a valid install and pull config if it is. - This call can be signed with an SSH key which identifies a trusted Almanac Device. In the cluster case, a web host can make an authenticated call to a repository host with similar key signing. To move toward this, put a proper Query class on top of SSH key access (this diff). In following diffs, I'll: - Rename `userPHID` to `objectPHID`. - Move this to the `auth` database. - Provide UI for device/key association. An alternative approach would be to build some kind of special token layer in Conduit, but I think that would be a lot harder to manage in the hosting case. This gives us a more direct attack on trusting requests from machines and recognizing machines as first (well, sort of second-class) actors without needing things like fake user accounts. Test Plan: - Added and removed SSH keys. - Added and removed SSH keys from a bot account. - Tried to edit an unonwned SSH key (denied). - Ran `bin/ssh-auth`, got sensible output. - Ran `bin/ssh-auth-key`, got sensible output. Reviewers: btrahan Reviewed By: btrahan Subscribers: epriestley Maniphest Tasks: T5833 Differential Revision: https://secure.phabricator.com/D10790
This commit is contained in:
parent
3ea31c92b9
commit
6f0d3b0796
7 changed files with 291 additions and 109 deletions
|
@ -4,48 +4,28 @@
|
||||||
$root = dirname(dirname(dirname(__FILE__)));
|
$root = dirname(dirname(dirname(__FILE__)));
|
||||||
require_once $root.'/scripts/__init_script__.php';
|
require_once $root.'/scripts/__init_script__.php';
|
||||||
|
|
||||||
$cert = file_get_contents('php://stdin');
|
try {
|
||||||
|
$cert = file_get_contents('php://stdin');
|
||||||
if (!$cert) {
|
$public_key = PhabricatorAuthSSHPublicKey::newFromRawKey($cert);
|
||||||
|
} catch (Exception $ex) {
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
$parts = preg_split('/\s+/', $cert);
|
$key = id(new PhabricatorAuthSSHKeyQuery())
|
||||||
if (count($parts) < 2) {
|
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||||
|
->withKeys(array($public_key))
|
||||||
|
->executeOne();
|
||||||
|
if (!$key) {
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
list($type, $body) = $parts;
|
$object = $key->getObject();
|
||||||
|
if (!($object instanceof PhabricatorUser)) {
|
||||||
$user_dao = new PhabricatorUser();
|
|
||||||
$ssh_dao = new PhabricatorUserSSHKey();
|
|
||||||
$conn_r = $user_dao->establishConnection('r');
|
|
||||||
|
|
||||||
$row = queryfx_one(
|
|
||||||
$conn_r,
|
|
||||||
'SELECT userName FROM %T u JOIN %T ssh ON u.phid = ssh.userPHID
|
|
||||||
WHERE ssh.keyType = %s AND ssh.keyBody = %s',
|
|
||||||
$user_dao->getTableName(),
|
|
||||||
$ssh_dao->getTableName(),
|
|
||||||
$type,
|
|
||||||
$body);
|
|
||||||
|
|
||||||
if (!$row) {
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
$user = idx($row, 'userName');
|
|
||||||
|
|
||||||
if (!$user) {
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!PhabricatorUser::validateUsername($user)) {
|
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
$bin = $root.'/bin/ssh-exec';
|
$bin = $root.'/bin/ssh-exec';
|
||||||
$cmd = csprintf('%s --phabricator-ssh-user %s', $bin, $user);
|
$cmd = csprintf('%s --phabricator-ssh-user %s', $bin, $object->getUsername());
|
||||||
// This is additional escaping for the SSH 'command="..."' string.
|
// This is additional escaping for the SSH 'command="..."' string.
|
||||||
$cmd = addcslashes($cmd, '"\\');
|
$cmd = addcslashes($cmd, '"\\');
|
||||||
|
|
||||||
|
|
|
@ -4,25 +4,27 @@
|
||||||
$root = dirname(dirname(dirname(__FILE__)));
|
$root = dirname(dirname(dirname(__FILE__)));
|
||||||
require_once $root.'/scripts/__init_script__.php';
|
require_once $root.'/scripts/__init_script__.php';
|
||||||
|
|
||||||
$user_dao = new PhabricatorUser();
|
$keys = id(new PhabricatorAuthSSHKeyQuery())
|
||||||
$ssh_dao = new PhabricatorUserSSHKey();
|
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||||
$conn_r = $user_dao->establishConnection('r');
|
->execute();
|
||||||
|
|
||||||
$rows = queryfx_all(
|
foreach ($keys as $key => $ssh_key) {
|
||||||
$conn_r,
|
// For now, filter out any keys which don't belong to users. Eventually we
|
||||||
'SELECT userName, keyBody, keyType FROM %T u JOIN %T ssh
|
// may allow devices to use this channel.
|
||||||
ON u.phid = ssh.userPHID',
|
if (!($ssh_key->getObject() instanceof PhabricatorUser)) {
|
||||||
$user_dao->getTableName(),
|
unset($keys[$key]);
|
||||||
$ssh_dao->getTableName());
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!$rows) {
|
if (!$keys) {
|
||||||
echo pht('No keys found.')."\n";
|
echo pht('No keys found.')."\n";
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
$bin = $root.'/bin/ssh-exec';
|
$bin = $root.'/bin/ssh-exec';
|
||||||
foreach ($rows as $row) {
|
foreach ($keys as $ssh_key) {
|
||||||
$user = $row['userName'];
|
$user = $ssh_key->getObject()->getUsername();
|
||||||
|
|
||||||
$cmd = csprintf('%s --phabricator-ssh-user %s', $bin, $user);
|
$cmd = csprintf('%s --phabricator-ssh-user %s', $bin, $user);
|
||||||
// This is additional escaping for the SSH 'command="..."' string.
|
// This is additional escaping for the SSH 'command="..."' string.
|
||||||
|
@ -30,13 +32,12 @@ foreach ($rows as $row) {
|
||||||
|
|
||||||
// Strip out newlines and other nonsense from the key type and key body.
|
// Strip out newlines and other nonsense from the key type and key body.
|
||||||
|
|
||||||
$type = $row['keyType'];
|
$type = $ssh_key->getKeyType();
|
||||||
$type = preg_replace('@[\x00-\x20]+@', '', $type);
|
$type = preg_replace('@[\x00-\x20]+@', '', $type);
|
||||||
|
|
||||||
$key = $row['keyBody'];
|
$key = $ssh_key->getKeyBody();
|
||||||
$key = preg_replace('@[\x00-\x20]+@', '', $key);
|
$key = preg_replace('@[\x00-\x20]+@', '', $key);
|
||||||
|
|
||||||
|
|
||||||
$options = array(
|
$options = array(
|
||||||
'command="'.$cmd.'"',
|
'command="'.$cmd.'"',
|
||||||
'no-port-forwarding',
|
'no-port-forwarding',
|
||||||
|
|
|
@ -1317,6 +1317,8 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorAuthProviderConfigTransactionQuery' => 'applications/auth/query/PhabricatorAuthProviderConfigTransactionQuery.php',
|
'PhabricatorAuthProviderConfigTransactionQuery' => 'applications/auth/query/PhabricatorAuthProviderConfigTransactionQuery.php',
|
||||||
'PhabricatorAuthRegisterController' => 'applications/auth/controller/PhabricatorAuthRegisterController.php',
|
'PhabricatorAuthRegisterController' => 'applications/auth/controller/PhabricatorAuthRegisterController.php',
|
||||||
'PhabricatorAuthRevokeTokenController' => 'applications/auth/controller/PhabricatorAuthRevokeTokenController.php',
|
'PhabricatorAuthRevokeTokenController' => 'applications/auth/controller/PhabricatorAuthRevokeTokenController.php',
|
||||||
|
'PhabricatorAuthSSHKeyQuery' => 'applications/auth/query/PhabricatorAuthSSHKeyQuery.php',
|
||||||
|
'PhabricatorAuthSSHPublicKey' => 'applications/auth/storage/PhabricatorAuthSSHPublicKey.php',
|
||||||
'PhabricatorAuthSession' => 'applications/auth/storage/PhabricatorAuthSession.php',
|
'PhabricatorAuthSession' => 'applications/auth/storage/PhabricatorAuthSession.php',
|
||||||
'PhabricatorAuthSessionEngine' => 'applications/auth/engine/PhabricatorAuthSessionEngine.php',
|
'PhabricatorAuthSessionEngine' => 'applications/auth/engine/PhabricatorAuthSessionEngine.php',
|
||||||
'PhabricatorAuthSessionGarbageCollector' => 'applications/auth/garbagecollector/PhabricatorAuthSessionGarbageCollector.php',
|
'PhabricatorAuthSessionGarbageCollector' => 'applications/auth/garbagecollector/PhabricatorAuthSessionGarbageCollector.php',
|
||||||
|
@ -4381,6 +4383,8 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorAuthProviderConfigTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
|
'PhabricatorAuthProviderConfigTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
|
||||||
'PhabricatorAuthRegisterController' => 'PhabricatorAuthController',
|
'PhabricatorAuthRegisterController' => 'PhabricatorAuthController',
|
||||||
'PhabricatorAuthRevokeTokenController' => 'PhabricatorAuthController',
|
'PhabricatorAuthRevokeTokenController' => 'PhabricatorAuthController',
|
||||||
|
'PhabricatorAuthSSHKeyQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||||
|
'PhabricatorAuthSSHPublicKey' => 'Phobject',
|
||||||
'PhabricatorAuthSession' => array(
|
'PhabricatorAuthSession' => array(
|
||||||
'PhabricatorAuthDAO',
|
'PhabricatorAuthDAO',
|
||||||
'PhabricatorPolicyInterface',
|
'PhabricatorPolicyInterface',
|
||||||
|
@ -5627,7 +5631,10 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorUserProfileEditor' => 'PhabricatorApplicationTransactionEditor',
|
'PhabricatorUserProfileEditor' => 'PhabricatorApplicationTransactionEditor',
|
||||||
'PhabricatorUserRealNameField' => 'PhabricatorUserCustomField',
|
'PhabricatorUserRealNameField' => 'PhabricatorUserCustomField',
|
||||||
'PhabricatorUserRolesField' => 'PhabricatorUserCustomField',
|
'PhabricatorUserRolesField' => 'PhabricatorUserCustomField',
|
||||||
'PhabricatorUserSSHKey' => 'PhabricatorUserDAO',
|
'PhabricatorUserSSHKey' => array(
|
||||||
|
'PhabricatorUserDAO',
|
||||||
|
'PhabricatorPolicyInterface',
|
||||||
|
),
|
||||||
'PhabricatorUserSchemaSpec' => 'PhabricatorConfigSchemaSpec',
|
'PhabricatorUserSchemaSpec' => 'PhabricatorConfigSchemaSpec',
|
||||||
'PhabricatorUserSearchIndexer' => 'PhabricatorSearchDocumentIndexer',
|
'PhabricatorUserSearchIndexer' => 'PhabricatorSearchDocumentIndexer',
|
||||||
'PhabricatorUserSinceField' => 'PhabricatorUserCustomField',
|
'PhabricatorUserSinceField' => 'PhabricatorUserCustomField',
|
||||||
|
|
104
src/applications/auth/query/PhabricatorAuthSSHKeyQuery.php
Normal file
104
src/applications/auth/query/PhabricatorAuthSSHKeyQuery.php
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class PhabricatorAuthSSHKeyQuery
|
||||||
|
extends PhabricatorCursorPagedPolicyAwareQuery {
|
||||||
|
|
||||||
|
private $ids;
|
||||||
|
private $objectPHIDs;
|
||||||
|
private $keys;
|
||||||
|
|
||||||
|
public function withIDs(array $ids) {
|
||||||
|
$this->ids = $ids;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function withObjectPHIDs(array $object_phids) {
|
||||||
|
$this->objectPHIDs = $object_phids;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function withKeys(array $keys) {
|
||||||
|
assert_instances_of($keys, 'PhabricatorAuthSSHPublicKey');
|
||||||
|
$this->keys = $keys;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function loadPage() {
|
||||||
|
$table = new PhabricatorUserSSHKey();
|
||||||
|
$conn_r = $table->establishConnection('r');
|
||||||
|
|
||||||
|
$data = queryfx_all(
|
||||||
|
$conn_r,
|
||||||
|
'SELECT * FROM %T %Q %Q %Q',
|
||||||
|
$table->getTableName(),
|
||||||
|
$this->buildWhereClause($conn_r),
|
||||||
|
$this->buildOrderClause($conn_r),
|
||||||
|
$this->buildLimitClause($conn_r));
|
||||||
|
|
||||||
|
return $table->loadAllFromArray($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function willFilterPage(array $keys) {
|
||||||
|
$object_phids = mpull($keys, 'getObjectPHID');
|
||||||
|
|
||||||
|
$objects = id(new PhabricatorObjectQuery())
|
||||||
|
->setViewer($this->getViewer())
|
||||||
|
->setParentQuery($this)
|
||||||
|
->withPHIDs($object_phids)
|
||||||
|
->execute();
|
||||||
|
$objects = mpull($objects, null, 'getPHID');
|
||||||
|
|
||||||
|
foreach ($keys as $key => $ssh_key) {
|
||||||
|
$object = idx($objects, $ssh_key->getObjectPHID());
|
||||||
|
if (!$object) {
|
||||||
|
unset($keys[$key]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$ssh_key->attachObject($object);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function buildWhereClause(AphrontDatabaseConnection $conn_r) {
|
||||||
|
$where = array();
|
||||||
|
|
||||||
|
if ($this->ids !== null) {
|
||||||
|
$where[] = qsprintf(
|
||||||
|
$conn_r,
|
||||||
|
'id IN (%Ld)',
|
||||||
|
$this->ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->objectPHIDs !== null) {
|
||||||
|
$where[] = qsprintf(
|
||||||
|
$conn_r,
|
||||||
|
'userPHID IN (%Ls)',
|
||||||
|
$this->objectPHIDs);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->keys !== null) {
|
||||||
|
// TODO: This could take advantage of a better key, and the hashing
|
||||||
|
// scheme for this table is a bit nonstandard and questionable.
|
||||||
|
|
||||||
|
$sql = array();
|
||||||
|
foreach ($this->keys as $key) {
|
||||||
|
$sql[] = qsprintf(
|
||||||
|
$conn_r,
|
||||||
|
'(keyType = %s AND keyBody = %s)',
|
||||||
|
$key->getType(),
|
||||||
|
$key->getBody());
|
||||||
|
}
|
||||||
|
$where[] = implode(' OR ', $sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
$where[] = $this->buildPagingClause($conn_r);
|
||||||
|
|
||||||
|
return $this->formatWhereClause($where);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getQueryApplicationClass() {
|
||||||
|
return 'PhabricatorAuthApplication';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,86 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data structure representing a raw public key.
|
||||||
|
*/
|
||||||
|
final class PhabricatorAuthSSHPublicKey extends Phobject {
|
||||||
|
|
||||||
|
private $type;
|
||||||
|
private $body;
|
||||||
|
private $comment;
|
||||||
|
|
||||||
|
private function __construct() {
|
||||||
|
// <internal>
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function newFromRawKey($entire_key) {
|
||||||
|
$entire_key = trim($entire_key);
|
||||||
|
if (!strlen($entire_key)) {
|
||||||
|
throw new Exception(pht('No public key was provided.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$parts = str_replace("\n", '', $entire_key);
|
||||||
|
|
||||||
|
// The third field (the comment) can have spaces in it, so split this
|
||||||
|
// into a maximum of three parts.
|
||||||
|
$parts = preg_split('/\s+/', $parts, 3);
|
||||||
|
|
||||||
|
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.
|
||||||
|
throw new Exception(pht('Provide a public key, not a private key!'));
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (count($parts)) {
|
||||||
|
case 1:
|
||||||
|
throw new Exception(
|
||||||
|
pht('Provided public key is not properly formatted.'));
|
||||||
|
case 2:
|
||||||
|
// Add an empty comment part.
|
||||||
|
$parts[] = '';
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
// This is the expected case.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
list($type, $body, $comment) = $parts;
|
||||||
|
|
||||||
|
$recognized_keys = array(
|
||||||
|
'ssh-dsa',
|
||||||
|
'ssh-dss',
|
||||||
|
'ssh-rsa',
|
||||||
|
'ecdsa-sha2-nistp256',
|
||||||
|
'ecdsa-sha2-nistp384',
|
||||||
|
'ecdsa-sha2-nistp521',
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!in_array($type, $recognized_keys)) {
|
||||||
|
$type_list = implode(', ', $recognized_keys);
|
||||||
|
throw new Exception(
|
||||||
|
pht(
|
||||||
|
'Public key type should be one of: %s',
|
||||||
|
$type_list));
|
||||||
|
}
|
||||||
|
|
||||||
|
$public_key = new PhabricatorAuthSSHPublicKey();
|
||||||
|
$public_key->type = $type;
|
||||||
|
$public_key->body = $body;
|
||||||
|
$public_key->comment = $comment;
|
||||||
|
|
||||||
|
return $public_key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getType() {
|
||||||
|
return $this->type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getBody() {
|
||||||
|
return $this->body;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getComment() {
|
||||||
|
return $this->comment;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -45,12 +45,16 @@ final class PhabricatorSettingsPanelSSHKeys
|
||||||
|
|
||||||
$id = nonempty($edit, $delete);
|
$id = nonempty($edit, $delete);
|
||||||
|
|
||||||
if ($id && is_numeric($id)) {
|
if ($id) {
|
||||||
// NOTE: This prevents editing/deleting of keys not owned by the user.
|
$key = id(new PhabricatorAuthSSHKeyQuery())
|
||||||
$key = id(new PhabricatorUserSSHKey())->loadOneWhere(
|
->setViewer($viewer)
|
||||||
'userPHID = %s AND id = %d',
|
->withIDs(array($id))
|
||||||
$user->getPHID(),
|
->requireCapabilities(
|
||||||
(int)$id);
|
array(
|
||||||
|
PhabricatorPolicyCapability::CAN_VIEW,
|
||||||
|
PhabricatorPolicyCapability::CAN_EDIT,
|
||||||
|
))
|
||||||
|
->executeOne();
|
||||||
if (!$key) {
|
if (!$key) {
|
||||||
return new Aphront404Response();
|
return new Aphront404Response();
|
||||||
}
|
}
|
||||||
|
@ -77,7 +81,11 @@ final class PhabricatorSettingsPanelSSHKeys
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
list($type, $body, $comment) = self::parsePublicKey($entire_key);
|
$public_key = PhabricatorAuthSSHPublicKey::newFromRawKey($entire_key);
|
||||||
|
|
||||||
|
$type = $public_key->getType();
|
||||||
|
$body = $public_key->getBody();
|
||||||
|
$comment = $public_key->getComment();
|
||||||
|
|
||||||
$key->setKeyType($type);
|
$key->setKeyType($type);
|
||||||
$key->setKeyBody($body);
|
$key->setKeyBody($body);
|
||||||
|
@ -153,9 +161,10 @@ final class PhabricatorSettingsPanelSSHKeys
|
||||||
$user = $this->getUser();
|
$user = $this->getUser();
|
||||||
$viewer = $request->getUser();
|
$viewer = $request->getUser();
|
||||||
|
|
||||||
$keys = id(new PhabricatorUserSSHKey())->loadAllWhere(
|
$keys = id(new PhabricatorAuthSSHKeyQuery())
|
||||||
'userPHID = %s',
|
->setViewer($viewer)
|
||||||
$user->getPHID());
|
->withObjectPHIDs(array($user->getPHID()))
|
||||||
|
->execute();
|
||||||
|
|
||||||
$rows = array();
|
$rows = array();
|
||||||
foreach ($keys as $key) {
|
foreach ($keys as $key) {
|
||||||
|
@ -294,7 +303,10 @@ final class PhabricatorSettingsPanelSSHKeys
|
||||||
'viewPolicy' => $viewer->getPHID(),
|
'viewPolicy' => $viewer->getPHID(),
|
||||||
));
|
));
|
||||||
|
|
||||||
list($type, $body, $comment) = self::parsePublicKey($public_key);
|
$public_key = PhabricatorAuthSSHPublicKey::newFromRawKey($public_key);
|
||||||
|
|
||||||
|
$type = $public_key->getType();
|
||||||
|
$body = $public_key->getBody();
|
||||||
|
|
||||||
$key = id(new PhabricatorUserSSHKey())
|
$key = id(new PhabricatorUserSSHKey())
|
||||||
->setUserPHID($user->getPHID())
|
->setUserPHID($user->getPHID())
|
||||||
|
@ -377,53 +389,4 @@ final class PhabricatorSettingsPanelSSHKeys
|
||||||
->setDialog($dialog);
|
->setDialog($dialog);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function parsePublicKey($entire_key) {
|
|
||||||
$parts = str_replace("\n", '', trim($entire_key));
|
|
||||||
|
|
||||||
// The third field (the comment) can have spaces in it, so split this
|
|
||||||
// into a maximum of three parts.
|
|
||||||
$parts = preg_split('/\s+/', $parts, 3);
|
|
||||||
|
|
||||||
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.
|
|
||||||
throw new Exception(
|
|
||||||
pht('Provide your public key, not your private key!'));
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (count($parts)) {
|
|
||||||
case 1:
|
|
||||||
throw new Exception(
|
|
||||||
pht('Provided public key is not properly formatted.'));
|
|
||||||
case 2:
|
|
||||||
// Add an empty comment part.
|
|
||||||
$parts[] = '';
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
// This is the expected case.
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
list($type, $body, $comment) = $parts;
|
|
||||||
|
|
||||||
$recognized_keys = array(
|
|
||||||
'ssh-dsa',
|
|
||||||
'ssh-dss',
|
|
||||||
'ssh-rsa',
|
|
||||||
'ecdsa-sha2-nistp256',
|
|
||||||
'ecdsa-sha2-nistp384',
|
|
||||||
'ecdsa-sha2-nistp521',
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!in_array($type, $recognized_keys)) {
|
|
||||||
$type_list = implode(', ', $recognized_keys);
|
|
||||||
throw new Exception(
|
|
||||||
pht(
|
|
||||||
'Public key type should be one of: %s',
|
|
||||||
$type_list));
|
|
||||||
}
|
|
||||||
|
|
||||||
return array($type, $body, $comment);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
final class PhabricatorUserSSHKey extends PhabricatorUserDAO {
|
final class PhabricatorUserSSHKey
|
||||||
|
extends PhabricatorUserDAO
|
||||||
|
implements PhabricatorPolicyInterface {
|
||||||
|
|
||||||
protected $userPHID;
|
protected $userPHID;
|
||||||
protected $name;
|
protected $name;
|
||||||
|
@ -9,6 +11,12 @@ final class PhabricatorUserSSHKey extends PhabricatorUserDAO {
|
||||||
protected $keyHash;
|
protected $keyHash;
|
||||||
protected $keyComment;
|
protected $keyComment;
|
||||||
|
|
||||||
|
private $object = self::ATTACHABLE;
|
||||||
|
|
||||||
|
public function getObjectPHID() {
|
||||||
|
return $this->getUserPHID();
|
||||||
|
}
|
||||||
|
|
||||||
public function getConfiguration() {
|
public function getConfiguration() {
|
||||||
return array(
|
return array(
|
||||||
self::CONFIG_COLUMN_SCHEMA => array(
|
self::CONFIG_COLUMN_SCHEMA => array(
|
||||||
|
@ -42,4 +50,37 @@ final class PhabricatorUserSSHKey extends PhabricatorUserDAO {
|
||||||
return trim(implode(' ', $parts));
|
return trim(implode(' ', $parts));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getObject() {
|
||||||
|
return $this->assertAttached($this->object);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function attachObject($object) {
|
||||||
|
$this->object = $object;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
||||||
|
|
||||||
|
|
||||||
|
public function getCapabilities() {
|
||||||
|
return array(
|
||||||
|
PhabricatorPolicyCapability::CAN_VIEW,
|
||||||
|
PhabricatorPolicyCapability::CAN_EDIT,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPolicy($capability) {
|
||||||
|
return $this->getObject()->getPolicy($capability);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
|
||||||
|
return $this->getObject()->hasAutomaticCapability($capability, $viewer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function describeAutomaticCapability($capability) {
|
||||||
|
return pht(
|
||||||
|
'SSH keys inherit the policies of the user or object they authenticate.');
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue