Page MenuHomePhorge

No OneTemporary

diff --git a/src/applications/config/option/PhabricatorNotificationConfigOptions.php b/src/applications/config/option/PhabricatorNotificationConfigOptions.php
index 9f06bc3c2d..e92017d2c5 100644
--- a/src/applications/config/option/PhabricatorNotificationConfigOptions.php
+++ b/src/applications/config/option/PhabricatorNotificationConfigOptions.php
@@ -1,63 +1,64 @@
<?php
final class PhabricatorNotificationConfigOptions
extends PhabricatorApplicationConfigOptions {
public function getName() {
return pht('Notifications');
}
public function getDescription() {
return pht('Configure real-time notifications.');
}
public function getIcon() {
return 'fa-bell';
}
public function getGroup() {
return 'core';
}
public function getOptions() {
$servers_type = 'cluster.notifications';
$servers_help = $this->deformat(pht(<<<EOTEXT
Provide a list of notification servers to enable real-time notifications.
For help setting up notification servers, see **[[ %s | %s ]]** in the
documentation.
EOTEXT
,
PhabricatorEnv::getDoclink(
'Notifications User Guide: Setup and Configuration'),
pht('Notifications User Guide: Setup and Configuration')));
$servers_example1 = array(
array(
'type' => 'client',
'host' => 'phabricator.mycompany.com',
'port' => 22280,
'protocol' => 'https',
),
array(
'type' => 'admin',
'host' => '127.0.0.1',
'port' => 22281,
'protocol' => 'http',
),
);
$servers_example1 = id(new PhutilJSON())->encodeAsList(
$servers_example1);
return array(
$this->newOption('notification.servers', $servers_type, array())
+ ->setHidden(true)
->setSummary(pht('Configure real-time notifications.'))
->setDescription($servers_help)
->addExample(
$servers_example1,
pht('Simple Example')),
);
}
}
diff --git a/src/applications/notification/client/PhabricatorNotificationServerRef.php b/src/applications/notification/client/PhabricatorNotificationServerRef.php
index 46d03a5c3a..714ad5f5d7 100644
--- a/src/applications/notification/client/PhabricatorNotificationServerRef.php
+++ b/src/applications/notification/client/PhabricatorNotificationServerRef.php
@@ -1,234 +1,255 @@
<?php
final class PhabricatorNotificationServerRef
extends Phobject {
private $type;
private $host;
private $port;
private $protocol;
private $path;
private $isDisabled;
const KEY_REFS = 'notification.refs';
public function setType($type) {
$this->type = $type;
return $this;
}
public function getType() {
return $this->type;
}
public function setHost($host) {
$this->host = $host;
return $this;
}
public function getHost() {
return $this->host;
}
public function setPort($port) {
$this->port = $port;
return $this;
}
public function getPort() {
return $this->port;
}
public function setProtocol($protocol) {
$this->protocol = $protocol;
return $this;
}
public function getProtocol() {
return $this->protocol;
}
public function setPath($path) {
$this->path = $path;
return $this;
}
public function getPath() {
return $this->path;
}
public function setIsDisabled($is_disabled) {
$this->isDisabled = $is_disabled;
return $this;
}
public function getIsDisabled() {
return $this->isDisabled;
}
public static function getLiveServers() {
$cache = PhabricatorCaches::getRequestCache();
$refs = $cache->getKey(self::KEY_REFS);
if (!$refs) {
$refs = self::newRefs();
$cache->setKey(self::KEY_REFS, $refs);
}
return $refs;
}
public static function newRefs() {
$configs = PhabricatorEnv::getEnvConfig('notification.servers');
$refs = array();
foreach ($configs as $config) {
$ref = id(new self())
->setType($config['type'])
->setHost($config['host'])
->setPort($config['port'])
->setProtocol($config['protocol'])
->setPath(idx($config, 'path'))
->setIsDisabled(idx($config, 'disabled', false));
$refs[] = $ref;
}
return $refs;
}
public static function getEnabledServers() {
$servers = self::getLiveServers();
foreach ($servers as $key => $server) {
if ($server->getIsDisabled()) {
unset($servers[$key]);
}
}
return array_values($servers);
}
public static function getEnabledAdminServers() {
$servers = self::getEnabledServers();
foreach ($servers as $key => $server) {
if (!$server->isAdminServer()) {
unset($servers[$key]);
}
}
return array_values($servers);
}
public static function getEnabledClientServers($with_protocol) {
$servers = self::getEnabledServers();
foreach ($servers as $key => $server) {
if ($server->isAdminServer()) {
unset($servers[$key]);
continue;
}
$protocol = $server->getProtocol();
if ($protocol != $with_protocol) {
unset($servers[$key]);
continue;
}
}
return array_values($servers);
}
public function isAdminServer() {
return ($this->type == 'admin');
}
public function getURI($to_path = null) {
$full_path = rtrim($this->getPath(), '/').'/'.ltrim($to_path, '/');
$uri = id(new PhutilURI('http://'.$this->getHost()))
->setProtocol($this->getProtocol())
->setPort($this->getPort())
->setPath($full_path);
$instance = PhabricatorEnv::getEnvConfig('cluster.instance');
if (strlen($instance)) {
$uri->replaceQueryParam('instance', $instance);
}
return $uri;
}
public function getWebsocketURI($to_path = null) {
$instance = PhabricatorEnv::getEnvConfig('cluster.instance');
if (strlen($instance)) {
$to_path = $to_path.'~'.$instance.'/';
}
$uri = $this->getURI($to_path);
if ($this->getProtocol() == 'https') {
$uri->setProtocol('wss');
} else {
$uri->setProtocol('ws');
}
return $uri;
}
public function testClient() {
if ($this->isAdminServer()) {
throw new Exception(
pht('Unable to test client on an admin server!'));
}
$server_uri = $this->getURI();
try {
id(new HTTPSFuture($server_uri))
->setTimeout(2)
->resolvex();
} catch (HTTPFutureHTTPResponseStatus $ex) {
// This is what we expect when things are working correctly.
if ($ex->getStatusCode() == 501) {
return true;
}
throw $ex;
}
throw new Exception(
pht('Got HTTP 200, but expected HTTP 501 (WebSocket Upgrade)!'));
}
public function loadServerStatus() {
if (!$this->isAdminServer()) {
throw new Exception(
pht(
'Unable to load server status: this is not an admin server!'));
}
$server_uri = $this->getURI('/status/');
- list($body) = id(new HTTPSFuture($server_uri))
- ->setTimeout(2)
+ list($body) = $this->newFuture($server_uri)
->resolvex();
return phutil_json_decode($body);
}
public function postMessage(array $data) {
if (!$this->isAdminServer()) {
throw new Exception(
pht('Unable to post message: this is not an admin server!'));
}
$server_uri = $this->getURI('/');
$payload = phutil_json_encode($data);
- id(new HTTPSFuture($server_uri, $payload))
+ $this->newFuture($server_uri, $payload)
->setMethod('POST')
- ->setTimeout(2)
->resolvex();
}
+ private function newFuture($uri, $data = null) {
+ if ($data === null) {
+ $future = new HTTPSFuture($uri);
+ } else {
+ $future = new HTTPSFuture($uri, $data);
+ }
+
+ $future->setTimeout(2);
+
+ // At one point, a HackerOne researcher reported a "Location:" redirect
+ // attack here (if the attacker can gain control of the notification
+ // server or the configuration).
+
+ // Although this attack is not particularly concerning, we don't expect
+ // Aphlict to ever issue a "Location:" header, so receiving one indicates
+ // something is wrong and declining to follow the header may make debugging
+ // easier.
+
+ $future->setFollowLocation(false);
+
+ return $future;
+ }
+
}
diff --git a/src/docs/user/configuration/configuration_locked.diviner b/src/docs/user/configuration/configuration_locked.diviner
index f96adc2d82..57ed76c5c7 100644
--- a/src/docs/user/configuration/configuration_locked.diviner
+++ b/src/docs/user/configuration/configuration_locked.diviner
@@ -1,170 +1,176 @@
@title Configuration Guide: Locked and Hidden Configuration
@group config
Details about locked and hidden configuration.
Overview
========
Some configuration options are **Locked** or **Hidden**. If an option has one
of these attributes, it means:
- **Locked Configuration**: This setting can not be written from the web UI.
- **Hidden Configuration**: This setting can not be read or written from
the web UI.
This document explains these attributes in more detail.
Locked Configuration
====================
**Locked Configuration** can not be edited from the web UI. In general, you
can edit it from the CLI instead, with `bin/config`:
```
phabricator/ $ ./bin/config set <key> <value>
```
Some configuration options take complicated values which can be difficult
to escape properly for the shell. The easiest way to set these options is
to use the `--stdin` flag. First, put your desired value in a `config.json`
file:
```name=config.json, lang=json
{
"duck": "quack",
"cow": "moo"
}
```
Then, set it with `--stdin` like this:
```
phabricator/ $ ./bin/config set <key> --stdin < config.json
```
A few settings have alternate CLI tools. Refer to the setting page for
details.
Note that these settings can not be written to the database, even from the
CLI.
Locked values can not be unlocked: they are locked because of what the setting
does or how the setting operates. Some of the reasons configuration options are
locked include:
**Required for bootstrapping**: Some options, like `mysql.host`, must be
available before Phabricator can read configuration from the database.
If you stored `mysql.host` only in the database, Phabricator would not know how
to connect to the database in order to read the value in the first place.
These options must be provided in a configuration source which is read earlier
in the bootstrapping process, before Phabricator connects to the database.
**Errors could not be fixed from the web UI**: Some options, like
`phabricator.base-uri`, can effectively disable the web UI if they are
configured incorrectly.
If these options could be configured from the web UI, you could not fix them if
you made a mistake (because the web UI would no longer work, so you could not
load the page to change the value).
We require these options to be edited from the CLI to make sure the editor has
access to fix any mistakes.
**Attackers could gain greater access**: Some options could be modified by an
attacker who has gained access to an administrator account in order to gain
greater access.
For example, an attacker who could modify `cluster.mailers` (and other
similar options), could potentially reconfigure Phabricator to send mail
through an evil server they controlled, then trigger password resets on other
user accounts to compromise them.
We require these options to be edited from the CLI to make sure the editor
has full access to the install.
Hidden Configuration
====================
**Hidden Configuration** is similar to locked configuration, but also can not
be //read// from the web UI.
In almost all cases, configuration is hidden because it is some sort of secret
key or access token for an external service. These values are hidden from the
web UI to prevent administrators (or attackers who have compromised
administrator accounts) from reading them.
You can review (and edit) hidden configuration from the CLI:
```
phabricator/ $ ./bin/config get <key>
phabricator/ $ ./bin/config set <key> <value>
```
Locked Configuration With Database Values
=========================================
You may receive a setup issue warning you that a locked configuration key has a
value set in the database. Most commonly, this is because:
- In some earlier version of Phabricator, this configuration was not locked.
- In the past, you or some other administrator used the web UI to set a
value. This value was written to the database.
- In a later version of the software, the value became locked.
When Phabricator was originally released, locked configuration did not yet
exist. Locked configuration was introduced later, and then configuration options
were gradually locked for a long time after that.
In some cases the meaning of a value changed and it became possible to use it
to break an install or the configuration became a security risk. In other
cases, we identified an existing security risk or arrived at some other reason
to lock the value.
Locking values was more common in the past, and it is now relatively rare for
an unlocked value to become locked: when new values are introduced, they are
generally locked or hidden appropriately. In most cases, this setup issue only
affects installs that have used Phabricator for a long time.
At time of writing (February 2019), Phabricator currently respects these old
database values. However, some future version of Phabricator will refuse to
read locked configuration from the database, because this improves security if
an attacker manages to find a way to bypass restrictions on editing locked
configuration from the web UI.
To clear this setup warning and avoid surprise behavioral changes in the future,
you should move these configuration values from the database to a local config
file. Usually, you'll do this by first copying the value from the database:
+```
+phabricator/ $ ./bin/config get <key>
+```
+
+...into local configuration:
+
```
phabricator/ $ ./bin/config set <key> <value>
```
...and then removing the database value:
```
phabricator/ $ ./bin/config delete --database <key>
```
See @{Configuration User Guide: Advanced Configuration} for some more detailed
discussion of different configuration sources.
Next Steps
==========
Continue by:
- learning more about advanced options with
@{Configuration User Guide: Advanced Configuration}; or
- returning to the @{article: Configuration Guide}.
diff --git a/src/docs/user/configuration/notifications.diviner b/src/docs/user/configuration/notifications.diviner
index 8e70315460..13d93317f9 100644
--- a/src/docs/user/configuration/notifications.diviner
+++ b/src/docs/user/configuration/notifications.diviner
@@ -1,279 +1,280 @@
@title Notifications User Guide: Setup and Configuration
@group config
Guide to setting up notifications.
Overview
========
By default, Phabricator delivers information about events (like users creating
tasks or commenting on code reviews) through email and in-application
notifications.
Phabricator can also be configured to deliver notifications in real time, by
popping up a message in any open browser windows if something has happened or
an object has been updated.
To enable real-time notifications:
- Configure and start the notification server, as described below.
- Adjust `notification.servers` to point at it.
This document describes the process in detail.
Supported Browsers
==================
Notifications are supported for browsers which support WebSockets. This covers
most modern browsers (like Chrome, Firefox, Safari, and recent versions of
Internet Explorer) and many mobile browsers.
IE8 and IE9 do not support WebSockets, so real-time notifications won't work in
those browsers.
Installing Node and Modules
===========================
The notification server uses Node.js, so you'll need to install it first.
To install Node.js, follow the instructions on
[[ http://nodejs.org | nodejs.org ]].
You will also need to install the `ws` module for Node. This needs to be
installed into the notification server directory:
phabricator/ $ cd support/aphlict/server/
phabricator/support/aphlict/server/ $ npm install ws
Once Node.js and the `ws` module are installed, you're ready to start the
server.
Running the Aphlict Server
==========================
After installing Node.js, you can control the notification server with the
`bin/aphlict` command. To start the server:
phabricator/ $ bin/aphlict start
By default, the server must be able to listen on port `22280`. If you're using
a host firewall (like a security group in EC2), make sure traffic can reach the
server.
The server configuration is controlled by a configuration file, which is
separate from Phabricator's configuration settings. The default file can
be found at `phabricator/conf/aphlict/aphlict.default.json`.
To make adjustments to the default configuration, either copy this file to
create `aphlict.custom.json` in the same directory (this file will be used if
it exists) or specify a configuration file explicitly with the `--config` flag:
phabricator/ $ bin/aphlict start --config path/to/config.json
The configuration file has these settings:
- `servers`: //Required list.// A list of servers to start.
- `logs`: //Optional list.// A list of logs to write to.
- `cluster`: //Optional list.// A list of cluster peers. This is an advanced
feature.
- `pidfile`: //Required string.// Path to a PID file.
- `memory.hint`: //Optional int.// Suggestion to `node` about how much
memory to use, via `--max-old-stack-size`. In most cases, this can be
left unspecified.
Each server in the `servers` list should be an object with these keys:
- `type`: //Required string.// The type of server to start. Options are
`admin` or `client`. Normally, you should run one of each.
- `port`: //Required int.// The port this server should listen on.
- `listen`: //Optional string.// Which interface to bind to. By default,
the `admin` server is bound to `127.0.0.1` (so only other services on the
local machine can connect to it), while the `client` server is bound
to `0.0.0.0` (so any client can connect).
- `ssl.key`: //Optional string.// If you want to use SSL on this port,
the path to an SSL key.
- `ssl.cert`: //Optional string.// If you want to use SSL on this port,
the path to an SSL certificate.
- `ssl.chain`: //Optional string.// If you have configured SSL on this
port, an optional path to a certificate chain file.
Each log in the `logs` list should be an object with these keys:
- `path`: //Required string.// Path to the log file.
Each peer in the `cluster` list should be an object with these keys:
- `host`: //Required string.// The peer host address.
- `port`: //Required int.// The peer port.
- `protocol`: //Required string.// The protocol to connect with, one of
`"http"` or `"https"`.
Cluster configuration is an advanced topic and can be omitted for most
installs. For more information on how to configure a cluster, see
@{article:Clustering Introduction} and @{article:Cluster: Notifications}.
The defaults are appropriate for simple cases, but you may need to adjust them
if you are running a more complex configuration.
Configuring Phabricator
=======================
After starting the server, configure Phabricator to connect to it by adjusting
`notification.servers`. This configuration option should have a list of servers
that Phabricator should interact with.
Normally, you'll list one client server and one admin server, like this:
```lang=json
[
{
"type": "client",
"host": "phabricator.mycompany.com",
"port": 22280,
"protocol": "https"
},
{
"type": "admin",
"host": "127.0.0.1",
"port": 22281,
"protocol": "http"
}
]
```
This definition defines which services the user's browser will attempt to
connect to. Most of the time, it will be very similar to the services defined
in the Aphlict configuration. However, if you are sending traffic through a
load balancer or terminating SSL somewhere before traffic reaches Aphlict,
the services the browser connects to may need to have different hosts, ports
or protocols than the underlying server listens on.
Verifying Server Status
=======================
After configuring `notification.servers`, navigate to
-{nav Config > Notification Servers} to verify that things are operational.
+{nav Config > Services > Notification Servers} to verify that things are
+operational.
Troubleshooting
===============
You can run `aphlict` in the foreground to get output to your console:
phabricator/ $ ./bin/aphlict debug
Because the notification server uses WebSockets, your browser error console
may also have information that is useful in figuring out what's wrong.
The server also generates a log, by default in `/var/log/aphlict.log`. You can
change this location by adjusting configuration. The log may contain
information that is useful in resolving issues.
SSL and HTTPS
=============
If you serve Phabricator over HTTPS, you must also serve websockets over HTTPS.
Browsers will refuse to connect to `ws://` websockets from HTTPS pages.
If a client connects to Phabricator over HTTPS, Phabricator will automatically
select an appropriate HTTPS service from `notification.servers` and instruct
the browser to open a websocket connection with `wss://`.
The simplest way to do this is configure Aphlict with an SSL key and
certificate and let it terminate SSL directly.
If you prefer not to do this, two other options are:
- run the websocket through a websocket-capable loadbalancer and terminate
SSL there; or
- run the websocket through `nginx` over the same socket as the rest of
your web traffic.
See the next sections for more detail.
Terminating SSL with a Load Balancer
====================================
If you want to terminate SSL in front of the notification server with a
traditional load balancer or a similar device, do this:
- Point `notification.servers` at your load balancer or reverse proxy,
specifying that the protocol is `https`.
- On the load balancer or proxy, terminate SSL and forward traffic to the
Aphlict server.
- In the Aphlict configuration, listen on the target port with `http`.
Terminating SSL with Nginx
==========================
If you use `nginx`, you can send websocket traffic to the same port as normal
HTTP traffic and have `nginx` proxy it selectively based on the request path.
This requires `nginx` 1.3 or greater. See the `nginx` documentation for
details:
> http://nginx.com/blog/websocket-nginx/
This is very complex, but allows you to support notifications without opening
additional ports.
An example `nginx` configuration might look something like this:
```lang=nginx, name=/etc/nginx/conf.d/connection_upgrade.conf
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
```
```lang=nginx, name=/etc/nginx/conf.d/websocket_pool.conf
upstream websocket_pool {
ip_hash;
server 127.0.0.1:22280;
}
```
```lang=nginx, name=/etc/nginx/sites-enabled/phabricator.example.com.conf
server {
server_name phabricator.example.com;
root /path/to/phabricator/webroot;
// ...
location = /ws/ {
proxy_pass http://websocket_pool;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 999999999;
}
}
```
With this approach, you should make these additional adjustments:
**Phabricator Configuration**: The entry in `notification.servers` with type
`"client"` should have these adjustments made:
- Set `host` to the Phabricator host.
- Set `port` to the standard HTTPS port (usually `443`).
- Set `protocol` to `"https"`.
- Set `path` to `/ws/`, so it matches the special `location` in your
`nginx` config.
You do not need to adjust the `"admin"` server.
**Aphlict**: Your Aphlict configuration should make these adjustments to
the `"client"` server:
- Do not specify any `ssl.*` options: `nginx` will send plain HTTP traffic
to Aphlict.
- Optionally, you can `listen` on `127.0.0.1` instead of `0.0.0.0`, because
the server will no longer receive external traffic.

File Metadata

Mime Type
text/x-diff
Expires
Sun, Jan 19, 17:38 (1 w, 5 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1127045
Default Alt Text
(24 KB)

Event Timeline