Skip to content

Commit

Permalink
Merge pull request #1097 from nextcloud/backport/1094/stable20
Browse files Browse the repository at this point in the history
[stable20] Add integration tests for push registration
  • Loading branch information
nickvergessen authored Oct 20, 2021
2 parents fa22583 + 7ab9500 commit 3212b38
Show file tree
Hide file tree
Showing 4 changed files with 172 additions and 7 deletions.
2 changes: 1 addition & 1 deletion lib/Controller/PushController.php
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,8 @@ public function registerDevice(string $pushTokenHash, string $devicePublicKey, s
$key = $this->identityProof->getKey($user);

$deviceIdentifier = json_encode([$user->getCloudId(), $token->getId()]);
openssl_sign($deviceIdentifier, $signature, $key->getPrivate(), OPENSSL_ALGO_SHA512);
$deviceIdentifier = base64_encode(hash('sha512', $deviceIdentifier, true));
openssl_sign($deviceIdentifier, $signature, $key->getPrivate(), OPENSSL_ALGO_SHA512);

$appType = 'unknown';
if ($this->request->isUserAgent([
Expand Down
109 changes: 105 additions & 4 deletions tests/Integration/features/bootstrap/FeatureContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ class FeatureContext implements Context, SnippetAcceptingContext {
/** @var string */
protected $lastEtag;

/** @var resource */
protected $deviceKey;

/** @var string[] */
protected $appPasswords;

/**
* FeatureContext constructor.
*/
Expand Down Expand Up @@ -221,6 +227,99 @@ public function deleteAllNotification($api) {
$this->sendingTo('DELETE', '/apps/notifications/api/' . $api . '/notifications');
}

/**
* @Then /^user "([^"]*)" unregisters from push notifications/
*
* @param string $user
*/
public function unregisterForPushNotifications(string $user) {
$currentUser = $this->currentUser;
$this->setCurrentUser($user);
$this->sendingToWith('DELETE', '/apps/notifications/api/v2/push?format=json');
$this->setCurrentUser($currentUser);
}

/**
* @Then /^user "([^"]*)" registers for push notifications with$/
*
* @param string $user
* @param TableNode|null $formData
*/
public function registerForPushNotifications(string $user, TableNode $formData) {
$data = $formData->getRowsHash();

if ($data['devicePublicKey'] === 'VALID_KEY') {
$config = [
'digest_alg' => 'sha512',
'private_key_bits' => 2048,
'private_key_type' => OPENSSL_KEYTYPE_RSA,
];
$this->deviceKey = openssl_pkey_new($config);
$keyDetails = openssl_pkey_get_details($this->deviceKey);
$publicKey = $keyDetails['key'];

$data['devicePublicKey'] = $publicKey;
}

$currentUser = $this->currentUser;
$this->setCurrentUser($user);
$this->sendingToWith('POST', '/apps/notifications/api/v2/push?format=json', $data);
$this->setCurrentUser($currentUser);
}

/**
* @Then /^can validate the response and signature$/
*/
public function validateResponseAndSignature(): void {
$response = $this->getArrayOfNotificationsResponded($this->response);

Assert::assertStringStartsWith('-----BEGIN PUBLIC KEY-----' . "\n", $response['publicKey']);
Assert::assertStringEndsWith('-----END PUBLIC KEY-----' . "\n", $response['publicKey']);
Assert::assertNotEmpty($response['deviceIdentifier'], 'Device identifier should not be empty');
Assert::assertNotEmpty($response['signature'], 'Signature should not be empty');

$result = openssl_verify($response['deviceIdentifier'], base64_decode($response['signature']), $response['publicKey'], OPENSSL_ALGO_SHA512);
Assert::assertEquals(true, $result, 'Failed to verify the signature');
}

/**
* @Then /^user "([^"]*)" creates an app password$/
*
* @param string $user
*/
public function createAppPassword(string $user) {
$currentUser = $this->currentUser;
$this->setCurrentUser($user);
$this->sendingToWith('GET', '/core/getapppassword?format=json');
$this->setCurrentUser($currentUser);

$response = $this->getArrayOfNotificationsResponded($this->response);
Assert::assertNotEquals('', $response['apppassword']);
$this->appPasswords[$user] = $response['apppassword'];
}

/**
* @Then /^user "([^"]*)" forgets the app password$/
*
* @param string $user
*/
public function removeAppPassword(string $user) {
unset($this->appPasswords[$user]);
}

/**
* @Then /^error "([^"]*)" is expected with status code ([0-9]*)$/
*
* @param string $error
* @param int $statusCode
*/
public function expectedErrorOnLastRequest(string $error, int $statusCode) {
$this->assertStatusCode($this->response, $statusCode);
$response = $this->getArrayOfNotificationsResponded($this->response);

Assert::assertEquals($error, $response['message']);
}

/**
* @Then /^status code is ([0-9]*)$/
*
Expand Down Expand Up @@ -347,15 +446,17 @@ public function sendingTo(string $verb, string $url) {
* @When /^sending "([^"]*)" to "([^"]*)" with$/
* @param string $verb
* @param string $url
* @param TableNode $body
* @param TableNode|array|null $body
* @param array $headers
*/
public function sendingToWith(string $verb, string $url, TableNode $body = null, array $headers = []) {
public function sendingToWith(string $verb, string $url, $body = null, array $headers = []) {
$fullUrl = $this->baseUrl . 'ocs/v2.php' . $url;
$client = new Client();
$options = [];
if ($this->currentUser === 'admin') {
$options['auth'] = ['admin', 'admin'];
if (isset($this->appPasswords[$this->currentUser])) {
$options['auth'] = [$this->currentUser, $this->appPasswords[$this->currentUser]];
} elseif ($this->currentUser === 'admin') {
$options['auth'] = [$this->currentUser, 'admin'];
} else {
$options['auth'] = [$this->currentUser, '123456'];
}
Expand Down
64 changes: 64 additions & 0 deletions tests/Integration/features/push-registration.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
Feature: Push registration
Background:
Given user "test1" exists
Given as user "test1"

Scenario: Invalid push token hash
Given user "test1" registers for push notifications with
| pushTokenHash | 12345 |
| devicePublicKey | INVALID_KEY |
| proxyServer | nextcloud |
Then error "INVALID_PUSHTOKEN_HASH" is expected with status code 400

Scenario: Invalid device key
Given user "test1" registers for push notifications with
| pushTokenHash | 12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678 |
| devicePublicKey | INVALID_KEY |
| proxyServer | nextcloud |
Then error "INVALID_DEVICE_KEY" is expected with status code 400

Scenario: Invalid proxy server
Given user "test1" registers for push notifications with
| pushTokenHash | 12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678 |
| devicePublicKey | VALID_KEY |
| proxyServer | nextcloud |
Then error "INVALID_PROXY_SERVER" is expected with status code 400

Scenario: Invalid session token: not using an app password
Given user "test1" registers for push notifications with
| pushTokenHash | 12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678 |
| devicePublicKey | VALID_KEY |
| proxyServer | https://push-notifications.nextcloud.com/ |
Then error "INVALID_SESSION_TOKEN" is expected with status code 400

Scenario: Successful registration
Given user "test1" creates an app password
Given user "test1" registers for push notifications with
| pushTokenHash | 12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678 |
| devicePublicKey | VALID_KEY |
| proxyServer | https://push-notifications.nextcloud.com/ |
Then status code is 201
And can validate the response and signature

Scenario: Unregistering from push notifications without app password
Given user "test1" forgets the app password
Given user "test1" unregisters from push notifications
Then error "INVALID_SESSION_TOKEN" is expected with status code 400

Scenario: Unregistering from push notifications successfully
Given user "test1" creates an app password
Given user "test1" registers for push notifications with
| pushTokenHash | 12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678 |
| devicePublicKey | VALID_KEY |
| proxyServer | https://push-notifications.nextcloud.com/ |
Then status code is 201
And can validate the response and signature
Given user "test1" unregisters from push notifications
Then status code is 202
Given user "test1" unregisters from push notifications
Then status code is 200

Scenario: Unregistering from push notifications without registering
Given user "test1" creates an app password
Given user "test1" unregisters from push notifications
Then status code is 200
4 changes: 2 additions & 2 deletions tests/Unit/Controller/PushControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ public function dataRegisterDevice() {
[
'publicKey' => $this->userPublicKey,
'deviceIdentifier' => 'XUCEZ1EHvTUcVhIvrQQQ1XcP0ZD2BFdFqw4EYbOhBfiEgXgirurR4x/ve4GSSyfivvbQOdOkZUM+g4m+tSb0Ew==',
'signature' => 'LRhbXO71WYX9qqDbQX7C+87YaaFfWoT/vG0DlaXdBz6+lhyOA0dw/1Ggz3fd7RerCQ0MfgnnTyxO+cSeRpUaPdA2yPjfoiPpfYA5SOJQGF3comS/HYna3fHiFDbOoM3BJOnjvqiSZdxA/ICdyl2mEEC5wO7AZ4OZKBTa5XfL7eSCXZLEv1YldqcLOStbXrI7voDQocTMJxoQZI/j8BVcf2i3D6F454aXIFDrYYzC2PQY+CKJoXZW0m0RMWaTM2B8tBmFFwrmaGLDqcjjpd33TsTtsV5DB7WimffLBPpOuGV4Z1Kiagp/mxpPLz2NImNV79mDX9gY3ZppCZTwChP5qQ==',
'signature' => 'X9+J7NNLfG9Ft6C36zrYLVJ5aH5euIROzdV937hsU81jL7WvOwzBfc7bImzxU3Bnev5wEKwkw7Ts/2q/+UUkOxgtEZinp52s87S5obKtsVXsczHbsqg4p/ueoBPhF17VsP1e8kMtxZ4snk/iArX4Eu1cfaM3+OckmpO0MYXy0rUbYpQPAJo4VgRFKKjFvfEVOj8N74DTIJ+TjRsvvDhJbb9KpeFe3a6Rv9mIo0AqoK+deAbUkWY0aM+74noVXvPtNzExgK4mWJ02+JHEuQEUbCuQsgoBia0vC3fILbwVxHzrieWGEnE7vkRyFEzlkeo7ZSMawDPxsPN5HxwBs2SZig==',
],
Http::STATUS_CREATED,
],
Expand All @@ -317,7 +317,7 @@ public function dataRegisterDevice() {
[
'publicKey' => $this->userPublicKey,
'deviceIdentifier' => 'x9vSImcGjhzR9BfZ/XbbUqqCCNC4bHKsX7vkQWNZRd1/MiY+OuF02fx8K08My0RpkNnwj/rQ/gVSU1oEdFwkww==',
'signature' => 'J9AcdJt5youJmMnBhS+Cc9ytArynIKtCRoNf/m0oOFO/e0hWHqs1NRdQBe81qzYIjf0+bj0Q97X9Xv1rnVJesPkQUbGaa4nAPt+viGSfvzTptjX4LKgqm8B3UkduBA262IcaWgM5P84gUqelkQIC1nIqq/MJTuC6oQ5lUwIV1a92ZurDjhwH4b3f7/ZLTTOTRD0DWN9W/yOyF1qECivgePR3eu+mkcBzXVU/TDZDJic9G7xhqcTnWV6qk+aKyzdNo1tu5W7mF+v5vF6rrGZrq55vPLWAHApTD7P+NFV01BnaCuN7/qGJNVs7m7EH03jpOw7y3jqNMmcmonYrJSMVqg==',
'signature' => 'GFpnv3MO7mcBef2RJ4Ayrl6RQakGM7AvlKhoTr3DUWnv+iBzwGy8YV34HIPoArz4tyqonHRlLsxPYq4ENPfGO99KrIS16z4RUq0wiCBGf+S8/K8lM9cE9EBKE9yrkTsSvZGICEusvxQ+cTfVr30bnavvi1wL1UuxxDBlJebda9FJ9HfaS24j4rT7K78oMguqDVM+4hhr6BMhcpUVV+kTpOaBpluw5pRDwUP3jJBmkkOa57WRKFcu0Lr/XIx/G0c8Si+BAfM//CTMstwp5XDFn4W9EYSStjNrvsULdV+tOKFwnowqts+UFzEDvmZ1g4qIMWUUPBF4/pjaiDqtMojgrA==',
],
Http::STATUS_OK,
],
Expand Down

0 comments on commit 3212b38

Please sign in to comment.