@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<?php
2
3/**
4 * Run a conduit method in-process, without requiring HTTP requests. Usage:
5 *
6 * $call = new ConduitCall('method.name', array('param' => 'value'));
7 * $call->setUser($user);
8 * $result = $call->execute();
9 *
10 */
11final class ConduitCall extends Phobject {
12
13 private $method;
14 private $handler;
15 private $request;
16 private $user;
17
18 public function __construct($method, array $params, $strictly_typed = true) {
19 $this->method = $method;
20 $this->handler = $this->buildMethodHandler($method);
21
22 $param_types = $this->handler->getParamTypes();
23
24 foreach ($param_types as $key => $spec) {
25 if (ConduitAPIMethod::getParameterMetadataKey($key) !== null) {
26 throw new ConduitException(
27 pht(
28 'API Method "%s" defines a disallowed parameter, "%s". This '.
29 'parameter name is reserved.',
30 $method,
31 $key));
32 }
33 }
34
35 $invalid_params = array_diff_key($params, $param_types);
36 if ($invalid_params) {
37 throw new ConduitException(
38 pht(
39 'API Method "%s" does not define these parameters: %s.',
40 $method,
41 "'".implode("', '", array_keys($invalid_params))."'"));
42 }
43
44 $this->request = new ConduitAPIRequest($params, $strictly_typed);
45 }
46
47 public function getAPIRequest() {
48 return $this->request;
49 }
50
51 public function setUser(PhabricatorUser $user) {
52 $this->user = $user;
53 return $this;
54 }
55
56 public function getUser() {
57 return $this->user;
58 }
59
60 public function shouldRequireAuthentication() {
61 return $this->handler->shouldRequireAuthentication();
62 }
63
64 public function shouldAllowUnguardedWrites() {
65 return $this->handler->shouldAllowUnguardedWrites();
66 }
67
68 public function getErrorDescription($code) {
69 return $this->handler->getErrorDescription($code);
70 }
71
72 public function execute() {
73 $profiler = PhutilServiceProfiler::getInstance();
74 $call_id = $profiler->beginServiceCall(
75 array(
76 'type' => 'conduit',
77 'method' => $this->method,
78 ));
79
80 try {
81 $result = $this->executeMethod();
82 } catch (Exception $ex) {
83 $profiler->endServiceCall($call_id, array());
84 throw $ex;
85 }
86
87 $profiler->endServiceCall($call_id, array());
88 return $result;
89 }
90
91 private function executeMethod() {
92 $user = $this->getUser();
93 if (!$user) {
94 $user = new PhabricatorUser();
95 }
96
97 $this->request->setUser($user);
98
99 if (!$this->shouldRequireAuthentication()) {
100 // No auth requirement here.
101 } else {
102
103 $allow_public = $this->handler->shouldAllowPublic() &&
104 PhabricatorEnv::getEnvConfig('policy.allow-public');
105 if (!$allow_public) {
106 if (!$user->isLoggedIn() && !$user->isOmnipotent()) {
107 // TODO: As per below, this should get centralized and cleaned up.
108 throw new ConduitException('ERR-INVALID-AUTH');
109 }
110 }
111
112 // TODO: This would be slightly cleaner by just using a Query, but the
113 // Conduit auth workflow requires the Call and User be built separately.
114 // Just do it this way for the moment.
115 $application = $this->handler->getApplication();
116 if ($application) {
117 $can_view = PhabricatorPolicyFilter::hasCapability(
118 $user,
119 $application,
120 PhabricatorPolicyCapability::CAN_VIEW);
121
122 if (!$can_view) {
123 throw new ConduitException(
124 pht(
125 'You do not have access to the application which provides this '.
126 'API method.'));
127 }
128 }
129 }
130
131 return $this->handler->executeMethod($this->request);
132 }
133
134 protected function buildMethodHandler($method_name) {
135 $method = ConduitAPIMethod::getConduitMethod($method_name);
136
137 if (!$method) {
138 throw new ConduitMethodDoesNotExistException($method_name);
139 }
140
141 $application = $method->getApplication();
142 if ($application && !$application->isInstalled()) {
143 $app_name = $application->getName();
144 throw new ConduitApplicationNotInstalledException($method, $app_name);
145 }
146
147 return $method;
148 }
149
150 public function getMethodImplementation() {
151 return $this->handler;
152 }
153
154
155}