Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2892874
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Advanced/Developer...
View Handle
View Hovercard
Size
11 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/applications/conduit/call/ConduitCall.php b/src/applications/conduit/call/ConduitCall.php
index 1b98dccb47..f4cac28956 100644
--- a/src/applications/conduit/call/ConduitCall.php
+++ b/src/applications/conduit/call/ConduitCall.php
@@ -1,163 +1,186 @@
<?php
/**
* Run a conduit method in-process, without requiring HTTP requests. Usage:
*
* $call = new ConduitCall('method.name', array('param' => 'value'));
* $call->setUser($user);
* $result = $call->execute();
*
*/
final class ConduitCall {
private $method;
private $request;
private $user;
private $servers;
private $forceLocal;
public function __construct($method, array $params) {
$this->method = $method;
$this->handler = $this->buildMethodHandler($method);
$this->servers = PhabricatorEnv::getEnvConfig('conduit.servers');
$this->forceLocal = false;
$invalid_params = array_diff_key(
$params,
$this->handler->defineParamTypes());
if ($invalid_params) {
throw new ConduitException(
"Method '{$method}' doesn't define these parameters: '" .
implode("', '", array_keys($invalid_params)) . "'.");
}
if ($this->servers) {
$current_host = AphrontRequest::getHTTPHeader('HOST');
foreach ($this->servers as $server) {
if ($current_host === id(new PhutilURI($server))->getDomain()) {
$this->forceLocal = true;
break;
}
}
}
$this->request = new ConduitAPIRequest($params);
}
public function setUser(PhabricatorUser $user) {
$this->user = $user;
return $this;
}
public function getUser() {
return $this->user;
}
public function setForceLocal($force_local) {
$this->forceLocal = $force_local;
return $this;
}
public function shouldForceLocal() {
return $this->forceLocal;
}
public function shouldRequireAuthentication() {
return $this->handler->shouldRequireAuthentication();
}
public function shouldAllowUnguardedWrites() {
return $this->handler->shouldAllowUnguardedWrites();
}
public function getRequiredScope() {
return $this->handler->getRequiredScope();
}
public function getErrorDescription($code) {
return $this->handler->getErrorDescription($code);
}
public function execute() {
- if (!$this->getUser()) {
- if ($this->shouldRequireAuthentication()) {
+ $user = $this->getUser();
+ if (!$user) {
+ $user = new PhabricatorUser();
+ }
+
+ $this->request->setUser($user);
+
+ if ($this->shouldRequireAuthentication()) {
+ if (!$user->isLoggedIn()) {
throw new ConduitException("ERR-INVALID-AUTH");
}
- } else {
- $this->request->setUser($this->getUser());
+
+ // TODO: This would be slightly cleaner by just using a Query, but the
+ // Conduit auth workflow requires the Call and User be built separately.
+ // Just do it this way for the moment.
+ $application = $this->handler->getApplication();
+ if ($application) {
+ $can_view = PhabricatorPolicyFilter::hasCapability(
+ $user,
+ $application,
+ PhabricatorPolicyCapability::CAN_VIEW);
+
+ if (!$can_view) {
+ throw new ConduitException(
+ pht(
+ "You do not have access to the application which provides this ".
+ "API method."));
+ }
+ }
}
if (!$this->shouldForceLocal() && $this->servers) {
$server = $this->pickRandomServer($this->servers);
$client = new ConduitClient($server);
$params = $this->request->getAllParameters();
$params["__conduit__"]["isProxied"] = true;
if ($this->handler->shouldRequireAuthentication()) {
$client->callMethodSynchronous(
'conduit.connect',
array(
'client' => 'PhabricatorConduit',
'clientVersion' => '1.0',
'user' => $this->getUser()->getUserName(),
'certificate' => $this->getUser()->getConduitCertificate(),
'__conduit__' => $params["__conduit__"],
));
}
return $client->callMethodSynchronous(
$this->method,
$params);
} else {
return $this->handler->executeMethod($this->request);
}
}
protected function pickRandomServer($servers) {
return $servers[array_rand($servers)];
}
protected function buildMethodHandler($method) {
$method_class = ConduitAPIMethod::getClassNameFromAPIMethodName($method);
// Test if the method exists.
$ok = false;
try {
$ok = class_exists($method_class);
} catch (Exception $ex) {
// Discard, we provide a more specific exception below.
}
if (!$ok) {
throw new ConduitException(
"Conduit method '{$method}' does not exist.");
}
$class_info = new ReflectionClass($method_class);
if ($class_info->isAbstract()) {
throw new ConduitException(
"Method '{$method}' is not valid; the implementation is an abstract ".
"base class.");
}
$method = newv($method_class, array());
if (!($method instanceof ConduitAPIMethod)) {
throw new ConduitException(
"Method '{$method_class}' is not valid; the implementation must be ".
"a subclass of ConduitAPIMethod.");
}
$application = $method->getApplication();
if ($application && !$application->isInstalled()) {
$app_name = $application->getName();
throw new ConduitException(
"Method '{$method_class}' belongs to application '{$app_name}', ".
"which is not installed.");
}
return $method;
}
}
diff --git a/src/applications/conduit/method/ConduitAPIMethod.php b/src/applications/conduit/method/ConduitAPIMethod.php
index 2b81af6555..3a9f389b5f 100644
--- a/src/applications/conduit/method/ConduitAPIMethod.php
+++ b/src/applications/conduit/method/ConduitAPIMethod.php
@@ -1,194 +1,204 @@
<?php
/**
*
* @task status Method Status
* @group conduit
*/
abstract class ConduitAPIMethod
extends Phobject
implements PhabricatorPolicyInterface {
const METHOD_STATUS_STABLE = 'stable';
const METHOD_STATUS_UNSTABLE = 'unstable';
const METHOD_STATUS_DEPRECATED = 'deprecated';
abstract public function getMethodDescription();
abstract public function defineParamTypes();
abstract public function defineReturnType();
abstract public function defineErrorTypes();
abstract protected function execute(ConduitAPIRequest $request);
public function __construct() {
}
/**
* 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->defineErrorTypes(), $error_code, 'Unknown Error');
}
public function getRequiredScope() {
// by default, conduit methods are not accessible via OAuth
return PhabricatorOAuthServerScope::SCOPE_NOT_ACCESSIBLE;
}
public function executeMethod(ConduitAPIRequest $request) {
return $this->execute($request);
}
public function getAPIMethodName() {
return self::getAPIMethodNameFromClassName(get_class($this));
}
/**
* Return a key which sorts methods by application name, then method status,
* then method name.
*/
public function getSortOrder() {
$name = $this->getAPIMethodName();
$map = array(
ConduitAPIMethod::METHOD_STATUS_STABLE => 0,
ConduitAPIMethod::METHOD_STATUS_UNSTABLE => 1,
ConduitAPIMethod::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 getClassNameFromAPIMethodName($method_name) {
$method_fragment = str_replace('.', '_', $method_name);
return 'ConduitAPI_'.$method_fragment.'_Method';
}
public function shouldRequireAuthentication() {
return true;
}
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;
}
public static function getAPIMethodNameFromClassName($class_name) {
$match = null;
$is_valid = preg_match(
'/^ConduitAPI_(.*)_Method$/',
$class_name,
$match);
if (!$is_valid) {
throw new Exception(
"Parameter '{$class_name}' is not a valid Conduit API method class.");
}
$method_fragment = $match[1];
return str_replace('_', '.', $method_fragment);
}
protected function validateHost($host) {
if (!$host) {
// If the client doesn't send a host key, don't complain. We should in
// the future, but this change isn't severe enough to bump the protocol
// version.
// TODO: Remove this once the protocol version gets bumped past 2 (i.e.,
// require the host key be present and valid).
return;
}
// NOTE: Compare domains only so we aren't sensitive to port specification
// or omission.
$host = new PhutilURI($host);
$host = $host->getDomain();
$self = new PhutilURI(PhabricatorEnv::getURI('/'));
$self = $self->getDomain();
if ($self !== $host) {
throw new Exception(
"Your client is connecting to this install as '{$host}', but it is ".
"configured as '{$self}'. The client and server must use the exact ".
"same URI to identify the install. Edit your .arcconfig or ".
"phabricator/conf so they agree on the URI for the install.");
}
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getPHID() {
return null;
}
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
);
}
public function getPolicy($capability) {
- return PhabricatorPolicies::POLICY_USER;
+ // 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) {
- // The policy interface on Conduit calls is currently just to let us hook
- // into ApplicationSearch. Calls are always visible (even to logged out
- // users).
- return true;
+ if (!$this->shouldRequireAuthentication()) {
+ // Make unauthenticated methods univerally visible.
+ return true;
+ }
+
+ return false;
}
public function describeAutomaticCapability($capability) {
return null;
}
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sun, Jan 19, 17:29 (1 w, 5 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1126971
Default Alt Text
(11 KB)
Attached To
Mode
rP Phorge
Attached
Detach File
Event Timeline
Log In to Comment