diff --git a/.gitignore b/.gitignore --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,8 @@ /conf/local/local.json /conf/local/ENVIRONMENT /conf/local/VERSION +/conf/local/HOSTKEY +/conf/local/HOSTID # Impact Font /resources/font/impact.ttf diff --git a/bin/almanac b/bin/almanac new file mode 120000 --- /dev/null +++ b/bin/almanac @@ -0,0 +1 @@ +../scripts/almanac/manage_almanac.php \ No newline at end of file diff --git a/resources/sql/autopatches/20140902.almanacdevice.1.sql b/resources/sql/autopatches/20140902.almanacdevice.1.sql new file mode 100644 --- /dev/null +++ b/resources/sql/autopatches/20140902.almanacdevice.1.sql @@ -0,0 +1,18 @@ +CREATE TABLE {$NAMESPACE}_almanac.almanac_device ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + phid VARBINARY(64) NOT NULL, + name VARCHAR(255) NOT NULL COLLATE {$COLLATE_TEXT}, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_phid` (phid) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; + +CREATE TABLE {$NAMESPACE}_almanac.almanac_deviceproperty ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + devicePHID VARBINARY(64) NOT NULL, + `key` VARCHAR(128) NOT NULL COLLATE {$COLLATE_TEXT}, + value LONGTEXT NOT NULL, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + KEY `key_device` (devicePHID, `key`) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/scripts/almanac/manage_almanac.php b/scripts/almanac/manage_almanac.php new file mode 100755 --- /dev/null +++ b/scripts/almanac/manage_almanac.php @@ -0,0 +1,21 @@ +#!/usr/bin/env php +setTagline('manage host directory'); +$args->setSynopsis(<<parseStandardArguments(); + +$workflows = id(new PhutilSymbolLoader()) + ->setAncestorClass('AlmanacManagementWorkflow') + ->loadObjects(); +$workflows[] = new PhutilHelpArgumentWorkflow(); +$args->parseWorkflows($workflows); 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 @@ -9,6 +9,14 @@ phutil_register_library_map(array( '__library_version__' => 2, 'class' => array( + 'AlmanacConduitUtil' => 'applications/almanac/util/AlmanacConduitUtil.php', + 'AlmanacDAO' => 'applications/almanac/storage/AlmanacDAO.php', + 'AlmanacDevice' => 'applications/almanac/storage/AlmanacDevice.php', + 'AlmanacDevicePHIDType' => 'applications/almanac/phid/AlmanacDevicePHIDType.php', + 'AlmanacDeviceProperty' => 'applications/almanac/storage/AlmanacDeviceProperty.php', + 'AlmanacDeviceQuery' => 'applications/almanac/query/AlmanacDeviceQuery.php', + 'AlmanacManagementRegisterWorkflow' => 'applications/almanac/management/AlmanacManagementRegisterWorkflow.php', + 'AlmanacManagementWorkflow' => 'applications/almanac/management/AlmanacManagementWorkflow.php', 'Aphront304Response' => 'aphront/response/Aphront304Response.php', 'Aphront400Response' => 'aphront/response/Aphront400Response.php', 'Aphront403Response' => 'aphront/response/Aphront403Response.php', @@ -1126,6 +1134,7 @@ 'PhabricatorActionListView' => 'view/layout/PhabricatorActionListView.php', 'PhabricatorActionView' => 'view/layout/PhabricatorActionView.php', 'PhabricatorAllCapsTranslation' => 'infrastructure/internationalization/translation/PhabricatorAllCapsTranslation.php', + 'PhabricatorAlmanacApplication' => 'applications/almanac/application/PhabricatorAlmanacApplication.php', 'PhabricatorAmazonAuthProvider' => 'applications/auth/provider/PhabricatorAmazonAuthProvider.php', 'PhabricatorAnchorView' => 'view/layout/PhabricatorAnchorView.php', 'PhabricatorAphlictManagementBuildWorkflow' => 'applications/aphlict/management/PhabricatorAphlictManagementBuildWorkflow.php', @@ -2847,6 +2856,17 @@ 'require_celerity_resource' => 'infrastructure/celerity/api.php', ), 'xmap' => array( + 'AlmanacConduitUtil' => 'Phobject', + 'AlmanacDAO' => 'PhabricatorLiskDAO', + 'AlmanacDevice' => array( + 'AlmanacDAO', + 'PhabricatorPolicyInterface', + ), + 'AlmanacDevicePHIDType' => 'PhabricatorPHIDType', + 'AlmanacDeviceProperty' => 'AlmanacDAO', + 'AlmanacDeviceQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'AlmanacManagementRegisterWorkflow' => 'AlmanacManagementWorkflow', + 'AlmanacManagementWorkflow' => 'PhabricatorManagementWorkflow', 'Aphront304Response' => 'AphrontResponse', 'Aphront400Response' => 'AphrontResponse', 'Aphront403Response' => 'AphrontHTMLResponse', @@ -4031,6 +4051,7 @@ 'PhabricatorActionListView' => 'AphrontView', 'PhabricatorActionView' => 'AphrontView', 'PhabricatorAllCapsTranslation' => 'PhabricatorTranslation', + 'PhabricatorAlmanacApplication' => 'PhabricatorApplication', 'PhabricatorAmazonAuthProvider' => 'PhabricatorOAuth2AuthProvider', 'PhabricatorAnchorView' => 'AphrontView', 'PhabricatorAphlictManagementBuildWorkflow' => 'PhabricatorAphlictManagementWorkflow', diff --git a/src/applications/almanac/application/PhabricatorAlmanacApplication.php b/src/applications/almanac/application/PhabricatorAlmanacApplication.php new file mode 100644 --- /dev/null +++ b/src/applications/almanac/application/PhabricatorAlmanacApplication.php @@ -0,0 +1,41 @@ +setName('register') + ->setSynopsis(pht('Register this host for authorized Conduit access.')) + ->setArguments(array()); + } + + public function execute(PhutilArgumentParser $args) { + $console = PhutilConsole::getConsole(); + + if (Filesystem::pathExists(AlmanacConduitUtil::getHostPrivateKeyPath())) { + throw new Exception( + 'This host already has a private key for Conduit access.'); + } + + $pair = PhabricatorSSHKeyGenerator::generateKeypair(); + list($public_key, $private_key) = $pair; + + $host = id(new AlmanacDevice()) + ->setName(php_uname('n')) + ->save(); + + id(new AlmanacDeviceProperty()) + ->setDevicePHID($host->getPHID()) + ->setKey('conduitPublicOpenSSHKey') + ->setValue($public_key) + ->save(); + + id(new AlmanacDeviceProperty()) + ->setDevicePHID($host->getPHID()) + ->setKey('conduitPublicOpenSSLKey') + ->setValue($this->convertToOpenSSLPublicKey($public_key)) + ->save(); + + Filesystem::writeFile( + AlmanacConduitUtil::getHostPrivateKeyPath(), + $private_key); + + Filesystem::writeFile( + AlmanacConduitUtil::getHostIDPath(), + $host->getID()); + + $console->writeOut("Registered as device %d.\n", $host->getID()); + } + + private function convertToOpenSSLPublicKey($openssh_public_key) { + $ssh_public_key_file = new TempFile(); + Filesystem::writeFile($ssh_public_key_file, $openssh_public_key); + + list($public_key, $stderr) = id(new ExecFuture( + 'ssh-keygen -e -f %s -m pkcs8', + $ssh_public_key_file))->resolvex(); + + unset($ssh_public_key_file); + + return $public_key; + } + +} diff --git a/src/applications/almanac/management/AlmanacManagementWorkflow.php b/src/applications/almanac/management/AlmanacManagementWorkflow.php new file mode 100644 --- /dev/null +++ b/src/applications/almanac/management/AlmanacManagementWorkflow.php @@ -0,0 +1,4 @@ +withPHIDs($phids); + } + + public function loadHandles( + PhabricatorHandleQuery $query, + array $handles, + array $objects) { + + foreach ($handles as $phid => $handle) { + $device = $objects[$phid]; + + $id = $device->getID(); + $name = $device->getName(); + + $handle->setObjectName(pht('Device %d', $id)); + $handle->setName($name); + } + } + +} diff --git a/src/applications/almanac/query/AlmanacDeviceQuery.php b/src/applications/almanac/query/AlmanacDeviceQuery.php new file mode 100644 --- /dev/null +++ b/src/applications/almanac/query/AlmanacDeviceQuery.php @@ -0,0 +1,60 @@ +ids = $ids; + return $this; + } + + public function withPHIDs(array $phids) { + $this->phids = $phids; + return $this; + } + + protected function loadPage() { + $table = new AlmanacDevice(); + $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 buildWhereClause($conn_r) { + $where = array(); + + if ($this->ids !== null) { + $where[] = qsprintf( + $conn_r, + 'id IN (%Ld)', + $this->ids); + } + + if ($this->phids !== null) { + $where[] = qsprintf( + $conn_r, + 'phid IN (%Ls)', + $this->phids); + } + + $where[] = $this->buildPagingClause($conn_r); + + return $this->formatWhereClause($where); + } + + public function getQueryApplicationClass() { + return 'PhabricatorAlmanacApplication'; + } + +} diff --git a/src/applications/almanac/storage/AlmanacDAO.php b/src/applications/almanac/storage/AlmanacDAO.php new file mode 100644 --- /dev/null +++ b/src/applications/almanac/storage/AlmanacDAO.php @@ -0,0 +1,9 @@ + true, + self::CONFIG_COLUMN_SCHEMA => array( + 'name' => 'text255', + ), + ) + parent::getConfiguration(); + } + + public function generatePHID() { + return PhabricatorPHID::generateNewPHID(AlmanacDevicePHIDType::TYPECONST); + } + + +/* -( PhabricatorPolicyInterface )----------------------------------------- */ + + + public function getCapabilities() { + return array( + PhabricatorPolicyCapability::CAN_VIEW, + ); + } + + public function getPolicy($capability) { + switch ($capability) { + case PhabricatorPolicyCapability::CAN_VIEW: + // Until we get a clearer idea on what's going to be stored in this + // table, don't allow anyone (other than the omnipotent user) to find + // these objects. + return PhabricatorPolicies::POLICY_NOONE; + } + } + + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { + return false; + } + + public function describeAutomaticCapability($capability) { + return null; + } + +} diff --git a/src/applications/almanac/storage/AlmanacDeviceProperty.php b/src/applications/almanac/storage/AlmanacDeviceProperty.php new file mode 100644 --- /dev/null +++ b/src/applications/almanac/storage/AlmanacDeviceProperty.php @@ -0,0 +1,25 @@ + array( + 'value' => self::SERIALIZATION_JSON, + ), + self::CONFIG_COLUMN_SCHEMA => array( + 'key' => 'text128', + ), + self::CONFIG_KEY_SCHEMA => array( + 'key_device' => array( + 'columns' => array('devicePHID', 'key'), + ), + ), + ) + parent::getConfiguration(); + } + +} diff --git a/src/applications/almanac/util/AlmanacConduitUtil.php b/src/applications/almanac/util/AlmanacConduitUtil.php new file mode 100644 --- /dev/null +++ b/src/applications/almanac/util/AlmanacConduitUtil.php @@ -0,0 +1,17 @@ + array(), 'db.system' => array(), 'db.fund' => array(), + 'db.almanac' => array(), '0000.legacy.sql' => array( 'legacy' => 0, ),