@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
3final class PhabricatorNotificationServerRef
4 extends Phobject {
5
6 private $type;
7 private $host;
8 private $port;
9 private $protocol;
10 private $path;
11 private $isDisabled;
12
13 const KEY_REFS = 'notification.refs';
14
15 public function setType($type) {
16 $this->type = $type;
17 return $this;
18 }
19
20 public function getType() {
21 return $this->type;
22 }
23
24 public function setHost($host) {
25 $this->host = $host;
26 return $this;
27 }
28
29 public function getHost() {
30 return $this->host;
31 }
32
33 public function setPort($port) {
34 $this->port = $port;
35 return $this;
36 }
37
38 public function getPort() {
39 return $this->port;
40 }
41
42 public function setProtocol($protocol) {
43 $this->protocol = $protocol;
44 return $this;
45 }
46
47 public function getProtocol() {
48 return $this->protocol;
49 }
50
51 public function setPath($path) {
52 $this->path = $path;
53 return $this;
54 }
55
56 public function getPath() {
57 return $this->path;
58 }
59
60 public function setIsDisabled($is_disabled) {
61 $this->isDisabled = $is_disabled;
62 return $this;
63 }
64
65 public function getIsDisabled() {
66 return $this->isDisabled;
67 }
68
69 public static function getLiveServers() {
70 $cache = PhabricatorCaches::getRequestCache();
71
72 $refs = $cache->getKey(self::KEY_REFS);
73 if (!$refs) {
74 $refs = self::newRefs();
75 $cache->setKey(self::KEY_REFS, $refs);
76 }
77
78 return $refs;
79 }
80
81 public static function newRefs() {
82 $configs = PhabricatorEnv::getEnvConfig('notification.servers');
83
84 $refs = array();
85 foreach ($configs as $config) {
86 $ref = id(new self())
87 ->setType($config['type'])
88 ->setHost($config['host'])
89 ->setPort($config['port'])
90 ->setProtocol($config['protocol'])
91 ->setPath(idx($config, 'path'))
92 ->setIsDisabled(idx($config, 'disabled', false));
93 $refs[] = $ref;
94 }
95
96 return $refs;
97 }
98
99 public static function getEnabledServers() {
100 $servers = self::getLiveServers();
101
102 foreach ($servers as $key => $server) {
103 if ($server->getIsDisabled()) {
104 unset($servers[$key]);
105 }
106 }
107
108 return array_values($servers);
109 }
110
111 public static function getEnabledAdminServers() {
112 $servers = self::getEnabledServers();
113
114 foreach ($servers as $key => $server) {
115 if (!$server->isAdminServer()) {
116 unset($servers[$key]);
117 }
118 }
119
120 return array_values($servers);
121 }
122
123 public static function getEnabledClientServers($with_protocol) {
124 $servers = self::getEnabledServers();
125
126 foreach ($servers as $key => $server) {
127 if ($server->isAdminServer()) {
128 unset($servers[$key]);
129 continue;
130 }
131
132 $protocol = $server->getProtocol();
133 if ($protocol != $with_protocol) {
134 unset($servers[$key]);
135 continue;
136 }
137 }
138
139 return array_values($servers);
140 }
141
142 public function isAdminServer() {
143 return ($this->type == 'admin');
144 }
145
146 public function getURI($to_path = '') {
147 $path = coalesce($this->path, '');
148 $to_path = coalesce($to_path, '');
149 $full_path = rtrim($path, '/').'/'.ltrim($to_path, '/');
150
151 $uri = id(new PhutilURI('http://'.$this->getHost()))
152 ->setProtocol($this->getProtocol())
153 ->setPort($this->getPort())
154 ->setPath($full_path);
155
156 $instance = PhabricatorEnv::getEnvConfig('cluster.instance');
157 if (phutil_nonempty_string($instance)) {
158 $uri->replaceQueryParam('instance', $instance);
159 }
160
161 return $uri;
162 }
163
164 public function getWebsocketURI($to_path = '') {
165 $instance = PhabricatorEnv::getEnvConfig('cluster.instance');
166 if (phutil_nonempty_string($instance)) {
167 $to_path = $to_path.'~'.$instance.'/';
168 }
169
170 $uri = $this->getURI($to_path);
171
172 if ($this->getProtocol() == 'https') {
173 $uri->setProtocol('wss');
174 } else {
175 $uri->setProtocol('ws');
176 }
177
178 return $uri;
179 }
180
181 public function testClient() {
182 if ($this->isAdminServer()) {
183 throw new Exception(
184 pht('Unable to test client on an admin server!'));
185 }
186
187 $server_uri = $this->getURI();
188
189 try {
190 id(new HTTPSFuture($server_uri))
191 ->setTimeout(2)
192 ->resolvex();
193 } catch (HTTPFutureHTTPResponseStatus $ex) {
194 // This is what we expect when things are working correctly.
195 if ($ex->getStatusCode() == 501) {
196 return true;
197 }
198 throw $ex;
199 }
200
201 throw new Exception(
202 pht('Got HTTP 200, but expected HTTP 501 (WebSocket Upgrade)!'));
203 }
204
205 public function loadServerStatus() {
206 if (!$this->isAdminServer()) {
207 throw new Exception(
208 pht(
209 'Unable to load server status: this is not an admin server!'));
210 }
211
212 $server_uri = $this->getURI('/status/');
213
214 list($body) = $this->newFuture($server_uri)
215 ->resolvex();
216
217 return phutil_json_decode($body);
218 }
219
220 public function postMessage(array $data) {
221 if (!$this->isAdminServer()) {
222 throw new Exception(
223 pht('Unable to post message: this is not an admin server!'));
224 }
225
226 $server_uri = $this->getURI('/');
227 $payload = phutil_json_encode($data);
228
229 $this->newFuture($server_uri, $payload)
230 ->setMethod('POST')
231 ->resolvex();
232 }
233
234 private function newFuture($uri, $data = null) {
235 if ($data === null) {
236 $future = new HTTPSFuture($uri);
237 } else {
238 $future = new HTTPSFuture($uri, $data);
239 }
240
241 $future->setTimeout(2);
242
243 // At one point, a HackerOne researcher reported a "Location:" redirect
244 // attack here (if the attacker can gain control of the notification
245 // server or the configuration).
246
247 // Although this attack is not particularly concerning, we don't expect
248 // Aphlict to ever issue a "Location:" header, so receiving one indicates
249 // something is wrong and declining to follow the header may make debugging
250 // easier.
251
252 $future->setFollowLocation(false);
253
254 return $future;
255 }
256
257}