Page MenuHomePhorge

No OneTemporary

diff --git a/src/applications/conduit/call/ConduitCall.php b/src/applications/conduit/call/ConduitCall.php
index b9e5a1491a..0b819bd093 100644
--- a/src/applications/conduit/call/ConduitCall.php
+++ b/src/applications/conduit/call/ConduitCall.php
@@ -1,187 +1,194 @@
<?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() {
$user = $this->getUser();
if (!$user) {
$user = new PhabricatorUser();
}
$this->request->setUser($user);
- if ($this->shouldRequireAuthentication()) {
- // TODO: As per below, this should get centralized and cleaned up.
- if (!$user->isLoggedIn() && !$user->isOmnipotent()) {
- throw new ConduitException("ERR-INVALID-AUTH");
+ if (!$this->shouldRequireAuthentication()) {
+ // No auth requirement here.
+ } else {
+
+ $allow_public = $this->handler->shouldAllowPublic() &&
+ PhabricatorEnv::getEnvConfig('policy.allow-public');
+ if (!$allow_public) {
+ if (!$user->isLoggedIn() && !$user->isOmnipotent()) {
+ // TODO: As per below, this should get centralized and cleaned up.
+ throw new ConduitException("ERR-INVALID-AUTH");
+ }
}
// 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 3a9f389b5f..e549f5755b 100644
--- a/src/applications/conduit/method/ConduitAPIMethod.php
+++ b/src/applications/conduit/method/ConduitAPIMethod.php
@@ -1,204 +1,208 @@
<?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 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;
}
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) {
// 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 univerally visible.
return true;
}
return false;
}
public function describeAutomaticCapability($capability) {
return null;
}
}
diff --git a/src/applications/diffusion/conduit/ConduitAPI_diffusion_abstractquery_Method.php b/src/applications/diffusion/conduit/ConduitAPI_diffusion_abstractquery_Method.php
index 3b84520d86..c4e48779b7 100644
--- a/src/applications/diffusion/conduit/ConduitAPI_diffusion_abstractquery_Method.php
+++ b/src/applications/diffusion/conduit/ConduitAPI_diffusion_abstractquery_Method.php
@@ -1,174 +1,179 @@
<?php
/**
* @group conduit
*/
abstract class ConduitAPI_diffusion_abstractquery_Method
extends ConduitAPI_diffusion_Method {
+ public function shouldAllowPublic() {
+ return true;
+ }
+
public function getMethodStatus() {
return self::METHOD_STATUS_UNSTABLE;
}
+
public function getMethodStatusDescription() {
return pht(
'See T2784 - migrating diffusion working copy calls to conduit methods. '.
'Until that task is completed (and possibly after) these methods are '.
'unstable.');
}
private $diffusionRequest;
private $repository;
private $shouldCreateDiffusionRequest = true;
protected function setDiffusionRequest(DiffusionRequest $request) {
$this->diffusionRequest = $request;
return $this;
}
protected function getDiffusionRequest() {
return $this->diffusionRequest;
}
/**
* A wee bit of magic here. If @{method:shouldCreateDiffusionRequest}
* returns false, this function grabs a repository object based on the
* callsign directly. Otherwise, the repository was loaded when we created a
* @{class:DiffusionRequest}, so this function just pulls it out of the
* @{class:DiffusionRequest}.
*
* @return @{class:PhabricatorRepository} $repository
*/
protected function getRepository(ConduitAPIRequest $request) {
if (!$this->repository) {
if ($this->shouldCreateDiffusionRequest()) {
$this->repository = $this->getDiffusionRequest()->getRepository();
} else {
$callsign = $request->getValue('callsign');
$repository = id(new PhabricatorRepositoryQuery())
->setViewer($request->getUser())
->withCallsigns(array($callsign))
->executeOne();
if (!$repository) {
throw new ConduitException('ERR-UNKNOWN-REPOSITORY');
}
$this->repository = $repository;
}
}
return $this->repository;
}
/**
* You should probably not mess with this unless your conduit method is
* involved with the creation / validation / etc. of
* @{class:DiffusionRequest}s. If you are dealing with
* @{class:DiffusionRequest}, setting this to false should help avoid
* infinite loops.
*/
protected function setShouldCreateDiffusionRequest($should) {
$this->shouldCreateDiffusionRequest = $should;
return $this;
}
private function shouldCreateDiffusionRequest() {
return $this->shouldCreateDiffusionRequest;
}
final public function defineErrorTypes() {
return $this->defineCustomErrorTypes() +
array(
'ERR-UNKNOWN-REPOSITORY' =>
pht('There is no repository with that callsign.'),
'ERR-UNKNOWN-VCS-TYPE' =>
pht('Unknown repository VCS type.'),
'ERR-UNSUPPORTED-VCS' =>
pht('VCS is not supported for this method.'));
}
/**
* Subclasses should override this to specify custom error types.
*/
protected function defineCustomErrorTypes() {
return array();
}
final public function defineParamTypes() {
return $this->defineCustomParamTypes() +
array(
'callsign' => 'required string',
'branch' => 'optional string',
);
}
/**
* Subclasses should override this to specify custom param types.
*/
protected function defineCustomParamTypes() {
return array();
}
/**
* Subclasses should override these methods with the proper result for the
* pertinent version control system, e.g. getGitResult for Git.
*
* If the result is not supported for that VCS, do not implement it. e.g.
* Subversion (SVN) does not support branches.
*/
protected function getGitResult(ConduitAPIRequest $request) {
throw new ConduitException('ERR-UNSUPPORTED-VCS');
}
protected function getSVNResult(ConduitAPIRequest $request) {
throw new ConduitException('ERR-UNSUPPORTED-VCS');
}
protected function getMercurialResult(ConduitAPIRequest $request) {
throw new ConduitException('ERR-UNSUPPORTED-VCS');
}
/**
* This method is final because most queries will need to construct a
* @{class:DiffusionRequest} and use it. Consolidating this codepath and
* enforcing @{method:getDiffusionRequest} works when we need it is good.
*
* @{method:getResult} should be overridden by subclasses as necessary, e.g.
* there is a common operation across all version control systems that
* should occur after @{method:getResult}, like formatting a timestamp.
*
* In the rare cases where one does not want to create a
* @{class:DiffusionRequest} - suppose to avoid infinite loops in the
* creation of a @{class:DiffusionRequest} - make sure to call
*
* $this->setShouldCreateDiffusionRequest(false);
*
* in the constructor of the pertinent Conduit method.
*/
final protected function execute(ConduitAPIRequest $request) {
if ($this->shouldCreateDiffusionRequest()) {
$drequest = DiffusionRequest::newFromDictionary(
array(
'user' => $request->getUser(),
'callsign' => $request->getValue('callsign'),
'branch' => $request->getValue('branch'),
'path' => $request->getValue('path'),
'commit' => $request->getValue('commit'),
));
$this->setDiffusionRequest($drequest);
}
return $this->getResult($request);
}
protected function getResult(ConduitAPIRequest $request) {
$repository = $this->getRepository($request);
$result = null;
switch ($repository->getVersionControlSystem()) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
$result = $this->getGitResult($request);
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
$result = $this->getMercurialResult($request);
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
$result = $this->getSVNResult($request);
break;
default:
throw new ConduitException('ERR-UNKNOWN-VCS-TYPE');
break;
}
return $result;
}
}

File Metadata

Mime Type
text/x-diff
Expires
Sun, Jan 19, 16:02 (2 w, 6 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1126297
Default Alt Text
(18 KB)

Event Timeline