Page MenuHomestyx hydra

No OneTemporary

diff --git a/scripts/ssh/ssh-exec.php b/scripts/ssh/ssh-exec.php
index 4a46e7562d..c3a85b399c 100755
--- a/scripts/ssh/ssh-exec.php
+++ b/scripts/ssh/ssh-exec.php
@@ -1,339 +1,347 @@
#!/usr/bin/env php
<?php
$ssh_start_time = microtime(true);
$root = dirname(dirname(dirname(__FILE__)));
require_once $root.'/scripts/init/init-script.php';
$error_log = id(new PhutilErrorLog())
->setLogName(pht('SSH Error Log'))
->setLogPath(PhabricatorEnv::getEnvConfig('log.ssh-error.path'))
->activateLog();
$ssh_log = PhabricatorSSHLog::getLog();
$request_identifier = Filesystem::readRandomCharacters(12);
$ssh_log->setData(
array(
'Q' => $request_identifier,
));
$args = new PhutilArgumentParser($argv);
$args->setTagline(pht('execute SSH requests'));
$args->setSynopsis(<<<EOSYNOPSIS
**ssh-exec** --phabricator-ssh-user __user__ [--ssh-command __commmand__]
**ssh-exec** --phabricator-ssh-device __device__ [--ssh-command __commmand__]
Execute authenticated SSH requests. This script is normally invoked
via SSHD, but can be invoked manually for testing.
EOSYNOPSIS
);
$args->parseStandardArguments();
$args->parse(
array(
array(
'name' => 'phabricator-ssh-user',
'param' => 'username',
'help' => pht(
'If the request authenticated with a user key, the name of the '.
'user.'),
),
array(
'name' => 'phabricator-ssh-device',
'param' => 'name',
'help' => pht(
'If the request authenticated with a device key, the name of the '.
'device.'),
),
array(
'name' => 'phabricator-ssh-key',
'param' => 'id',
'help' => pht(
'The ID of the SSH key which authenticated this request. This is '.
'used to allow logs to report when specific keys were used, to make '.
'it easier to manage credentials.'),
),
array(
'name' => 'ssh-command',
'param' => 'command',
'help' => pht(
'Provide a command to execute. This makes testing this script '.
'easier. When running normally, the command is read from the '.
'environment (%s), which is populated by sshd.',
'SSH_ORIGINAL_COMMAND'),
),
));
try {
$remote_address = null;
$ssh_client = getenv('SSH_CLIENT');
if ($ssh_client) {
// This has the format "<ip> <remote-port> <local-port>". Grab the IP.
$remote_address = head(explode(' ', $ssh_client));
$ssh_log->setData(
array(
'r' => $remote_address,
));
}
$key_id = $args->getArg('phabricator-ssh-key');
if ($key_id) {
$ssh_log->setData(
array(
'k' => $key_id,
));
}
$user_name = $args->getArg('phabricator-ssh-user');
$device_name = $args->getArg('phabricator-ssh-device');
$user = null;
$device = null;
$is_cluster_request = false;
if ($user_name && $device_name) {
throw new Exception(
pht(
'The %s and %s flags are mutually exclusive. You can not '.
'authenticate as both a user ("%s") and a device ("%s"). '.
'Specify one or the other, but not both.',
'--phabricator-ssh-user',
'--phabricator-ssh-device',
$user_name,
$device_name));
} else if (strlen($user_name)) {
$user = id(new PhabricatorPeopleQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withUsernames(array($user_name))
->executeOne();
if (!$user) {
throw new Exception(
pht(
'Invalid username ("%s"). There is no user with this username.',
$user_name));
}
id(new PhabricatorAuthSessionEngine())
->willServeRequestForUser($user);
} else if (strlen($device_name)) {
if (!$remote_address) {
throw new Exception(
pht(
'Unable to identify remote address from the %s environment '.
'variable. Device authentication is accepted only from trusted '.
'sources.',
'SSH_CLIENT'));
}
if (!PhabricatorEnv::isClusterAddress($remote_address)) {
throw new Exception(
pht(
'This request originates from outside of the Phabricator cluster '.
'address range. Requests signed with a trusted device key must '.
'originate from trusted hosts.'));
}
$device = id(new AlmanacDeviceQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withNames(array($device_name))
->executeOne();
if (!$device) {
throw new Exception(
pht(
'Invalid device name ("%s"). There is no device with this name.',
$device_name));
}
+ if ($device->isDisabled()) {
+ throw new Exception(
+ pht(
+ 'This request has authenticated as a device ("%s"), but this '.
+ 'device is disabled.',
+ $device->getName()));
+ }
+
// We're authenticated as a device, but we're going to read the user out of
// the command below.
$is_cluster_request = true;
} else {
throw new Exception(
pht(
'This script must be invoked with either the %s or %s flag.',
'--phabricator-ssh-user',
'--phabricator-ssh-device'));
}
if ($args->getArg('ssh-command')) {
$original_command = $args->getArg('ssh-command');
} else {
$original_command = getenv('SSH_ORIGINAL_COMMAND');
}
$original_argv = id(new PhutilShellLexer())
->splitArguments($original_command);
if ($device) {
// If we're authenticating as a device, the first argument may be a
// "@username" argument to act as a particular user.
$first_argument = head($original_argv);
if (preg_match('/^@/', $first_argument)) {
$act_as_name = array_shift($original_argv);
$act_as_name = substr($act_as_name, 1);
$user = id(new PhabricatorPeopleQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withUsernames(array($act_as_name))
->executeOne();
if (!$user) {
throw new Exception(
pht(
'Device request identifies an acting user with an invalid '.
'username ("%s"). There is no user with this username.',
$act_as_name));
}
} else {
$user = PhabricatorUser::getOmnipotentUser();
}
}
if ($user->isOmnipotent()) {
$user_name = 'device/'.$device->getName();
} else {
$user_name = $user->getUsername();
}
$ssh_log->setData(
array(
'u' => $user_name,
'P' => $user->getPHID(),
));
if (!$device) {
if (!$user->canEstablishSSHSessions()) {
throw new Exception(
pht(
'Your account ("%s") does not have permission to establish SSH '.
'sessions. Visit the web interface for more information.',
$user_name));
}
}
$workflows = id(new PhutilClassMapQuery())
->setAncestorClass('PhabricatorSSHWorkflow')
->setUniqueMethod('getName')
->execute();
$command_list = array_keys($workflows);
$command_list = implode(', ', $command_list);
$error_lines = array();
$error_lines[] = pht('Welcome to Phabricator.');
$error_lines[] = pht(
'You are logged in as %s.',
$user_name);
if (!$original_argv) {
$error_lines[] = pht(
'You have not specified a command to run. This means you are requesting '.
'an interactive shell, but Phabricator does not provide interactive '.
'shells over SSH.');
$error_lines[] = pht(
'(Usually, you should run a command like "git clone" or "hg push" '.
'instead of connecting directly with SSH.)');
$error_lines[] = pht(
'Supported commands are: %s.',
$command_list);
$error_lines = implode("\n\n", $error_lines);
throw new PhutilArgumentUsageException($error_lines);
}
$log_argv = implode(' ', $original_argv);
$log_argv = id(new PhutilUTF8StringTruncator())
->setMaximumCodepoints(128)
->truncateString($log_argv);
$ssh_log->setData(
array(
'C' => $original_argv[0],
'U' => $log_argv,
));
$command = head($original_argv);
$parseable_argv = $original_argv;
array_unshift($parseable_argv, 'phabricator-ssh-exec');
$parsed_args = new PhutilArgumentParser($parseable_argv);
if (empty($workflows[$command])) {
$error_lines[] = pht(
'You have specified the command "%s", but that command is not '.
'supported by Phabricator. As received by Phabricator, your entire '.
'argument list was:',
$command);
$error_lines[] = csprintf(' $ ssh ... -- %Ls', $parseable_argv);
$error_lines[] = pht(
'Supported commands are: %s.',
$command_list);
$error_lines = implode("\n\n", $error_lines);
throw new PhutilArgumentUsageException($error_lines);
}
$workflow = $parsed_args->parseWorkflows($workflows);
$workflow->setSSHUser($user);
$workflow->setOriginalArguments($original_argv);
$workflow->setIsClusterRequest($is_cluster_request);
$workflow->setRequestIdentifier($request_identifier);
$sock_stdin = fopen('php://stdin', 'r');
if (!$sock_stdin) {
throw new Exception(pht('Unable to open stdin.'));
}
$sock_stdout = fopen('php://stdout', 'w');
if (!$sock_stdout) {
throw new Exception(pht('Unable to open stdout.'));
}
$sock_stderr = fopen('php://stderr', 'w');
if (!$sock_stderr) {
throw new Exception(pht('Unable to open stderr.'));
}
$socket_channel = new PhutilSocketChannel(
$sock_stdin,
$sock_stdout);
$error_channel = new PhutilSocketChannel(null, $sock_stderr);
$metrics_channel = new PhutilMetricsChannel($socket_channel);
$workflow->setIOChannel($metrics_channel);
$workflow->setErrorChannel($error_channel);
$rethrow = null;
try {
$err = $workflow->execute($parsed_args);
$metrics_channel->flush();
$error_channel->flush();
} catch (Exception $ex) {
$rethrow = $ex;
}
// Always write this if we got as far as building a metrics channel.
$ssh_log->setData(
array(
'i' => $metrics_channel->getBytesRead(),
'o' => $metrics_channel->getBytesWritten(),
));
if ($rethrow) {
throw $rethrow;
}
} catch (Exception $ex) {
fwrite(STDERR, "phabricator-ssh-exec: ".$ex->getMessage()."\n");
$err = 1;
}
$ssh_log->setData(
array(
'c' => $err,
'T' => phutil_microseconds_since($ssh_start_time),
));
exit($err);
diff --git a/src/applications/conduit/controller/PhabricatorConduitAPIController.php b/src/applications/conduit/controller/PhabricatorConduitAPIController.php
index 9ca84fff3d..bc200e7078 100644
--- a/src/applications/conduit/controller/PhabricatorConduitAPIController.php
+++ b/src/applications/conduit/controller/PhabricatorConduitAPIController.php
@@ -1,745 +1,755 @@
<?php
final class PhabricatorConduitAPIController
extends PhabricatorConduitController {
public function shouldRequireLogin() {
return false;
}
public function handleRequest(AphrontRequest $request) {
$method = $request->getURIData('method');
$time_start = microtime(true);
$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, $strictly_typed) = $this->decodeConduitParams(
$request,
$method);
$call = new ConduitCall($method, $params, $strictly_typed);
$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()) {
$auth_error = $this->authenticateUser($api_request, $metadata, $method);
// 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) {
$result = null;
if ($ex instanceof ConduitException) {
$error_code = 'ERR-CONDUIT-CALL';
} else {
$error_code = 'ERR-CONDUIT-CORE';
// See T13581. When a Conduit method raises an uncaught exception
// other than a "ConduitException", log it.
phlog($ex);
}
$error_info = $ex->getMessage();
}
$log
->setCallerPHID(
isset($conduit_user)
? $conduit_user->getPHID()
: null)
->setError((string)$error_code)
->setDuration(phutil_microseconds_since($time_start));
if (!PhabricatorEnv::isReadOnly()) {
$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:
$response = id(new AphrontJSONResponse())
->setAddJSONShield(false)
->setContent($response->toDictionary());
$capabilities = $this->getConduitCapabilities();
if ($capabilities) {
$capabilities = implode(' ', $capabilities);
$response->addHeader('X-Conduit-Capabilities', $capabilities);
}
return $response;
}
}
/**
* Authenticate the client making the request to a Phabricator user account.
*
* @param ConduitAPIRequest Request being executed.
* @param dict Request metadata.
* @return null|pair Null to indicate successful authentication, or
* an error code and error message pair.
*/
private function authenticateUser(
ConduitAPIRequest $api_request,
array $metadata,
$method) {
$request = $this->getRequest();
if ($request->getUser()->getPHID()) {
$request->validateCSRF();
return $this->validateAuthenticatedUser(
$api_request,
$request->getUser());
}
$auth_type = idx($metadata, 'auth.type');
if ($auth_type === ConduitClient::AUTH_ASYMMETRIC) {
$host = idx($metadata, 'auth.host');
if (!$host) {
return array(
'ERR-INVALID-AUTH',
pht(
'Request is missing required "%s" parameter.',
'auth.host'),
);
}
// TODO: Validate that we are the host!
$raw_key = idx($metadata, 'auth.key');
$public_key = PhabricatorAuthSSHPublicKey::newFromRawKey($raw_key);
$ssl_public_key = $public_key->toPKCS8();
// First, verify the signature.
try {
$protocol_data = $metadata;
ConduitClient::verifySignature(
$method,
$api_request->getAllParameters(),
$protocol_data,
$ssl_public_key);
} catch (Exception $ex) {
return array(
'ERR-INVALID-AUTH',
pht(
'Signature verification failure. %s',
$ex->getMessage()),
);
}
// If the signature is valid, find the user or device which is
// associated with this public key.
$stored_key = id(new PhabricatorAuthSSHKeyQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withKeys(array($public_key))
->withIsActive(true)
->executeOne();
if (!$stored_key) {
$key_summary = id(new PhutilUTF8StringTruncator())
->setMaximumBytes(64)
->truncateString($raw_key);
return array(
'ERR-INVALID-AUTH',
pht(
'No user or device is associated with the public key "%s".',
$key_summary),
);
}
$object = $stored_key->getObject();
if ($object instanceof PhabricatorUser) {
$user = $object;
} else {
+ if ($object->isDisabled()) {
+ return array(
+ 'ERR-INVALID-AUTH',
+ pht(
+ 'The key which signed this request is associated with a '.
+ 'disabled device ("%s").',
+ $object->getName()),
+ );
+ }
+
if (!$stored_key->getIsTrusted()) {
return array(
'ERR-INVALID-AUTH',
pht(
'The key which signed this request is not trusted. Only '.
'trusted keys can be used to sign API calls.'),
);
}
if (!PhabricatorEnv::isClusterRemoteAddress()) {
return array(
'ERR-INVALID-AUTH',
pht(
'This request originates from outside of the Phabricator '.
'cluster address range. Requests signed with trusted '.
'device keys must originate from within the cluster.'),
);
}
$user = PhabricatorUser::getOmnipotentUser();
// Flag this as an intracluster request.
$api_request->setIsClusterRequest(true);
}
return $this->validateAuthenticatedUser(
$api_request,
$user);
} else if ($auth_type === null) {
// No specified authentication type, continue with other authentication
// methods below.
} else {
return array(
'ERR-INVALID-AUTH',
pht(
'Provided "%s" ("%s") is not recognized.',
'auth.type',
$auth_type),
);
}
$token_string = idx($metadata, 'token');
if (strlen($token_string)) {
if (strlen($token_string) != 32) {
return array(
'ERR-INVALID-AUTH',
pht(
'API token "%s" has the wrong length. API tokens should be '.
'32 characters long.',
$token_string),
);
}
$type = head(explode('-', $token_string));
$valid_types = PhabricatorConduitToken::getAllTokenTypes();
$valid_types = array_fuse($valid_types);
if (empty($valid_types[$type])) {
return array(
'ERR-INVALID-AUTH',
pht(
'API token "%s" has the wrong format. API tokens should be '.
'32 characters long and begin with one of these prefixes: %s.',
$token_string,
implode(', ', $valid_types)),
);
}
$token = id(new PhabricatorConduitTokenQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withTokens(array($token_string))
->withExpired(false)
->executeOne();
if (!$token) {
$token = id(new PhabricatorConduitTokenQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withTokens(array($token_string))
->withExpired(true)
->executeOne();
if ($token) {
return array(
'ERR-INVALID-AUTH',
pht(
'API token "%s" was previously valid, but has expired.',
$token_string),
);
} else {
return array(
'ERR-INVALID-AUTH',
pht(
'API token "%s" is not valid.',
$token_string),
);
}
}
// If this is a "cli-" token, it expires shortly after it is generated
// by default. Once it is actually used, we extend its lifetime and make
// it permanent. This allows stray tokens to get cleaned up automatically
// if they aren't being used.
if ($token->getTokenType() == PhabricatorConduitToken::TYPE_COMMANDLINE) {
if ($token->getExpires()) {
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
$token->setExpires(null);
$token->save();
unset($unguarded);
}
}
// If this is a "clr-" token, Phabricator must be configured in cluster
// mode and the remote address must be a cluster node.
if ($token->getTokenType() == PhabricatorConduitToken::TYPE_CLUSTER) {
if (!PhabricatorEnv::isClusterRemoteAddress()) {
return array(
'ERR-INVALID-AUTH',
pht(
'This request originates from outside of the Phabricator '.
'cluster address range. Requests signed with cluster API '.
'tokens must originate from within the cluster.'),
);
}
// Flag this as an intracluster request.
$api_request->setIsClusterRequest(true);
}
$user = $token->getObject();
if (!($user instanceof PhabricatorUser)) {
return array(
'ERR-INVALID-AUTH',
pht('API token is not associated with a valid user.'),
);
}
return $this->validateAuthenticatedUser(
$api_request,
$user);
}
$access_token = idx($metadata, 'access_token');
if ($access_token) {
$token = id(new PhabricatorOAuthServerAccessToken())
->loadOneWhere('token = %s', $access_token);
if (!$token) {
return array(
'ERR-INVALID-AUTH',
pht('Access token does not exist.'),
);
}
$oauth_server = new PhabricatorOAuthServer();
$authorization = $oauth_server->authorizeToken($token);
if (!$authorization) {
return array(
'ERR-INVALID-AUTH',
pht('Access token is invalid or expired.'),
);
}
$user = id(new PhabricatorPeopleQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withPHIDs(array($token->getUserPHID()))
->executeOne();
if (!$user) {
return array(
'ERR-INVALID-AUTH',
pht('Access token is for invalid user.'),
);
}
$ok = $this->authorizeOAuthMethodAccess($authorization, $method);
if (!$ok) {
return array(
'ERR-OAUTH-ACCESS',
pht('You do not have authorization to call this method.'),
);
}
$api_request->setOAuthToken($token);
return $this->validateAuthenticatedUser(
$api_request,
$user);
}
// For intracluster requests, use a public user if no authentication
// information is provided. We could do this safely for any request,
// but making the API fully public means there's no way to disable badly
// behaved clients.
if (PhabricatorEnv::isClusterRemoteAddress()) {
if (PhabricatorEnv::getEnvConfig('policy.allow-public')) {
$api_request->setIsClusterRequest(true);
$user = new PhabricatorUser();
return $this->validateAuthenticatedUser(
$api_request,
$user);
}
}
// Handle sessionless auth.
// TODO: This is super messy.
// TODO: Remove this in favor of token-based auth.
if (isset($metadata['authUser'])) {
$user = id(new PhabricatorUser())->loadOneWhere(
'userName = %s',
$metadata['authUser']);
if (!$user) {
return array(
'ERR-INVALID-AUTH',
pht('Authentication is invalid.'),
);
}
$token = idx($metadata, 'authToken');
$signature = idx($metadata, 'authSignature');
$certificate = $user->getConduitCertificate();
$hash = sha1($token.$certificate);
if (!phutil_hashes_are_identical($hash, $signature)) {
return array(
'ERR-INVALID-AUTH',
pht('Authentication is invalid.'),
);
}
return $this->validateAuthenticatedUser(
$api_request,
$user);
}
// Handle session-based auth.
// TODO: Remove this in favor of token-based auth.
$session_key = idx($metadata, 'sessionKey');
if (!$session_key) {
return array(
'ERR-INVALID-SESSION',
pht('Session key is not present.'),
);
}
$user = id(new PhabricatorAuthSessionEngine())
->loadUserForSession(PhabricatorAuthSession::TYPE_CONDUIT, $session_key);
if (!$user) {
return array(
'ERR-INVALID-SESSION',
pht('Session key is invalid.'),
);
}
return $this->validateAuthenticatedUser(
$api_request,
$user);
}
private function validateAuthenticatedUser(
ConduitAPIRequest $request,
PhabricatorUser $user) {
if (!$user->canEstablishAPISessions()) {
return array(
'ERR-INVALID-AUTH',
pht('User account is not permitted to use the API.'),
);
}
$request->setUser($user);
id(new PhabricatorAuthSessionEngine())
->willServeRequestForUser($user);
return null;
}
private function buildHumanReadableResponse(
$method,
ConduitAPIRequest $request = null,
$result = null,
ConduitAPIMethod $method_implementation = null) {
$param_rows = array();
$param_rows[] = array('Method', $this->renderAPIValue($method));
if ($request) {
foreach ($request->getAllParameters() as $key => $value) {
$param_rows[] = array(
$key,
$this->renderAPIValue($value),
);
}
}
$param_table = new AphrontTableView($param_rows);
$param_table->setColumnClasses(
array(
'header',
'wide',
));
$result_rows = array();
foreach ($result as $key => $value) {
$result_rows[] = array(
$key,
$this->renderAPIValue($value),
);
}
$result_table = new AphrontTableView($result_rows);
$result_table->setColumnClasses(
array(
'header',
'wide',
));
$param_panel = id(new PHUIObjectBoxView())
->setHeaderText(pht('Method Parameters'))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setTable($param_table);
$result_panel = id(new PHUIObjectBoxView())
->setHeaderText(pht('Method Result'))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setTable($result_table);
$method_uri = $this->getApplicationURI('method/'.$method.'/');
$crumbs = $this->buildApplicationCrumbs()
->addTextCrumb($method, $method_uri)
->addTextCrumb(pht('Call'))
->setBorder(true);
$example_panel = null;
if ($request && $method_implementation) {
$params = $request->getAllParameters();
$example_panel = $this->renderExampleBox(
$method_implementation,
$params);
}
$title = pht('Method Call Result');
$header = id(new PHUIHeaderView())
->setHeader($title)
->setHeaderIcon('fa-exchange');
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setFooter(array(
$param_panel,
$result_panel,
$example_panel,
));
$title = pht('Method Call Result');
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->appendChild($view);
}
private function renderAPIValue($value) {
$json = new PhutilJSON();
if (is_array($value)) {
$value = $json->encodeFormatted($value);
}
$value = phutil_tag(
'pre',
array('style' => 'white-space: pre-wrap;'),
$value);
return $value;
}
private function decodeConduitParams(
AphrontRequest $request,
$method) {
$content_type = $request->getHTTPHeader('Content-Type');
if ($content_type == 'application/json') {
throw new Exception(
pht('Use form-encoded data to submit parameters to Conduit endpoints. '.
'Sending a JSON-encoded body and setting \'Content-Type\': '.
'\'application/json\' is not currently supported.'));
}
// Look for parameters from the Conduit API Console, which are encoded
// as HTTP POST parameters in an array, e.g.:
//
// params[name]=value&params[name2]=value2
//
// The fields are individually JSON encoded, since we require users to
// enter JSON so that we avoid type ambiguity.
$params = $request->getArr('params', null);
if ($params !== null) {
foreach ($params as $key => $value) {
if ($value == '') {
// Interpret empty string null (e.g., the user didn't type anything
// into the box).
$value = 'null';
}
$decoded_value = json_decode($value, true);
if ($decoded_value === null && strtolower($value) != 'null') {
// When json_decode() fails, it returns null. This almost certainly
// indicates that a user was using the web UI and didn't put quotes
// around a string value. We can either do what we think they meant
// (treat it as a string) or fail. For now, err on the side of
// caution and fail. In the future, if we make the Conduit API
// actually do type checking, it might be reasonable to treat it as
// a string if the parameter type is string.
throw new Exception(
pht(
"The value for parameter '%s' is not valid JSON. All ".
"parameters must be encoded as JSON values, including strings ".
"(which means you need to surround them in double quotes). ".
"Check your syntax. Value was: %s.",
$key,
$value));
}
$params[$key] = $decoded_value;
}
$metadata = idx($params, '__conduit__', array());
unset($params['__conduit__']);
return array($metadata, $params, true);
}
// Otherwise, look for a single parameter called 'params' which has the
// entire param dictionary JSON encoded.
$params_json = $request->getStr('params');
if (strlen($params_json)) {
$params = null;
try {
$params = phutil_json_decode($params_json);
} catch (PhutilJSONParserException $ex) {
throw new PhutilProxyException(
pht(
"Invalid parameter information was passed to method '%s'.",
$method),
$ex);
}
$metadata = idx($params, '__conduit__', array());
unset($params['__conduit__']);
return array($metadata, $params, true);
}
// If we do not have `params`, assume this is a simple HTTP request with
// HTTP key-value pairs.
$params = array();
$metadata = array();
foreach ($request->getPassthroughRequestData() as $key => $value) {
$meta_key = ConduitAPIMethod::getParameterMetadataKey($key);
if ($meta_key !== null) {
$metadata[$meta_key] = $value;
} else {
$params[$key] = $value;
}
}
return array($metadata, $params, false);
}
private function authorizeOAuthMethodAccess(
PhabricatorOAuthClientAuthorization $authorization,
$method_name) {
$method = ConduitAPIMethod::getConduitMethod($method_name);
if (!$method) {
return false;
}
$required_scope = $method->getRequiredScope();
switch ($required_scope) {
case ConduitAPIMethod::SCOPE_ALWAYS:
return true;
case ConduitAPIMethod::SCOPE_NEVER:
return false;
}
$authorization_scope = $authorization->getScope();
if (!empty($authorization_scope[$required_scope])) {
return true;
}
return false;
}
private function getConduitCapabilities() {
$capabilities = array();
if (AphrontRequestStream::supportsGzip()) {
$capabilities[] = 'gzip';
}
return $capabilities;
}
}

File Metadata

Mime Type
text/x-diff
Expires
Sun, Nov 24, 12:12 PM (1 d, 11 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1008
Default Alt Text
(33 KB)

Event Timeline