Ejemplo n.º 1
0
 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());
     }
 }