diff --git a/tests/Integration/features/bootstrap/FeatureContext.php b/tests/Integration/features/bootstrap/FeatureContext.php index 893d47009..b6e42c61b 100644 --- a/tests/Integration/features/bootstrap/FeatureContext.php +++ b/tests/Integration/features/bootstrap/FeatureContext.php @@ -56,6 +56,12 @@ class FeatureContext implements Context, SnippetAcceptingContext { /** @var string */ protected $lastEtag; + /** @var resource */ + protected $deviceKey; + + /** @var string[] */ + protected $appPasswords; + /** * FeatureContext constructor. */ @@ -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]*)$/ * @@ -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']; } diff --git a/tests/Integration/features/push-registration.feature b/tests/Integration/features/push-registration.feature new file mode 100644 index 000000000..3d1eea99a --- /dev/null +++ b/tests/Integration/features/push-registration.feature @@ -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