@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Allow applications to call Conduit directly

Summary:
Sorry this took so long, had a bunch of stuff going on today.

Separate the actual core part of making conduit calls from the controller, so the application can make conduit calls without needing to invoke HTTP or redo auth. Generally, this lets us build more parts of the application on top of Conduit, as appropriate.

This diff can be simplified, but I wanted to unblock you guys first. I'll followup with a cleanup patch once I have a chance.

Test Plan: Ran unit tests, ran calls from the conduit API console, and ran calls over arc.

Reviewers: nodren, 20after4, btrahan, vrana

Reviewed By: 20after4

CC: aran, svemir

Maniphest Tasks: T945

Differential Revision: https://secure.phabricator.com/D2718

+220 -68
+3
src/__phutil_library_map__.php
··· 194 194 'ConduitAPI_user_query_Method' => 'applications/conduit/method/user/ConduitAPI_user_query_Method.php', 195 195 'ConduitAPI_user_removestatus_Method' => 'applications/conduit/method/user/ConduitAPI_user_removestatus_Method.php', 196 196 'ConduitAPI_user_whoami_Method' => 'applications/conduit/method/user/ConduitAPI_user_whoami_Method.php', 197 + 'ConduitCall' => 'applications/conduit/call/ConduitCall.php', 198 + 'ConduitCallTestCase' => 'applications/conduit/call/__tests__/ConduitCallTestCase.php', 197 199 'ConduitException' => 'applications/conduit/protocol/ConduitException.php', 198 200 'DarkConsoleConfigPlugin' => 'aphront/console/plugin/DarkConsoleConfigPlugin.php', 199 201 'DarkConsoleController' => 'aphront/console/DarkConsoleController.php', ··· 1256 1258 'ConduitAPI_user_query_Method' => 'ConduitAPI_user_Method', 1257 1259 'ConduitAPI_user_removestatus_Method' => 'ConduitAPI_user_Method', 1258 1260 'ConduitAPI_user_whoami_Method' => 'ConduitAPI_user_Method', 1261 + 'ConduitCallTestCase' => 'PhabricatorTestCase', 1259 1262 'ConduitException' => 'Exception', 1260 1263 'DarkConsoleConfigPlugin' => 'DarkConsolePlugin', 1261 1264 'DarkConsoleController' => 'PhabricatorController',
+104
src/applications/conduit/call/ConduitCall.php
··· 1 + <?php 2 + 3 + /* 4 + * Copyright 2012 Facebook, Inc. 5 + * 6 + * Licensed under the Apache License, Version 2.0 (the "License"); 7 + * you may not use this file except in compliance with the License. 8 + * You may obtain a copy of the License at 9 + * 10 + * http://www.apache.org/licenses/LICENSE-2.0 11 + * 12 + * Unless required by applicable law or agreed to in writing, software 13 + * distributed under the License is distributed on an "AS IS" BASIS, 14 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 + * See the License for the specific language governing permissions and 16 + * limitations under the License. 17 + */ 18 + 19 + /** 20 + * Run a conduit method in-process, without requiring HTTP requests. Usage: 21 + * 22 + * $call = new ConduitCall('method.name', array('param' => 'value')); 23 + * $call->setUser($user); 24 + * $result = $call->execute(); 25 + * 26 + */ 27 + final class ConduitCall { 28 + 29 + private $method; 30 + private $params; 31 + private $request; 32 + private $user; 33 + 34 + public function __construct($method, array $params) { 35 + $this->method = $method; 36 + $this->params = $params; 37 + $this->handler = $this->buildMethodHandler($method); 38 + $this->request = new ConduitAPIRequest($params); 39 + } 40 + 41 + public function setUser(PhabricatorUser $user) { 42 + $this->user = $user; 43 + return $this; 44 + } 45 + 46 + public function getUser() { 47 + return $this->user; 48 + } 49 + 50 + public function shouldRequireAuthentication() { 51 + return $this->handler->shouldRequireAuthentication(); 52 + } 53 + 54 + public function shouldAllowUnguardedWrites() { 55 + return $this->handler->shouldAllowUnguardedWrites(); 56 + } 57 + 58 + public function getRequiredScope() { 59 + return $this->handler->getRequiredScope(); 60 + } 61 + 62 + public function getErrorDescription($code) { 63 + return $this->handler->getErrorDescription($code); 64 + } 65 + 66 + public function execute() { 67 + if (!$this->getUser()) { 68 + if ($this->shouldRequireAuthentication()) { 69 + throw new ConduitException("ERR-INVALID-AUTH"); 70 + } 71 + } else { 72 + $this->request->setUser($this->getUser()); 73 + } 74 + 75 + return $this->handler->executeMethod($this->request); 76 + } 77 + 78 + protected function buildMethodHandler($method) { 79 + $method_class = ConduitAPIMethod::getClassNameFromAPIMethodName($method); 80 + 81 + // Test if the method exists. 82 + $ok = false; 83 + try { 84 + $ok = class_exists($method_class); 85 + } catch (Exception $ex) { 86 + // Discard, we provide a more specific exception below. 87 + } 88 + if (!$ok) { 89 + throw new Exception( 90 + "Conduit method '{$method}' does not exist."); 91 + } 92 + 93 + $class_info = new ReflectionClass($method_class); 94 + if ($class_info->isAbstract()) { 95 + throw new Exception( 96 + "Method '{$method}' is not valid; the implementation is an abstract ". 97 + "base class."); 98 + } 99 + 100 + return newv($method_class, array()); 101 + } 102 + 103 + 104 + }
+43
src/applications/conduit/call/__tests__/ConduitCallTestCase.php
··· 1 + <?php 2 + 3 + /* 4 + * Copyright 2012 Facebook, Inc. 5 + * 6 + * Licensed under the Apache License, Version 2.0 (the "License"); 7 + * you may not use this file except in compliance with the License. 8 + * You may obtain a copy of the License at 9 + * 10 + * http://www.apache.org/licenses/LICENSE-2.0 11 + * 12 + * Unless required by applicable law or agreed to in writing, software 13 + * distributed under the License is distributed on an "AS IS" BASIS, 14 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 + * See the License for the specific language governing permissions and 16 + * limitations under the License. 17 + */ 18 + 19 + final class ConduitCallTestCase extends PhabricatorTestCase { 20 + 21 + public function testConduitPing() { 22 + $call = new ConduitCall('conduit.ping', array()); 23 + $result = $call->execute(); 24 + 25 + $this->assertEqual(false, empty($result)); 26 + } 27 + 28 + public function testConduitAuth() { 29 + $call = new ConduitCall('user.whoami', array()); 30 + 31 + $caught = null; 32 + try { 33 + $result = $call->execute(); 34 + } catch (ConduitException $ex) { 35 + $caught = $ex; 36 + } 37 + 38 + $this->assertEqual( 39 + true, 40 + ($caught instanceof ConduitException), 41 + "user.whoami should require authentication"); 42 + } 43 + }
+70 -68
src/applications/conduit/controller/PhabricatorConduitAPIController.php
··· 39 39 40 40 $method = $this->method; 41 41 42 - $method_class = ConduitAPIMethod::getClassNameFromAPIMethodName($method); 43 42 $api_request = null; 44 43 45 44 $log = new PhabricatorConduitMethodCallLog(); 46 45 $log->setMethod($method); 47 - $metadata = array(); 48 46 49 47 try { 50 48 51 - $ok = false; 52 - try { 53 - $ok = class_exists($method_class); 54 - } catch (Exception $ex) { 55 - // Discard, we provide a more specific exception below. 56 - } 57 - 58 - if (!$ok) { 59 - throw new Exception( 60 - "Conduit method '{$method}' does not exist."); 61 - } 62 - 63 - $class_info = new ReflectionClass($method_class); 64 - if ($class_info->isAbstract()) { 65 - throw new Exception( 66 - "Method '{$method}' is not valid; the implementation is an abstract ". 67 - "base class."); 68 - } 69 - 70 - $method_handler = newv($method_class, array()); 71 - 72 - if (isset($_REQUEST['params']) && is_array($_REQUEST['params'])) { 73 - $params_post = $request->getArr('params'); 74 - foreach ($params_post as $key => $value) { 75 - if ($value == '') { 76 - // Interpret empty string null (e.g., the user didn't type anything 77 - // into the box). 78 - $value = 'null'; 79 - } 80 - $decoded_value = json_decode($value, true); 81 - if ($decoded_value === null && strtolower($value) != 'null') { 82 - // When json_decode() fails, it returns null. This almost certainly 83 - // indicates that a user was using the web UI and didn't put quotes 84 - // around a string value. We can either do what we think they meant 85 - // (treat it as a string) or fail. For now, err on the side of 86 - // caution and fail. In the future, if we make the Conduit API 87 - // actually do type checking, it might be reasonable to treat it as 88 - // a string if the parameter type is string. 89 - throw new Exception( 90 - "The value for parameter '{$key}' is not valid JSON. All ". 91 - "parameters must be encoded as JSON values, including strings ". 92 - "(which means you need to surround them in double quotes). ". 93 - "Check your syntax. Value was: {$value}"); 94 - } 95 - $params_post[$key] = $decoded_value; 96 - } 97 - $params = $params_post; 98 - } else { 99 - $params_json = $request->getStr('params'); 100 - if (!strlen($params_json)) { 101 - $params = array(); 102 - } else { 103 - $params = json_decode($params_json, true); 104 - if (!is_array($params)) { 105 - throw new Exception( 106 - "Invalid parameter information was passed to method ". 107 - "'{$method}', could not decode JSON serialization."); 108 - } 109 - } 110 - } 111 - 49 + $params = $this->decodeConduitParams($request); 112 50 $metadata = idx($params, '__conduit__', array()); 113 51 unset($params['__conduit__']); 52 + 53 + $call = new ConduitCall($method, $params); 114 54 115 55 $result = null; 116 56 57 + // TODO: Straighten out the auth pathway here. We shouldn't be creating 58 + // a ConduitAPIRequest at this level, but some of the auth code expects 59 + // it. Landing a halfway version of this to unblock T945. 60 + 117 61 $api_request = new ConduitAPIRequest($params); 118 62 119 63 $allow_unguarded_writes = false; 120 64 $auth_error = null; 121 65 $conduit_username = '-'; 122 - if ($method_handler->shouldRequireAuthentication()) { 123 - $metadata['scope'] = $method_handler->getRequiredScope(); 66 + if ($call->shouldRequireAuthentication()) { 67 + $metadata['scope'] = $call->getRequiredScope(); 124 68 $auth_error = $this->authenticateUser($api_request, $metadata); 125 69 // If we've explicitly authenticated the user here and either done 126 70 // CSRF validation or are using a non-web authentication mechanism. ··· 135 79 if ($conduit_user && $conduit_user->getPHID()) { 136 80 $conduit_username = $conduit_user->getUsername(); 137 81 } 82 + $call->setUser($api_request->getUser()); 138 83 } 139 84 } 140 85 ··· 147 92 )); 148 93 } 149 94 150 - if ($method_handler->shouldAllowUnguardedWrites()) { 95 + if ($call->shouldAllowUnguardedWrites()) { 151 96 $allow_unguarded_writes = true; 152 97 } 153 98 ··· 157 102 } 158 103 159 104 try { 160 - $result = $method_handler->executeMethod($api_request); 105 + $result = $call->execute(); 161 106 $error_code = null; 162 107 $error_info = null; 163 108 } catch (ConduitException $ex) { ··· 166 111 if ($ex->getErrorDescription()) { 167 112 $error_info = $ex->getErrorDescription(); 168 113 } else { 169 - $error_info = $method_handler->getErrorDescription($error_code); 114 + $error_info = $call->getErrorDescription($error_code); 170 115 } 171 116 } 172 117 if ($allow_unguarded_writes) { ··· 475 420 return $value; 476 421 } 477 422 423 + private function decodeConduitParams(AphrontRequest $request) { 424 + 425 + // Look for parameters from the Conduit API Console, which are encoded 426 + // as HTTP POST parameters in an array, e.g.: 427 + // 428 + // params[name]=value&params[name2]=value2 429 + // 430 + // The fields are individually JSON encoded, since we require users to 431 + // enter JSON so that we avoid type ambiguity. 432 + 433 + $params = $request->getArr('params', null); 434 + if ($params !== null) { 435 + foreach ($params as $key => $value) { 436 + if ($value == '') { 437 + // Interpret empty string null (e.g., the user didn't type anything 438 + // into the box). 439 + $value = 'null'; 440 + } 441 + $decoded_value = json_decode($value, true); 442 + if ($decoded_value === null && strtolower($value) != 'null') { 443 + // When json_decode() fails, it returns null. This almost certainly 444 + // indicates that a user was using the web UI and didn't put quotes 445 + // around a string value. We can either do what we think they meant 446 + // (treat it as a string) or fail. For now, err on the side of 447 + // caution and fail. In the future, if we make the Conduit API 448 + // actually do type checking, it might be reasonable to treat it as 449 + // a string if the parameter type is string. 450 + throw new Exception( 451 + "The value for parameter '{$key}' is not valid JSON. All ". 452 + "parameters must be encoded as JSON values, including strings ". 453 + "(which means you need to surround them in double quotes). ". 454 + "Check your syntax. Value was: {$value}"); 455 + } 456 + $params[$key] = $decoded_value; 457 + } 458 + 459 + return $params; 460 + } 461 + 462 + // Otherwise, look for a single parameter called 'params' which has the 463 + // entire param dictionary JSON encoded. This is the usual case for remote 464 + // requests. 465 + 466 + $params_json = $request->getStr('params'); 467 + if (!strlen($params_json)) { 468 + $params = array(); 469 + } else { 470 + $params = json_decode($params_json, true); 471 + if (!is_array($params)) { 472 + throw new Exception( 473 + "Invalid parameter information was passed to method ". 474 + "'{$method}', could not decode JSON serialization."); 475 + } 476 + } 477 + 478 + return $params; 479 + } 478 480 }