@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
at upstream/main 257 lines 5.9 kB view raw
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}