diff --git a/src/applications/conduit/controller/PhabricatorConduitConsoleController.php b/src/applications/conduit/controller/PhabricatorConduitConsoleController.php index e570861f29..e878365acb 100644 --- a/src/applications/conduit/controller/PhabricatorConduitConsoleController.php +++ b/src/applications/conduit/controller/PhabricatorConduitConsoleController.php @@ -1,207 +1,209 @@ getViewer(); $method_name = $request->getURIData('method'); $method = id(new PhabricatorConduitMethodQuery()) ->setViewer($viewer) ->withMethods(array($method_name)) ->executeOne(); if (!$method) { return new Aphront404Response(); } + $method->setViewer($viewer); + $call_uri = '/api/'.$method->getAPIMethodName(); $status = $method->getMethodStatus(); $reason = $method->getMethodStatusDescription(); $errors = array(); switch ($status) { case ConduitAPIMethod::METHOD_STATUS_DEPRECATED: $reason = nonempty($reason, pht('This method is deprecated.')); $errors[] = pht('Deprecated Method: %s', $reason); break; case ConduitAPIMethod::METHOD_STATUS_UNSTABLE: $reason = nonempty( $reason, pht( 'This method is new and unstable. Its interface is subject '. 'to change.')); $errors[] = pht('Unstable Method: %s', $reason); break; } $form = id(new AphrontFormView()) ->setAction($call_uri) ->setUser($request->getUser()) ->appendRemarkupInstructions( pht( 'Enter parameters using **JSON**. For instance, to enter a '. 'list, type: `%s`', '["apple", "banana", "cherry"]')); $params = $method->getParamTypes(); foreach ($params as $param => $desc) { $form->appendChild( id(new AphrontFormTextControl()) ->setLabel($param) ->setName("params[{$param}]") ->setCaption($desc)); } $must_login = !$viewer->isLoggedIn() && $method->shouldRequireAuthentication(); if ($must_login) { $errors[] = pht( 'Login Required: This method requires authentication. You must '. 'log in before you can make calls to it.'); } else { $form ->appendChild( id(new AphrontFormSelectControl()) ->setLabel(pht('Output Format')) ->setName('output') ->setOptions( array( 'human' => pht('Human Readable'), 'json' => pht('JSON'), ))) ->appendChild( id(new AphrontFormSubmitControl()) ->addCancelButton($this->getApplicationURI()) ->setValue(pht('Call Method'))); } $header = id(new PHUIHeaderView()) ->setUser($viewer) ->setHeader($method->getAPIMethodName()); $form_box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Call Method')) ->setForm($form); $content = array(); $properties = $this->buildMethodProperties($method); $info_box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('API Method: %s', $method->getAPIMethodName())) ->setFormErrors($errors) ->appendChild($properties); $content[] = $info_box; $content[] = $form_box; $content[] = $this->renderExampleBox($method, null); $query = $method->newQueryObject(); if ($query) { $orders = $query->getBuiltinOrders(); $rows = array(); foreach ($orders as $key => $order) { $rows[] = array( $key, $order['name'], implode(', ', $order['vector']), ); } $table = id(new AphrontTableView($rows)) ->setHeaders( array( pht('Key'), pht('Description'), pht('Columns'), )) ->setColumnClasses( array( 'pri', '', 'wide', )); $content[] = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Builtin Orders')) ->setTable($table); $columns = $query->getOrderableColumns(); $rows = array(); foreach ($columns as $key => $column) { $rows[] = array( $key, idx($column, 'unique') ? pht('Yes') : pht('No'), ); } $table = id(new AphrontTableView($rows)) ->setHeaders( array( pht('Key'), pht('Unique'), )) ->setColumnClasses( array( 'pri', 'wide', )); $content[] = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Column Orders')) ->setTable($table); } $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($method->getAPIMethodName()); return $this->buildApplicationPage( array( $crumbs, $content, ), array( 'title' => $method->getAPIMethodName(), )); } private function buildMethodProperties(ConduitAPIMethod $method) { $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()); $view->addProperty( pht('Returns'), $method->getReturnType()); $error_types = $method->getErrorTypes(); $error_types['ERR-CONDUIT-CORE'] = pht('See error message for details.'); $error_description = array(); foreach ($error_types as $error => $meaning) { $error_description[] = hsprintf( '
  • %s: %s
  • ', $error, $meaning); } $error_description = phutil_tag('ul', array(), $error_description); $view->addProperty( pht('Errors'), $error_description); $view->addSectionHeader( pht('Description'), PHUIPropertyListView::ICON_SUMMARY); $view->addTextContent( new PHUIRemarkupView($viewer, $method->getMethodDescription())); return $view; } } diff --git a/src/applications/conduit/method/ConduitAPIMethod.php b/src/applications/conduit/method/ConduitAPIMethod.php index afa75c53b3..8471ac8919 100644 --- a/src/applications/conduit/method/ConduitAPIMethod.php +++ b/src/applications/conduit/method/ConduitAPIMethod.php @@ -1,382 +1,392 @@ getMethodDescription(); } /** * Get a detailed description of the method. * * This method should return remarkup. * * @return string Detailed description of the method. * @task info */ abstract public function getMethodDescription(); abstract protected function defineParamTypes(); abstract protected function defineReturnType(); protected function defineErrorTypes() { return array(); } abstract protected function execute(ConduitAPIRequest $request); - public function __construct() {} - public function getParamTypes() { $types = $this->defineParamTypes(); $query = $this->newQueryObject(); if ($query) { $types['order'] = 'optional order'; $types += $this->getPagerParamTypes(); } return $types; } public function getReturnType() { return $this->defineReturnType(); } public function getErrorTypes() { return $this->defineErrorTypes(); } /** * This is mostly for compatibility with * @{class:PhabricatorCursorPagedPolicyAwareQuery}. */ public function getID() { return $this->getAPIMethodName(); } /** * Get the status for this method (e.g., stable, unstable or deprecated). * Should return a METHOD_STATUS_* constant. By default, methods are * "stable". * * @return const METHOD_STATUS_* constant. * @task status */ public function getMethodStatus() { return self::METHOD_STATUS_STABLE; } /** * Optional description to supplement the method status. In particular, if * a method is deprecated, you can return a string here describing the reason * for deprecation and stable alternatives. * * @return string|null Description of the method status, if available. * @task status */ public function getMethodStatusDescription() { return null; } public function getErrorDescription($error_code) { return idx($this->getErrorTypes(), $error_code, pht('Unknown Error')); } public function getRequiredScope() { // by default, conduit methods are not accessible via OAuth return PhabricatorOAuthServerScope::SCOPE_NOT_ACCESSIBLE; } public function executeMethod(ConduitAPIRequest $request) { + $this->setViewer($request->getUser()); + return $this->execute($request); } abstract public function getAPIMethodName(); /** * Return a key which sorts methods by application name, then method status, * then method name. */ public function getSortOrder() { $name = $this->getAPIMethodName(); $map = array( self::METHOD_STATUS_STABLE => 0, self::METHOD_STATUS_UNSTABLE => 1, self::METHOD_STATUS_DEPRECATED => 2, ); $ord = idx($map, $this->getMethodStatus(), 0); list($head, $tail) = explode('.', $name, 2); return "{$head}.{$ord}.{$tail}"; } public function getApplicationName() { return head(explode('.', $this->getAPIMethodName(), 2)); } public static function loadAllConduitMethods() { return id(new PhutilClassMapQuery()) ->setAncestorClass(__CLASS__) ->setUniqueMethod('getAPIMethodName') ->execute(); } public static function getConduitMethod($method_name) { $method_map = self::loadAllConduitMethods(); return idx($method_map, $method_name); } public function shouldRequireAuthentication() { return true; } public function shouldAllowPublic() { return false; } public function shouldAllowUnguardedWrites() { return false; } /** * Optionally, return a @{class:PhabricatorApplication} which this call is * part of. The call will be disabled when the application is uninstalled. * * @return PhabricatorApplication|null Related application. */ public function getApplication() { return null; } protected function formatStringConstants($constants) { foreach ($constants as $key => $value) { $constants[$key] = '"'.$value.'"'; } $constants = implode(', ', $constants); return 'string-constant<'.$constants.'>'; } public static function getParameterMetadataKey($key) { if (strncmp($key, 'api.', 4) === 0) { // All keys passed beginning with "api." are always metadata keys. return substr($key, 4); } else { switch ($key) { // These are real keys which always belong to request metadata. case 'access_token': case 'scope': case 'output': // This is not a real metadata key; it is included here only to // prevent Conduit methods from defining it. case '__conduit__': // This is prevented globally as a blanket defense against OAuth // redirection attacks. It is included here to stop Conduit methods // from defining it. case 'code': // This is not a real metadata key, but the presence of this // parameter triggers an alternate request decoding pathway. case 'params': return $key; } } return null; } + final public function setViewer(PhabricatorUser $viewer) { + $this->viewer = $viewer; + return $this; + } + + final public function getViewer() { + return $this->viewer; + } + /* -( Paging Results )----------------------------------------------------- */ /** * @task pager */ protected function getPagerParamTypes() { return array( 'before' => 'optional string', 'after' => 'optional string', 'limit' => 'optional int (default = 100)', ); } /** * @task pager */ protected function newPager(ConduitAPIRequest $request) { $limit = $request->getValue('limit', 100); $limit = min(1000, $limit); $limit = max(1, $limit); $pager = id(new AphrontCursorPagerView()) ->setPageSize($limit); $before_id = $request->getValue('before'); if ($before_id !== null) { $pager->setBeforeID($before_id); } $after_id = $request->getValue('after'); if ($after_id !== null) { $pager->setAfterID($after_id); } return $pager; } /** * @task pager */ protected function addPagerResults( array $results, AphrontCursorPagerView $pager) { $results['cursor'] = array( 'limit' => $pager->getPageSize(), 'after' => $pager->getNextPageID(), 'before' => $pager->getPrevPageID(), ); return $results; } /* -( Implementing Query Methods )----------------------------------------- */ public function newQueryObject() { return null; } protected function newQueryForRequest(ConduitAPIRequest $request) { $query = $this->newQueryObject(); if (!$query) { throw new Exception( pht( 'You can not call newQueryFromRequest() in this method ("%s") '. 'because it does not implement newQueryObject().', get_class($this))); } if (!($query instanceof PhabricatorCursorPagedPolicyAwareQuery)) { throw new Exception( pht( 'Call to method newQueryObject() did not return an object of class '. '"%s".', 'PhabricatorCursorPagedPolicyAwareQuery')); } $query->setViewer($request->getUser()); $order = $request->getValue('order'); if ($order !== null) { if (is_scalar($order)) { $query->setOrder($order); } else { $query->setOrderVector($order); } } return $query; } /* -( PhabricatorPolicyInterface )----------------------------------------- */ public function getPHID() { return null; } public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, ); } public function getPolicy($capability) { // Application methods get application visibility; other methods get open // visibility. $application = $this->getApplication(); if ($application) { return $application->getPolicy($capability); } return PhabricatorPolicies::getMostOpenPolicy(); } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { if (!$this->shouldRequireAuthentication()) { // Make unauthenticated methods universally visible. return true; } return false; } public function describeAutomaticCapability($capability) { return null; } protected function hasApplicationCapability( $capability, PhabricatorUser $viewer) { $application = $this->getApplication(); if (!$application) { return false; } return PhabricatorPolicyFilter::hasCapability( $viewer, $application, $capability); } protected function requireApplicationCapability( $capability, PhabricatorUser $viewer) { $application = $this->getApplication(); if (!$application) { return; } PhabricatorPolicyFilter::requireCapability( $viewer, $this->getApplication(), $capability); } } diff --git a/src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php b/src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php index ae27cb07c0..1ef3e02f33 100644 --- a/src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php +++ b/src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php @@ -1,389 +1,389 @@ newSearchEngine(); $class = $engine->getApplicationClassName(); return PhabricatorApplication::getByClass($class); } public function getMethodStatus() { return self::METHOD_STATUS_UNSTABLE; } public function getMethodStatusDescription() { return pht('ApplicationSearch methods are highly unstable.'); } final protected function defineParamTypes() { return array( 'queryKey' => 'optional string', 'constraints' => 'optional map', 'order' => 'optional order', ) + $this->getPagerParamTypes(); } final protected function defineReturnType() { return 'map'; } final protected function execute(ConduitAPIRequest $request) { $engine = $this->newSearchEngine() ->setViewer($request->getUser()); return $engine->buildConduitResponse($request); } final public function getMethodDescription() { - // TODO: We don't currently have a real viewer in this method. - $viewer = PhabricatorUser::getOmnipotentUser(); + $viewer = $this->getViewer(); $engine = $this->newSearchEngine() ->setViewer($viewer); $query = $engine->newQuery(); $out = array(); $out[] = pht(<<loadAllNamedQueries(); $table = array(); $table[] = "| {$head_querykey} | {$head_name} | {$head_builtin} |"; $table[] = '|------------------|--------------|-----------------|'; foreach ($named_queries as $named_query) { $key = $named_query->getQueryKey(); $name = $named_query->getQueryName(); $builtin = $named_query->getIsBuiltin() ? pht('Builtin') : pht('Custom'); $table[] = "| `{$key}` | {$name} | {$builtin} |"; } $table = implode("\n", $table); $out[] = $table; $out[] = pht(<<getSearchFieldsForConduit(); $table = array(); $table[] = "| {$head_key} | {$head_label} | {$head_type} | {$head_desc} |"; $table[] = '|-------------|---------------|--------------|--------------|'; $table[] = "| `ids` | **IDs** | `list` | {$desc_ids} |"; $table[] = "| `phids` | **PHIDs** | `list` | {$desc_phids} |"; foreach ($fields as $field) { $key = $field->getKeyForConduit(); $label = $field->getLabel(); // TODO: Support generating and surfacing this information. $type = pht('TODO'); $description = pht('TODO'); $table[] = "| `{$key}` | **{$label}** | `{$type}` | {$description}"; } $table = implode("\n", $table); $out[] = $table; $out[] = pht(<<getBuiltinOrders(); $table = array(); $table[] = "| {$head_builtin} | {$head_description} | {$head_columns} |"; $table[] = '|-----------------|---------------------|-----------------|'; foreach ($orders as $key => $order) { $name = $order['name']; $columns = implode(', ', $order['vector']); $table[] = "| `{$key}` | {$name} | {$columns} |"; } $table = implode("\n", $table); $out[] = $table; $out[] = pht(<<getOrderableColumns(); $table = array(); $table[] = "| {$head_column} | {$head_unique} |"; $table[] = '|----------------|----------------|'; foreach ($columns as $key => $column) { $unique = idx($column, 'unique') ? pht('Yes') : pht('No'); $table[] = "| `{$key}` | {$unique} |"; } $table = implode("\n", $table); $out[] = $table; $out[] = pht(<<getAllConduitFieldSpecifications(); $table = array(); $table[] = "| {$head_key} | {$head_type} | {$head_description} |"; $table[] = '|-------------|--------------|---------------------|'; foreach ($specs as $key => $spec) { $type = idx($spec, 'type'); $description = idx($spec, 'description'); $table[] = "| `{$key}` | `{$type}` | {$description} |"; } $table = implode("\n", $table); $out[] = $table; $out[] = pht(<<newEditEngine(); $class = $engine->getEngineApplicationClass(); return PhabricatorApplication::getByClass($class); } public function getMethodStatus() { return self::METHOD_STATUS_UNSTABLE; } public function getMethodStatusDescription() { return pht('ApplicationEditor methods are highly unstable.'); } final protected function defineParamTypes() { return array( 'transactions' => 'list>', 'objectIdentifier' => 'optional id|phid|string', ); } final protected function defineReturnType() { return 'map'; } final protected function execute(ConduitAPIRequest $request) { $engine = $this->newEditEngine() ->setViewer($request->getUser()); return $engine->buildConduitResponse($request); } final public function getMethodDescription() { - // TODO: We don't currently have a real viewer in this method. - $viewer = PhabricatorUser::getOmnipotentUser(); + $viewer = $this->getViewer(); $engine = $this->newEditEngine() ->setViewer($viewer); $types = $engine->getConduitEditTypes(); $out = array(); $out[] = pht(<<getEditType(); $edit_summary = $type->getSummary(); $table[] = "| `{$edit_type}` | {$edit_summary} |"; } $out[] = implode("\n", $table); foreach ($types as $type) { $section = array(); $section[] = pht('Edit Type: %s', $type->getEditType()); $section[] = '---------'; $section[] = null; $section[] = $type->getDescription(); $section[] = null; $section[] = pht( 'This edit generates transactions of type `%s` internally.', $type->getTransactionType()); $section[] = null; $type_description = pht( 'Use `%s` to select this edit type.', $type->getEditType()); $value_type = $type->getValueType(); if (!strlen($value_type)) { $value_type = '?'; } $value_description = $type->getValueDescription(); $table = array(); $table[] = "| {$key} | {$head_type} | {$description} |"; $table[] = '|--------|--------------|----------------|'; $table[] = "| `type` | `const` | {$type_description} |"; $table[] = "| `value` | `{$value_type}` | {$value_description} |"; $section[] = implode("\n", $table); $out[] = implode("\n", $section); } $out = implode("\n\n", $out); return $out; } }