PageRenderTime 47ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/src/applications/notification/client/PhabricatorNotificationServerRef.php

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