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 @@ -14,6 +14,7 @@ 'AlmanacDevice' => 'applications/almanac/storage/AlmanacDevice.php', 'AlmanacDevicePHIDType' => 'applications/almanac/phid/AlmanacDevicePHIDType.php', 'AlmanacDeviceProperty' => 'applications/almanac/storage/AlmanacDeviceProperty.php', + 'AlmanacDevicePropertyQuery' => 'applications/almanac/query/AlmanacDevicePropertyQuery.php', 'AlmanacDeviceQuery' => 'applications/almanac/query/AlmanacDeviceQuery.php', 'AlmanacManagementRegisterWorkflow' => 'applications/almanac/management/AlmanacManagementRegisterWorkflow.php', 'AlmanacManagementWorkflow' => 'applications/almanac/management/AlmanacManagementWorkflow.php', @@ -2864,6 +2865,7 @@ ), 'AlmanacDevicePHIDType' => 'PhabricatorPHIDType', 'AlmanacDeviceProperty' => 'AlmanacDAO', + 'AlmanacDevicePropertyQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'AlmanacDeviceQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'AlmanacManagementRegisterWorkflow' => 'AlmanacManagementWorkflow', 'AlmanacManagementWorkflow' => 'PhabricatorManagementWorkflow', diff --git a/src/applications/almanac/query/AlmanacDevicePropertyQuery.php b/src/applications/almanac/query/AlmanacDevicePropertyQuery.php new file mode 100644 --- /dev/null +++ b/src/applications/almanac/query/AlmanacDevicePropertyQuery.php @@ -0,0 +1,60 @@ +keys = $keys; + return $this; + } + + public function withDevicePHIDs(array $device_phids) { + $this->devicePHIDs = $device_phids; + return $this; + } + + protected function loadPage() { + $table = new AlmanacDeviceProperty(); + $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->keys !== null) { + $where[] = qsprintf( + $conn_r, + '`key` IN (%Ls)', + $this->keys); + } + + if ($this->devicePHIDs !== null) { + $where[] = qsprintf( + $conn_r, + 'devicePHIDs IN (%Ls)', + $this->devicePHIDs); + } + + $where[] = $this->buildPagingClause($conn_r); + + return $this->formatWhereClause($where); + } + + public function getQueryApplicationClass() { + return 'PhabricatorAlmanacApplication'; + } + +} diff --git a/src/applications/almanac/util/AlmanacConduitUtil.php b/src/applications/almanac/util/AlmanacConduitUtil.php --- a/src/applications/almanac/util/AlmanacConduitUtil.php +++ b/src/applications/almanac/util/AlmanacConduitUtil.php @@ -2,6 +2,42 @@ final class AlmanacConduitUtil extends Phobject { + public static function loadSigningKeys() { + static $private_key = null; + static $public_key = null; + + if ($private_key === null) { + $private_key = new PhutilOpaqueEnvelope( + Filesystem::readFile(self::getHostPrivateKeyPath())); + } + + if ($public_key === null) { + $host_id = Filesystem::readFile(self::getHostIDPath()); + + $device = id(new AlmanacDevice())->load($host_id); + + if ($device === null) { + throw new Exception('Current device is not registered in Almanac.'); + } + + $property = id(new AlmanacDevicePropertyQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withDevicePHIDs(array($device->getPHID())) + ->withKeys(array('conduitPublicOpenSSLKey')) + ->executeOne(); + + if ($property === null) { + throw new Exception( + 'Current device does not have an OpenSSL key available '. + 'for Conduit access.'); + } + + $public_key = $property->getValue(); + } + + return array($public_key, $private_key); + } + public static function getHostPrivateKeyPath() { $root = dirname(phutil_get_library_root('phabricator')); $path = $root.'/conf/local/HOSTKEY'; diff --git a/src/applications/conduit/call/ConduitCall.php b/src/applications/conduit/call/ConduitCall.php --- a/src/applications/conduit/call/ConduitCall.php +++ b/src/applications/conduit/call/ConduitCall.php @@ -145,15 +145,22 @@ $params['__conduit__']['isProxied'] = true; if ($this->handler->shouldRequireAuthentication()) { - $client->callMethodSynchronous( - 'conduit.connect', - array( - 'client' => 'PhabricatorConduit', - 'clientVersion' => '1.0', - 'user' => $this->getUser()->getUserName(), - 'certificate' => $this->getUser()->getConduitCertificate(), - '__conduit__' => $params['__conduit__'], - )); + if ($user->isOmnipotent()) { + list($public_key, $private_key) = + AlmanacConduitUtil::loadSigningKeys(); + + $client->setSigningKeys($public_key, $private_key); + } else { + $client->callMethodSynchronous( + 'conduit.connect', + array( + 'client' => 'PhabricatorConduit', + 'clientVersion' => '1.0', + 'user' => $this->getUser()->getUserName(), + 'certificate' => $this->getUser()->getConduitCertificate(), + '__conduit__' => $params['__conduit__'], + )); + } } return $client->callMethodSynchronous( diff --git a/src/applications/conduit/controller/PhabricatorConduitAPIController.php b/src/applications/conduit/controller/PhabricatorConduitAPIController.php --- a/src/applications/conduit/controller/PhabricatorConduitAPIController.php +++ b/src/applications/conduit/controller/PhabricatorConduitAPIController.php @@ -209,6 +209,48 @@ $request->getUser()); } + // handle cross-host auth + if (isset($metadata['signature']) && isset($metadata['publicKey'])) { + $signature = $metadata['signature']; + $public_key = $metadata['publicKey']; + + $property = id(new AlmanacDevicePropertyQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withKeys(array('conduitPublicOpenSSLKey')) + ->withValues(array($public_key)) + ->executeOne(); + if ($property === null) { + return array( + 'ERR-INVALID-AUTH', + 'No registered device with matching OpenSSL public key.', + ); + } + + // These are not included in the signature, so remove them from + // metadata before verifying. + unset($metadata['signature']); + unset($metadata['publicKey']); + + $verified = ConduitClient::verifySignature( + $signature, + $public_key, + $this->method, + $api_request->getAllParameters(), + $metadata); + + if ($verified) { + // Authenticated using server signature; therefore the user is + // the omnipotent user. + $api_request->setUser(PhabricatorUser::getOmnipotentUser()); + return null; + } else { + return array( + 'ERR-INVALID-AUTH', + 'Server signature is invalid.', + ); + } + } + // handle oauth $access_token = $request->getStr('access_token'); $method_scope = $metadata['scope'];