Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/notification/client/PhabricatorNotificationServerRef.php
12256 views
1
<?php
2
3
final 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 = null) {
147
if ($to_path === null || !strlen($to_path)) {
148
$to_path = '';
149
} else {
150
$to_path = ltrim($to_path, '/');
151
}
152
153
$base_path = $this->getPath();
154
if ($base_path === null || !strlen($base_path)) {
155
$base_path = '';
156
} else {
157
$base_path = rtrim($base_path, '/');
158
}
159
$full_path = $base_path.'/'.$to_path;
160
161
$uri = id(new PhutilURI('http://'.$this->getHost()))
162
->setProtocol($this->getProtocol())
163
->setPort($this->getPort())
164
->setPath($full_path);
165
166
$instance = PhabricatorEnv::getEnvConfig('cluster.instance');
167
if ($instance !== null && strlen($instance)) {
168
$uri->replaceQueryParam('instance', $instance);
169
}
170
171
return $uri;
172
}
173
174
public function getWebsocketURI($to_path = null) {
175
$instance = PhabricatorEnv::getEnvConfig('cluster.instance');
176
if ($instance !== null && strlen($instance)) {
177
$to_path = $to_path.'~'.$instance.'/';
178
}
179
180
$uri = $this->getURI($to_path);
181
182
if ($this->getProtocol() == 'https') {
183
$uri->setProtocol('wss');
184
} else {
185
$uri->setProtocol('ws');
186
}
187
188
return $uri;
189
}
190
191
public function testClient() {
192
if ($this->isAdminServer()) {
193
throw new Exception(
194
pht('Unable to test client on an admin server!'));
195
}
196
197
$server_uri = $this->getURI();
198
199
try {
200
id(new HTTPSFuture($server_uri))
201
->setTimeout(2)
202
->resolvex();
203
} catch (HTTPFutureHTTPResponseStatus $ex) {
204
// This is what we expect when things are working correctly.
205
if ($ex->getStatusCode() == 501) {
206
return true;
207
}
208
throw $ex;
209
}
210
211
throw new Exception(
212
pht('Got HTTP 200, but expected HTTP 501 (WebSocket Upgrade)!'));
213
}
214
215
public function loadServerStatus() {
216
if (!$this->isAdminServer()) {
217
throw new Exception(
218
pht(
219
'Unable to load server status: this is not an admin server!'));
220
}
221
222
$server_uri = $this->getURI('/status/');
223
224
list($body) = $this->newFuture($server_uri)
225
->resolvex();
226
227
return phutil_json_decode($body);
228
}
229
230
public function postMessage(array $data) {
231
if (!$this->isAdminServer()) {
232
throw new Exception(
233
pht('Unable to post message: this is not an admin server!'));
234
}
235
236
$server_uri = $this->getURI('/');
237
$payload = phutil_json_encode($data);
238
239
$this->newFuture($server_uri, $payload)
240
->setMethod('POST')
241
->resolvex();
242
}
243
244
private function newFuture($uri, $data = null) {
245
if ($data === null) {
246
$future = new HTTPSFuture($uri);
247
} else {
248
$future = new HTTPSFuture($uri, $data);
249
}
250
251
$future->setTimeout(2);
252
253
// At one point, a HackerOne researcher reported a "Location:" redirect
254
// attack here (if the attacker can gain control of the notification
255
// server or the configuration).
256
257
// Although this attack is not particularly concerning, we don't expect
258
// Aphlict to ever issue a "Location:" header, so receiving one indicates
259
// something is wrong and declining to follow the header may make debugging
260
// easier.
261
262
$future->setFollowLocation(false);
263
264
return $future;
265
}
266
267
}
268
269