Receive the proxy server from the app

This allows users to use one client against multiple different servers

Signed-off-by: Joas Schilling <coding@schilljs.com>
This commit is contained in:
Joas Schilling 2017-03-29 11:43:23 +02:00
Родитель 3e53f38630
Коммит 62605403a4
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: E166FD8976B3BAC8
4 изменённых файлов: 84 добавлений и 90 удалений

Просмотреть файл

@ -160,6 +160,12 @@
<notnull>true</notnull>
<length>128</length>
</field>
<field>
<name>proxyserver</name>
<type>text</type>
<notnull>true</notnull>
<length>256</length>
</field>
<index>
<name>oc_notifpushtoken</name>

Просмотреть файл

@ -6,7 +6,7 @@
In order to find out if notifications support push on the server you can run a request against the capabilities endpoint: `/ocs/v2.php/cloud/capabilities`
```json
```
{
"ocs": {
...
@ -36,14 +36,15 @@ In order to find out if notifications support push on the server you can run a r
3. The device generates a `sha512` hash of the `PushToken` (`PushTokenHash`)
4. The device then sends the `devicePublicKey` and `PushTokenHash` to the Nextcloud server:
4. The device then sends the `devicePublicKey`, `PushTokenHash` and `proxyServerUrl` to the Nextcloud server:
```json
```
POST /ocs/v2.php/apps/notifications/api/v3/push
{
"pushTokenHash": "{{PushTokenHash}}",
"devicePublicKey": "{{devicePublicKey}}"
"devicePublicKey": "{{devicePublicKey}}",
"proxyServer": "{{proxyServerUrl}}"
}
```
@ -83,6 +84,7 @@ In case of `400` the following `message` can appear in the body:
| `INVALID_PUSHTOKEN_HASH` | The hash of the push token was not a valid `sha512` hash. |
| `INVALID_SESSION_TOKEN` | The authentication token of the request could not be identified. Check whether a password was used to login. |
| `INVALID_DEVICE_KEY` | The device key does not match the one registered to the provided session token. |
| `INVALID_PROXY_SERVER` | The proxy server was not a valid https URL. |
@ -92,14 +94,10 @@ When an account is removed from a device, the device should unregister on the se
The device should then send the `devicePublicKey` and `PushTokenHash` to the Nextcloud server:
The device should then send a `DELETE` request to the Nextcloud server:
```json
```
DELETE /ocs/v2.php/apps/notifications/api/v3/push
{
"devicePublicKey": "{{devicePublicKey}}"
}
```
@ -110,6 +108,7 @@ The server replies with the following status codes:
| Status code | Meaning |
| ----------- | ---------------------------------------- |
| 200 | Push token was not registered on the server |
| 202 | Push token was deleted and **needs to be deleted from the `Proxy`** |
| 400 | Invalid public key or device does not use a token to authenticate |
| 401 | Device is not logged in |
@ -131,7 +130,7 @@ In case of `400` the following `message` can appear in the body:
The device sends the`PushToken` as well as the `deviceIdentifier`, `signature` and the user´s `publicKey` (from the server´s response) to the Push Proxy:
```json
```
POST /devices
{
@ -161,7 +160,7 @@ The server replies with the following status codes:
The device sends the `deviceIdentifier`, `deviceIdentifierSignature` and the user´s `publicKey` (from the server´s response) to the Push Proxy:
```json
```
DELETE /devices
{
@ -190,7 +189,9 @@ The server replies with the following status codes:
The pushed notifications format depends on the service that is used.
In case of Apple Push Notification Service, the payload of a push notification looks like the following:
```json
***⚠️ Warning:** Maybe different since we switched to FCM instead of APNS*
```
{
"aps" : {
"alert" : {

Просмотреть файл

@ -77,9 +77,10 @@ class PushController extends OCSController {
*
* @param string $pushTokenHash
* @param string $devicePublicKey
* @param string $proxyServer
* @return JSONResponse
*/
public function registerDevice($pushTokenHash, $devicePublicKey) {
public function registerDevice($pushTokenHash, $devicePublicKey, $proxyServer) {
$user = $this->userSession->getUser();
if (!$user instanceof IUser) {
return new JSONResponse([], Http::STATUS_UNAUTHORIZED);
@ -95,6 +96,10 @@ class PushController extends OCSController {
return new JSONResponse(['message' => 'INVALID_DEVICE_KEY'], Http::STATUS_BAD_REQUEST);
}
if (!filter_input(FILTER_VALIDATE_URL, $proxyServer) || strpos($proxyServer, 'https://') !== 0 || strlen($proxyServer)> 256) {
return new JSONResponse(['message' => 'INVALID_PROXY_SERVER'], Http::STATUS_BAD_REQUEST);
}
$tokenId = $this->session->get('token-id');
try {
$token = $this->tokenProvider->getTokenById($tokenId);
@ -108,11 +113,7 @@ class PushController extends OCSController {
openssl_sign($deviceIdentifier, $signature, $key->getPrivate(), OPENSSL_ALGO_SHA512);
$deviceIdentifier = base64_encode(hash('sha512', $deviceIdentifier, true));
try {
$created = $this->savePushToken($user, $token, $deviceIdentifier, $devicePublicKey, $pushTokenHash);
} catch (\BadMethodCallException $e) {
return new JSONResponse(['message' => 'INVALID_DEVICE_KEY'], Http::STATUS_BAD_REQUEST);
}
$created = $this->savePushToken($user, $token, $deviceIdentifier, $devicePublicKey, $pushTokenHash, $proxyServer);
return new JSONResponse([
'publicKey' => $key->getPublic(),
@ -125,21 +126,14 @@ class PushController extends OCSController {
* @NoAdminRequired
* @NoCSRFRequired
*
* @param string $devicePublicKey
* @return JSONResponse
*/
public function removeDevice($devicePublicKey) {
public function removeDevice() {
$user = $this->userSession->getUser();
if (!$user instanceof IUser) {
return new JSONResponse([], Http::STATUS_UNAUTHORIZED);
}
if (strlen($devicePublicKey) !== 450 ||
strpos($devicePublicKey, '-----BEGIN PUBLIC KEY-----') !== 0 ||
strpos($devicePublicKey, '-----END PUBLIC KEY-----') !== 426) {
return new JSONResponse(['message' => 'INVALID_DEVICE_KEY'], Http::STATUS_BAD_REQUEST);
}
$sessionId = $this->session->getId();
try {
$token = $this->tokenProvider->getToken($sessionId);
@ -147,13 +141,11 @@ class PushController extends OCSController {
return new JSONResponse(['message' => 'INVALID_SESSION_TOKEN'], Http::STATUS_BAD_REQUEST);
}
try {
$this->deletePushToken($user, $token, $devicePublicKey);
} catch (\BadMethodCallException $e) {
return new JSONResponse(['message' => 'INVALID_DEVICE_KEY'], Http::STATUS_BAD_REQUEST);
if ($this->deletePushToken($user, $token)) {
return new JSONResponse([], Http::STATUS_ACCEPTED);
}
return new JSONResponse([], Http::STATUS_ACCEPTED);
return new JSONResponse([], Http::STATUS_OK);
}
/**
@ -162,26 +154,24 @@ class PushController extends OCSController {
* @param string $deviceIdentifier
* @param string $devicePublicKey
* @param string $pushTokenHash
* @param string $proxyServer
* @return bool If the hash was new to the database
* @throws \BadMethodCallException
*/
protected function savePushToken(IUser $user, IToken $token, $deviceIdentifier, $devicePublicKey, $pushTokenHash) {
protected function savePushToken(IUser $user, IToken $token, $deviceIdentifier, $devicePublicKey, $pushTokenHash, $proxyServer) {
$query = $this->db->getQueryBuilder();
$query->select('pushtokenhash')
$query->select('*')
->from('notifications_pushtokens')
->where($query->expr()->eq('uid', $query->createNamedParameter($user->getUID())))
->andWhere($query->expr()->eq('token', $query->createNamedParameter($token->getId())))
->andWhere($query->expr()->eq('devicepublickey', $query->createNamedParameter($devicePublicKey)));
->andWhere($query->expr()->eq('token', $query->createNamedParameter($token->getId())));
$result = $query->execute();
$row = $result->fetch();
$result->closeCursor();
if (!$row) {
return $this->insertPushToken($user, $token, $deviceIdentifier, $devicePublicKey, $pushTokenHash);
} else if ($row['pushtokenhash'] !== $pushTokenHash) {
return $this->updatePushToken($user, $token, $devicePublicKey, $pushTokenHash);
return $this->insertPushToken($user, $token, $deviceIdentifier, $devicePublicKey, $pushTokenHash, $proxyServer);
}
return false;
return $this->updatePushToken($user, $token, $devicePublicKey, $pushTokenHash, $proxyServer);
}
/**
@ -190,9 +180,10 @@ class PushController extends OCSController {
* @param string $deviceIdentifier
* @param string $devicePublicKey
* @param string $pushTokenHash
* @param string $proxyServer
* @return bool If the entry was created
*/
protected function insertPushToken(IUser $user, IToken $token, $deviceIdentifier, $devicePublicKey, $pushTokenHash) {
protected function insertPushToken(IUser $user, IToken $token, $deviceIdentifier, $devicePublicKey, $pushTokenHash, $proxyServer) {
$devicePublicKeyHash = hash('sha512', $devicePublicKey);
$query = $this->db->getQueryBuilder();
@ -204,6 +195,7 @@ class PushController extends OCSController {
'devicepublickey' => $query->createNamedParameter($devicePublicKey),
'devicepublickeyhash' => $query->createNamedParameter($devicePublicKeyHash),
'pushtokenhash' => $query->createNamedParameter($pushTokenHash),
'proxyserver' => $query->createNamedParameter($proxyServer),
]);
return $query->execute() > 0;
}
@ -213,46 +205,35 @@ class PushController extends OCSController {
* @param IToken $token
* @param string $devicePublicKey
* @param string $pushTokenHash
* @param string $proxyServer
* @return bool If the entry was updated
* @throws \BadMethodCallException
*/
protected function updatePushToken(IUser $user, IToken $token, $devicePublicKey, $pushTokenHash) {
protected function updatePushToken(IUser $user, IToken $token, $devicePublicKey, $pushTokenHash, $proxyServer) {
$devicePublicKeyHash = hash('sha512', $devicePublicKey);
$query = $this->db->getQueryBuilder();
$query->update('notifications_pushtokens')
->set('devicepublickey', $query->createNamedParameter($devicePublicKey))
->set('devicepublickeyhash', $query->createNamedParameter($devicePublicKeyHash))
->set('pushtokenhash', $query->createNamedParameter($pushTokenHash))
->set('proxyserver', $query->createNamedParameter($proxyServer))
->where($query->expr()->eq('uid', $query->createNamedParameter($user->getUID())))
->andWhere($query->expr()->eq('token', $query->createNamedParameter($token->getId(), IQueryBuilder::PARAM_INT)))
->andWhere($query->expr()->eq('devicepublickeyhash', $query->createNamedParameter($devicePublicKeyHash)));
->andWhere($query->expr()->eq('token', $query->createNamedParameter($token->getId(), IQueryBuilder::PARAM_INT)));
if ($query->execute() !== 0) {
throw new \BadMethodCallException();
}
return true;
return $query->execute() !== 0;
}
/**
* @param IUser $user
* @param IToken $token
* @param string $devicePublicKey
* @return bool If the entry was deleted
* @throws \BadMethodCallException
*/
protected function deletePushToken(IUser $user, IToken $token, $devicePublicKey) {
$devicePublicKeyHash = hash('sha512', $devicePublicKey);
protected function deletePushToken(IUser $user, IToken $token) {
$query = $this->db->getQueryBuilder();
$query->delete('notifications_pushtokens')
->where($query->expr()->eq('uid', $query->createNamedParameter($user->getUID())))
->andWhere($query->expr()->eq('token', $query->createNamedParameter($token->getId(), IQueryBuilder::PARAM_INT)))
->andWhere($query->expr()->eq('devicepublickeyhash', $query->createNamedParameter($devicePublicKeyHash)));
->andWhere($query->expr()->eq('token', $query->createNamedParameter($token->getId(), IQueryBuilder::PARAM_INT)));
if ($query->execute() !== 0) {
throw new \BadMethodCallException();
}
return true;
return $query->execute() !== 0;
}
}

Просмотреть файл

@ -88,7 +88,10 @@ class Push {
$pushNotifications = [];
foreach ($devices as $device) {
try {
$pushNotifications[] = json_encode($this->encryptAndSign($userKey, $device, $notification));
if (!isset($pushNotifications[$device['proxyserver']])) {
$pushNotifications[$device['proxyserver']] = [];
}
$pushNotifications[$device['proxyserver']] = json_encode($this->encryptAndSign($userKey, $device, $notification));
} catch (InvalidTokenException $e) {
// Token does not exist anymore, should drop the push device entry
// FIXME delete push token
@ -99,33 +102,36 @@ class Push {
}
$client = $this->clientService->newClient();
try {
$pushServer = rtrim($this->config->getAppValue('notifications', 'push_server', 'https://push-notifications.nextcloud.com'), '/');
$response = $client->post($pushServer . '/notifications', [
'body' => [
'notifications' => $pushNotifications,
],
]);
} catch (\Exception $e) {
$this->log->logException($e, [
'app' => 'notifications',
]);
return;
}
foreach ($pushNotifications as $proxyServer => $notifications) {
try {
$response = $client->post(rtrim($proxyServer, '/') . '/notifications', [
'body' => [
'notifications' => $notifications,
],
]);
} catch (\Exception $e) {
$this->log->logException($e, [
'app' => 'notifications',
]);
return;
}
$status = $response->getStatusCode();
if ($status !== Http::STATUS_OK && $status !== Http::STATUS_SERVICE_UNAVAILABLE) {
$body = $response->getBody();
$this->log->error('Could not send notification to push server: {error}',[
'error' => is_string($body) ? $body : 'no reason given',
'app' => 'notifications',
]);
} else if ($status === Http::STATUS_SERVICE_UNAVAILABLE && $this->config->getSystemValue('debug', false)) {
$body = $response->getBody();
$this->log->debug('Could not send notification to push server: {error}',[
'error' => is_string($body) ? $body : 'no reason given',
'app' => 'notifications',
]);
$status = $response->getStatusCode();
if ($status !== Http::STATUS_OK && $status !== Http::STATUS_SERVICE_UNAVAILABLE) {
$body = $response->getBody();
$this->log->error('Could not send notification to push server [{url}]: {error}',[
'error' => is_string($body) ? $body : 'no reason given',
'url' => $proxyServer,
'app' => 'notifications',
]);
} else if ($status === Http::STATUS_SERVICE_UNAVAILABLE && $this->config->getSystemValue('debug', false)) {
$body = $response->getBody();
$this->log->debug('Could not send notification to push server [{url}]: {error}',[
'error' => is_string($body) ? $body : 'no reason given',
'url' => $proxyServer,
'app' => 'notifications',
]);
}
}
}