public function execute(PhutilArgumentParser $args) { $time_start = microtime(true); $methodv = $args->getArg('method'); if (!$methodv) { throw new Exception(pht('No Conduit method provided.')); } else { if (count($methodv) > 1) { throw new Exception(pht('Too many Conduit methods provided.')); } } $method = head($methodv); $json = $this->readAllInput(); $raw_params = null; try { $raw_params = phutil_json_decode($json); } catch (PhutilJSONParserException $ex) { throw new PhutilProxyException(pht('Invalid JSON input.'), $ex); } $params = idx($raw_params, 'params', '[]'); $params = phutil_json_decode($params); $metadata = idx($params, '__conduit__', array()); unset($params['__conduit__']); $call = null; $error_code = null; $error_info = null; try { $call = new ConduitCall($method, $params); $call->setUser($this->getUser()); $result = $call->execute(); } catch (ConduitException $ex) { $result = null; $error_code = $ex->getMessage(); if ($ex->getErrorDescription()) { $error_info = $ex->getErrorDescription(); } else { if ($call) { $error_info = $call->getErrorDescription($error_code); } } } $response = id(new ConduitAPIResponse())->setResult($result)->setErrorCode($error_code)->setErrorInfo($error_info); $json_out = json_encode($response->toDictionary()); $json_out = $json_out . "\n"; $this->getIOChannel()->write($json_out); // NOTE: Flush here so we can get an accurate result for the duration, // if the response is large and the receiver is slow to read it. $this->getIOChannel()->flush(); $time_end = microtime(true); $connection_id = idx($metadata, 'connectionID'); $log = id(new PhabricatorConduitMethodCallLog())->setCallerPHID($this->getUser()->getPHID())->setConnectionID($connection_id)->setMethod($method)->setError((string) $error_code)->setDuration(1000000 * ($time_end - $time_start))->save(); }
public function processRequest() { $time_start = microtime(true); $request = $this->getRequest(); $method = $this->method; $api_request = null; $log = new PhabricatorConduitMethodCallLog(); $log->setMethod($method); $metadata = array(); try { $params = $this->decodeConduitParams($request, $method); $metadata = idx($params, '__conduit__', array()); unset($params['__conduit__']); $call = new ConduitCall($method, $params); $result = null; // TODO: Straighten out the auth pathway here. We shouldn't be creating // a ConduitAPIRequest at this level, but some of the auth code expects // it. Landing a halfway version of this to unblock T945. $api_request = new ConduitAPIRequest($params); $allow_unguarded_writes = false; $auth_error = null; $conduit_username = '******'; if ($call->shouldRequireAuthentication()) { $metadata['scope'] = $call->getRequiredScope(); $auth_error = $this->authenticateUser($api_request, $metadata); // If we've explicitly authenticated the user here and either done // CSRF validation or are using a non-web authentication mechanism. $allow_unguarded_writes = true; if (isset($metadata['actAsUser'])) { $this->actAsUser($api_request, $metadata['actAsUser']); } if ($auth_error === null) { $conduit_user = $api_request->getUser(); if ($conduit_user && $conduit_user->getPHID()) { $conduit_username = $conduit_user->getUsername(); } $call->setUser($api_request->getUser()); } } $access_log = PhabricatorAccessLog::getLog(); if ($access_log) { $access_log->setData(array('u' => $conduit_username, 'm' => $method)); } if ($call->shouldAllowUnguardedWrites()) { $allow_unguarded_writes = true; } if ($auth_error === null) { if ($allow_unguarded_writes) { $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); } try { $result = $call->execute(); $error_code = null; $error_info = null; } catch (ConduitException $ex) { $result = null; $error_code = $ex->getMessage(); if ($ex->getErrorDescription()) { $error_info = $ex->getErrorDescription(); } else { $error_info = $call->getErrorDescription($error_code); } } if ($allow_unguarded_writes) { unset($unguarded); } } else { list($error_code, $error_info) = $auth_error; } } catch (Exception $ex) { phlog($ex); $result = null; $error_code = 'ERR-CONDUIT-CORE'; $error_info = $ex->getMessage(); } $time_end = microtime(true); $connection_id = null; if (idx($metadata, 'connectionID')) { $connection_id = $metadata['connectionID']; } else { if ($method == 'conduit.connect' && $result) { $connection_id = idx($result, 'connectionID'); } } $log->setConnectionID($connection_id); $log->setError((string) $error_code); $log->setDuration(1000000 * ($time_end - $time_start)); // TODO: This is a hack, but the insert is comparatively expensive and // we only really care about having these logs for real CLI clients, if // even that. if (empty($metadata['authToken'])) { $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); $log->save(); unset($unguarded); } $response = id(new ConduitAPIResponse())->setResult($result)->setErrorCode($error_code)->setErrorInfo($error_info); switch ($request->getStr('output')) { case 'human': return $this->buildHumanReadableResponse($method, $api_request, $response->toDictionary()); case 'json': default: return id(new AphrontJSONResponse())->setAddJSONShield(false)->setContent($response->toDictionary()); } }
public function processRequest() { $time_start = microtime(true); $request = $this->getRequest(); $method = $this->method; $api_request = null; $method_implementation = null; $log = new PhabricatorConduitMethodCallLog(); $log->setMethod($method); $metadata = array(); $multimeter = MultimeterControl::getInstance(); if ($multimeter) { $multimeter->setEventContext('api.' . $method); } try { list($metadata, $params) = $this->decodeConduitParams($request, $method); $call = new ConduitCall($method, $params); $method_implementation = $call->getMethodImplementation(); $result = null; // TODO: The relationship between ConduitAPIRequest and ConduitCall is a // little odd here and could probably be improved. Specifically, the // APIRequest is a sub-object of the Call, which does not parallel the // role of AphrontRequest (which is an indepenent object). // In particular, the setUser() and getUser() existing independently on // the Call and APIRequest is very awkward. $api_request = $call->getAPIRequest(); $allow_unguarded_writes = false; $auth_error = null; $conduit_username = '******'; if ($call->shouldRequireAuthentication()) { $metadata['scope'] = $call->getRequiredScope(); $auth_error = $this->authenticateUser($api_request, $metadata); // If we've explicitly authenticated the user here and either done // CSRF validation or are using a non-web authentication mechanism. $allow_unguarded_writes = true; if ($auth_error === null) { $conduit_user = $api_request->getUser(); if ($conduit_user && $conduit_user->getPHID()) { $conduit_username = $conduit_user->getUsername(); } $call->setUser($api_request->getUser()); } } $access_log = PhabricatorAccessLog::getLog(); if ($access_log) { $access_log->setData(array('u' => $conduit_username, 'm' => $method)); } if ($call->shouldAllowUnguardedWrites()) { $allow_unguarded_writes = true; } if ($auth_error === null) { if ($allow_unguarded_writes) { $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); } try { $result = $call->execute(); $error_code = null; $error_info = null; } catch (ConduitException $ex) { $result = null; $error_code = $ex->getMessage(); if ($ex->getErrorDescription()) { $error_info = $ex->getErrorDescription(); } else { $error_info = $call->getErrorDescription($error_code); } } if ($allow_unguarded_writes) { unset($unguarded); } } else { list($error_code, $error_info) = $auth_error; } } catch (Exception $ex) { if (!$ex instanceof ConduitMethodNotFoundException) { phlog($ex); } $result = null; $error_code = $ex instanceof ConduitException ? 'ERR-CONDUIT-CALL' : 'ERR-CONDUIT-CORE'; $error_info = $ex->getMessage(); } $time_end = microtime(true); $connection_id = null; if (idx($metadata, 'connectionID')) { $connection_id = $metadata['connectionID']; } else { if ($method == 'conduit.connect' && $result) { $connection_id = idx($result, 'connectionID'); } } $log->setCallerPHID(isset($conduit_user) ? $conduit_user->getPHID() : null)->setConnectionID($connection_id)->setError((string) $error_code)->setDuration(1000000 * ($time_end - $time_start)); $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); $log->save(); unset($unguarded); $response = id(new ConduitAPIResponse())->setResult($result)->setErrorCode($error_code)->setErrorInfo($error_info); switch ($request->getStr('output')) { case 'human': return $this->buildHumanReadableResponse($method, $api_request, $response->toDictionary(), $method_implementation); case 'json': default: return id(new AphrontJSONResponse())->setAddJSONShield(false)->setContent($response->toDictionary()); } }