From 371717abad23d726ef54597489f0838ab4fc6b1f Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Thu, 26 Aug 2021 18:02:38 +0200 Subject: [PATCH 1/7] feat(realtime): allow authentication via message --- app/realtime.php | 89 ++++++++++++++++++++- src/Appwrite/Messaging/Adapter/Realtime.php | 8 -- 2 files changed, 85 insertions(+), 12 deletions(-) diff --git a/app/realtime.php b/app/realtime.php index bee946dd0..d84f55764 100644 --- a/app/realtime.php +++ b/app/realtime.php @@ -206,7 +206,7 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats, foreach ($stats as $projectId => $value) { $event = [ 'project' => 'console', - 'roles' => ['team:'.$value['teamId']], + 'roles' => ['team:' . $value['teamId']], 'data' => [ 'event' => 'stats.connections', 'channels' => ['project'], @@ -446,9 +446,90 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server, } }); -$server->onMessage(function (int $connection, string $message) use ($server) { - $server->send([$connection], 'Sending messages is not allowed.'); - $server->close($connection, 1003); +$server->onMessage(function (int $connection, string $message) use ($server, $register, $realtime, $containerId) { + try { + $db = $register->get('dbPool')->get(); + $cache = $register->get('redisPool')->get(); + + $projectDB = new Database(); + $projectDB->setAdapter(new RedisAdapter(new MySQLAdapter($db, $cache), $cache)); + $projectDB->setNamespace('app_' . $realtime->connections[$connection]['projectId']); + $projectDB->setMocks(Config::getParam('collections', [])); + + /* + * Abuse Check + * + * Abuse limits are sending 32 times per minute and connection. + */ + $timeLimit = new TimeLimit('url:{url},conection:{connection}', 32, 60, $db); + $timeLimit + ->setNamespace('app_' . $realtime->connections[$connection]['projectId']) + ->setParam('{connection}', $connection) + ->setParam('{container}', $containerId); + + $abuse = new Abuse($timeLimit); + + if ($abuse->check() && App::getEnv('_APP_OPTIONS_ABUSE', 'enabled') === 'enabled') { + throw new Exception('Too many messages', 1013); + } + + $message = json_decode($message, true); + + if (is_null($message) || (!array_key_exists('type', $message) && !array_key_exists('data', $message))) { + $response = [ + 'code' => 1003, + 'message' => 'Message format is wrong.' + ]; + $server->close($connection, 1003); + $server->send([$connection], json_encode($response)); + } + + switch ($message['type']) { + case 'authentication': + if (!array_key_exists('session', $message['data'])) { + throw new Exception('Payload not valid.', 1003); + } + + $session = Auth::decodeSession($message['data']['session']); + Auth::$unique = $session['id']; + Auth::$secret = $session['secret']; + + $user = $projectDB->getDocument(Auth::$unique); + + if ( + empty($user->getId()) // Check a document has been found in the DB + || Database::SYSTEM_COLLECTION_USERS !== $user->getCollection() // Validate returned document is really a user document + || !Auth::sessionVerify($user->getAttribute('sessions', []), Auth::$secret) // Validate user has valid login token + ) { + // cookie not valid + throw new Exception('Session not valid.', 1003); + } + + $roles = Auth::getRoles($user); + $channels = Realtime::convertChannels(array_flip($realtime->connections[$connection]['channels']), $user->getId()); + $realtime->subscribe($realtime->connections[$connection]['projectId'], $connection, $roles, $channels); + + break; + + default: + throw new Exception('Message type not valid.', 1003); + break; + } + } catch (\Throwable $th) { + $response = [ + 'code' => $th->getCode(), + 'message' => $th->getMessage() + ]; + + $server->send([$connection], json_encode($response)); + + if ($th->getCode() === 1008) { + $server->close($connection, $th->getCode()); + } + } finally { + $register->get('dbPool')->put($db); + $register->get('redisPool')->put($cache); + } }); $server->onClose(function (int $connection) use ($realtime, $stats) { diff --git a/src/Appwrite/Messaging/Adapter/Realtime.php b/src/Appwrite/Messaging/Adapter/Realtime.php index 11eca4ade..5f03009fc 100644 --- a/src/Appwrite/Messaging/Adapter/Realtime.php +++ b/src/Appwrite/Messaging/Adapter/Realtime.php @@ -226,18 +226,10 @@ class Realtime extends Adapter if (!empty($userId)) { $channels['account.' . $userId] = $value; } - unset($channels['account']); break; } } - if (\array_key_exists('account', $channels)) { - if ($userId) { - $channels['account.' . $userId] = $channels['account']; - } - unset($channels['account']); - } - return $channels; } From 45cb05bb31f6cd7c4537668b0a6f93b4da32a1bc Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Thu, 26 Aug 2021 18:10:53 +0200 Subject: [PATCH 2/7] fix(realtime): exception --- app/realtime.php | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/app/realtime.php b/app/realtime.php index d84f55764..415c9457e 100644 --- a/app/realtime.php +++ b/app/realtime.php @@ -476,12 +476,7 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re $message = json_decode($message, true); if (is_null($message) || (!array_key_exists('type', $message) && !array_key_exists('data', $message))) { - $response = [ - 'code' => 1003, - 'message' => 'Message format is wrong.' - ]; - $server->close($connection, 1003); - $server->send([$connection], json_encode($response)); + throw new Exception('Message format is not valid.', 1003); } switch ($message['type']) { From 67ae70ed69ce814570293f6fb1d7fb705dd5ca41 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Fri, 27 Aug 2021 10:20:44 +0200 Subject: [PATCH 3/7] update tests --- app/realtime.php | 27 +- tests/e2e/Services/Realtime/RealtimeBase.php | 450 ++++++++++++------- tests/unit/Messaging/MessagingTest.php | 8 +- 3 files changed, 324 insertions(+), 161 deletions(-) diff --git a/app/realtime.php b/app/realtime.php index 415c9457e..22efe787e 100644 --- a/app/realtime.php +++ b/app/realtime.php @@ -305,7 +305,10 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats, $server->send( $receivers, - json_encode($event['data']) + json_encode([ + 'type' => 'event', + 'data' => $event['data'] + ]) ); if (($num = count($receivers)) > 0) { @@ -421,8 +424,11 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server, $stats->incr($project->getId(), 'connectionsTotal'); } catch (\Throwable $th) { $response = [ - 'code' => $th->getCode(), - 'message' => $th->getMessage() + 'type' => 'error', + 'data' => [ + 'code' => $th->getCode(), + 'message' => $th->getMessage() + ] ]; $server->send([$connection], json_encode($response)); @@ -504,6 +510,14 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re $channels = Realtime::convertChannels(array_flip($realtime->connections[$connection]['channels']), $user->getId()); $realtime->subscribe($realtime->connections[$connection]['projectId'], $connection, $roles, $channels); + $server->send([$connection], json_encode([ + 'type' => 'response', + 'data' => [ + 'to' => 'authentication', + 'success' => true + ] + ])); + break; default: @@ -512,8 +526,11 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re } } catch (\Throwable $th) { $response = [ - 'code' => $th->getCode(), - 'message' => $th->getMessage() + 'type' => 'error', + 'data' => [ + 'code' => $th->getCode(), + 'message' => $th->getMessage() + ] ]; $server->send([$connection], json_encode($response)); diff --git a/tests/e2e/Services/Realtime/RealtimeBase.php b/tests/e2e/Services/Realtime/RealtimeBase.php index b9f4f5f84..3716e38ff 100644 --- a/tests/e2e/Services/Realtime/RealtimeBase.php +++ b/tests/e2e/Services/Realtime/RealtimeBase.php @@ -42,15 +42,23 @@ trait RealtimeBase */ $client = $this->getWebsocket(['documents'], ['origin' => 'http://appwrite.unknown']); $payload = json_decode($client->receive(), true); - $this->assertEquals(1008, $payload['code']); - $this->assertEquals('Invalid Origin. Register your new client (appwrite.unknown) as a new Web platform on your project console dashboard', $payload['message']); + + $this->assertArrayHasKey('type', $payload); + $this->assertArrayHasKey('data', $payload); + $this->assertEquals('error', $payload['type']); + $this->assertEquals(1008, $payload['data']['code']); + $this->assertEquals('Invalid Origin. Register your new client (appwrite.unknown) as a new Web platform on your project console dashboard', $payload['data']['message']); $this->expectException(ConnectionException::class); // Check if server disconnnected client $client->close(); $client = $this->getWebsocket(); $payload = json_decode($client->receive(), true); - $this->assertEquals(1008, $payload['code']); - $this->assertEquals('Missing channels', $payload['message']); + + $this->assertArrayHasKey('type', $payload); + $this->assertArrayHasKey('data', $payload); + $this->assertEquals('error', $payload['type']); + $this->assertEquals(1008, $payload['data']['code']); + $this->assertEquals('Missing channels', $payload['data']['message']); $this->expectException(ConnectionException::class); // Check if server disconnnected client $client->close(); @@ -60,8 +68,12 @@ trait RealtimeBase ] ]); $payload = json_decode($client->receive(), true); - $this->assertEquals(1008, $payload['code']); - $this->assertEquals('Missing or unknown project ID', $payload['message']); + + $this->assertArrayHasKey('type', $payload); + $this->assertArrayHasKey('data', $payload); + $this->assertEquals('error', $payload['type']); + $this->assertEquals(1008, $payload['data']['code']); + $this->assertEquals('Missing or unknown project ID', $payload['data']['message']); $this->expectException(ConnectionException::class); // Check if server disconnnected client $client->close(); @@ -71,8 +83,12 @@ trait RealtimeBase ] ]); $payload = json_decode($client->receive(), true); - $this->assertEquals(1008, $payload['code']); - $this->assertEquals('Missing or unknown project ID', $payload['message']); + + $this->assertArrayHasKey('type', $payload); + $this->assertArrayHasKey('data', $payload); + $this->assertEquals('error', $payload['type']); + $this->assertEquals(1008, $payload['data']['code']); + $this->assertEquals('Missing or unknown project ID', $payload['data']['message']); $this->expectException(ConnectionException::class); // Check if server disconnnected client $client->close(); } @@ -95,14 +111,16 @@ trait RealtimeBase $client = $this->getWebsocket(['account'], $headers); $response = json_decode($client->receive(), true); - $this->assertCount(1, $response); + $this->assertCount(2, $response); + $this->assertArrayHasKey('account', $response); $this->assertArrayHasKey('account.' . $userId, $response); $client->close(); $client = $this->getWebsocket(['account', 'documents', 'account.123'], $headers); $response = json_decode($client->receive(), true); - $this->assertCount(2, $response); + $this->assertCount(3, $response); $this->assertArrayHasKey('documents', $response); + $this->assertArrayHasKey('account', $response); $this->assertArrayHasKey('account.' . $userId, $response); $client->close(); @@ -119,9 +137,11 @@ trait RealtimeBase 'documents.1', 'documents.2', ], $headers); + $response = json_decode($client->receive(), true); - $this->assertCount(11, $response); + $this->assertCount(12, $response); + $this->assertArrayHasKey('account', $response); $this->assertArrayHasKey('account.' . $userId, $response); $this->assertArrayHasKey('files', $response); $this->assertArrayHasKey('files.1', $response); @@ -136,6 +156,40 @@ trait RealtimeBase $client->close(); } + public function testAuthenticateMessage() + { + $user = $this->getUser(); + $userId = $user['$id'] ?? ''; + $session = $user['session'] ?? ''; + $projectId = $this->getProject()['$id']; + + $client = $this->getWebsocket(['account'], [ + 'origin' => 'http://localhost' + ]); + $response = json_decode($client->receive(), true); + + $this->assertCount(1, $response); + $this->assertArrayHasKey('account', $response); + + $client->send(\json_encode([ + 'type' => 'authentication', + 'data' => [ + 'session' => $session + ] + ])); + + $response = json_decode($client->receive(), true); + + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('response', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertEquals('authentication', $response['data']['to']); + $this->assertTrue($response['data']['success']); + + $client->close(); + } + public function testChannelAccount() { $user = $this->getUser(); @@ -148,7 +202,9 @@ trait RealtimeBase 'cookie' => 'a_session_'.$projectId.'=' . $session ]); $response = json_decode($client->receive(), true); - $this->assertCount(1, $response); + + $this->assertCount(2, $response); + $this->assertArrayHasKey('account', $response); $this->assertArrayHasKey('account.' . $userId, $response); /** @@ -167,14 +223,18 @@ trait RealtimeBase $response = json_decode($client->receive(), true); - $this->assertCount(2, $response['channels']); - $this->assertArrayHasKey('timestamp', $response); - $this->assertContains('account', $response['channels']); - $this->assertContains('account.' . $userId, $response['channels']); - $this->assertEquals('account.update.name', $response['event']); - $this->assertNotEmpty($response['payload']); + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('event', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertCount(2, $response['data']['channels']); + $this->assertArrayHasKey('timestamp', $response['data']); + $this->assertContains('account', $response['data']['channels']); + $this->assertContains('account.' . $userId, $response['data']['channels']); + $this->assertEquals('account.update.name', $response['data']['event']); + $this->assertNotEmpty($response['data']['payload']); - $this->assertEquals($name, $response['payload']['name']); + $this->assertEquals($name, $response['data']['payload']['name']); /** @@ -192,14 +252,18 @@ trait RealtimeBase $response = json_decode($client->receive(), true); - $this->assertCount(2, $response['channels']); - $this->assertArrayHasKey('timestamp', $response); - $this->assertContains('account', $response['channels']); - $this->assertContains('account.' . $userId, $response['channels']); - $this->assertEquals('account.update.password', $response['event']); - $this->assertNotEmpty($response['payload']); + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('event', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertCount(2, $response['data']['channels']); + $this->assertArrayHasKey('timestamp', $response['data']); + $this->assertContains('account', $response['data']['channels']); + $this->assertContains('account.' . $userId, $response['data']['channels']); + $this->assertEquals('account.update.password', $response['data']['event']); + $this->assertNotEmpty($response['data']['payload']); - $this->assertEquals($name, $response['payload']['name']); + $this->assertEquals($name, $response['data']['payload']['name']); /** * Test Account Email Update @@ -216,14 +280,18 @@ trait RealtimeBase $response = json_decode($client->receive(), true); - $this->assertCount(2, $response['channels']); - $this->assertArrayHasKey('timestamp', $response); - $this->assertContains('account', $response['channels']); - $this->assertContains('account.' . $userId, $response['channels']); - $this->assertEquals('account.update.email', $response['event']); - $this->assertNotEmpty($response['payload']); + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('event', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertCount(2, $response['data']['channels']); + $this->assertArrayHasKey('timestamp', $response['data']); + $this->assertContains('account', $response['data']['channels']); + $this->assertContains('account.' . $userId, $response['data']['channels']); + $this->assertEquals('account.update.email', $response['data']['event']); + $this->assertNotEmpty($response['data']['payload']); - $this->assertEquals('torsten@appwrite.io', $response['payload']['email']); + $this->assertEquals('torsten@appwrite.io', $response['data']['payload']['email']); /** * Test Account Verification Create @@ -239,11 +307,15 @@ trait RealtimeBase $response = json_decode($client->receive(), true); - $this->assertCount(2, $response['channels']); - $this->assertArrayHasKey('timestamp', $response); - $this->assertContains('account', $response['channels']); - $this->assertContains('account.' . $userId, $response['channels']); - $this->assertEquals('account.verification.create', $response['event']); + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('event', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertCount(2, $response['data']['channels']); + $this->assertArrayHasKey('timestamp', $response['data']); + $this->assertContains('account', $response['data']['channels']); + $this->assertContains('account.' . $userId, $response['data']['channels']); + $this->assertEquals('account.verification.create', $response['data']['event']); $lastEmail = $this->getLastEmail(); $verification = substr($lastEmail['text'], strpos($lastEmail['text'], '&secret=', 0) + 8, 256); @@ -263,11 +335,15 @@ trait RealtimeBase $response = json_decode($client->receive(), true); - $this->assertCount(2, $response['channels']); - $this->assertArrayHasKey('timestamp', $response); - $this->assertContains('account', $response['channels']); - $this->assertContains('account.' . $userId, $response['channels']); - $this->assertEquals('account.verification.update', $response['event']); + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('event', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertCount(2, $response['data']['channels']); + $this->assertArrayHasKey('timestamp', $response['data']); + $this->assertContains('account', $response['data']['channels']); + $this->assertContains('account.' . $userId, $response['data']['channels']); + $this->assertEquals('account.verification.update', $response['data']['event']); /** * Test Acoount Prefs Update @@ -286,12 +362,16 @@ trait RealtimeBase $response = json_decode($client->receive(), true); - $this->assertCount(2, $response['channels']); - $this->assertArrayHasKey('timestamp', $response); - $this->assertContains('account', $response['channels']); - $this->assertContains('account.' . $userId, $response['channels']); - $this->assertEquals('account.update.prefs', $response['event']); - $this->assertNotEmpty($response['payload']); + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('event', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertCount(2, $response['data']['channels']); + $this->assertArrayHasKey('timestamp', $response['data']); + $this->assertContains('account', $response['data']['channels']); + $this->assertContains('account.' . $userId, $response['data']['channels']); + $this->assertEquals('account.update.prefs', $response['data']['event']); + $this->assertNotEmpty($response['data']['payload']); /** * Test Account Session Create @@ -310,12 +390,16 @@ trait RealtimeBase $response = json_decode($client->receive(), true); - $this->assertCount(2, $response['channels']); - $this->assertArrayHasKey('timestamp', $response); - $this->assertContains('account', $response['channels']); - $this->assertContains('account.' . $userId, $response['channels']); - $this->assertEquals('account.sessions.create', $response['event']); - $this->assertNotEmpty($response['payload']); + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('event', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertCount(2, $response['data']['channels']); + $this->assertArrayHasKey('timestamp', $response['data']); + $this->assertContains('account', $response['data']['channels']); + $this->assertContains('account.' . $userId, $response['data']['channels']); + $this->assertEquals('account.sessions.create', $response['data']['event']); + $this->assertNotEmpty($response['data']['payload']); /** * Test Account Session Delete @@ -329,12 +413,16 @@ trait RealtimeBase $response = json_decode($client->receive(), true); - $this->assertCount(2, $response['channels']); - $this->assertArrayHasKey('timestamp', $response); - $this->assertContains('account', $response['channels']); - $this->assertContains('account.' . $userId, $response['channels']); - $this->assertEquals('account.sessions.delete', $response['event']); - $this->assertNotEmpty($response['payload']); + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('event', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertCount(2, $response['data']['channels']); + $this->assertArrayHasKey('timestamp', $response['data']); + $this->assertContains('account', $response['data']['channels']); + $this->assertContains('account.' . $userId, $response['data']['channels']); + $this->assertEquals('account.sessions.delete', $response['data']['event']); + $this->assertNotEmpty($response['data']['payload']); /** * Test Account Create Recovery @@ -353,12 +441,16 @@ trait RealtimeBase $lastEmail = $this->getLastEmail(); $recovery = substr($lastEmail['text'], strpos($lastEmail['text'], '&secret=', 0) + 8, 256); - $this->assertCount(2, $response['channels']); - $this->assertArrayHasKey('timestamp', $response); - $this->assertContains('account', $response['channels']); - $this->assertContains('account.' . $userId, $response['channels']); - $this->assertEquals('account.recovery.create', $response['event']); - $this->assertNotEmpty($response['payload']); + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('event', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertCount(2, $response['data']['channels']); + $this->assertArrayHasKey('timestamp', $response['data']); + $this->assertContains('account', $response['data']['channels']); + $this->assertContains('account.' . $userId, $response['data']['channels']); + $this->assertEquals('account.recovery.create', $response['data']['event']); + $this->assertNotEmpty($response['data']['payload']); $response = $this->client->call(Client::METHOD_PUT, '/account/recovery', array_merge([ 'origin' => 'http://localhost', @@ -373,12 +465,16 @@ trait RealtimeBase $response = json_decode($client->receive(), true); - $this->assertCount(2, $response['channels']); - $this->assertArrayHasKey('timestamp', $response); - $this->assertContains('account', $response['channels']); - $this->assertContains('account.' . $userId, $response['channels']); - $this->assertEquals('account.recovery.update', $response['event']); - $this->assertNotEmpty($response['payload']); + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('event', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertCount(2, $response['data']['channels']); + $this->assertArrayHasKey('timestamp', $response['data']); + $this->assertContains('account', $response['data']['channels']); + $this->assertContains('account.' . $userId, $response['data']['channels']); + $this->assertEquals('account.recovery.update', $response['data']['event']); + $this->assertNotEmpty($response['data']['payload']); $client->close(); } @@ -394,6 +490,7 @@ trait RealtimeBase 'cookie' => 'a_session_'.$projectId.'=' . $session ]); $response = json_decode($client->receive(), true); + $this->assertCount(2, $response); $this->assertArrayHasKey('documents', $response); $this->assertArrayHasKey('collections', $response); @@ -431,12 +528,16 @@ trait RealtimeBase $response = json_decode($client->receive(), true); - $this->assertArrayHasKey('timestamp', $response); - $this->assertCount(2, $response['channels']); - $this->assertContains('collections', $response['channels']); - $this->assertContains('collections.' . $actors['body']['$id'], $response['channels']); - $this->assertEquals('database.collections.create', $response['event']); - $this->assertNotEmpty($response['payload']); + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('event', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertArrayHasKey('timestamp', $response['data']); + $this->assertCount(2, $response['data']['channels']); + $this->assertContains('collections', $response['data']['channels']); + $this->assertContains('collections.' . $actors['body']['$id'], $response['data']['channels']); + $this->assertEquals('database.collections.create', $response['data']['event']); + $this->assertNotEmpty($response['data']['payload']); $data = ['actorsId' => $actors['body']['$id']]; @@ -457,13 +558,17 @@ trait RealtimeBase $response = json_decode($client->receive(), true); - $this->assertArrayHasKey('timestamp', $response); - $this->assertCount(3, $response['channels']); - $this->assertContains('documents', $response['channels']); - $this->assertContains('documents.' . $document['body']['$id'], $response['channels']); - $this->assertContains('collections.' . $actors['body']['$id'] . '.documents', $response['channels']); - $this->assertEquals('database.documents.create', $response['event']); - $this->assertNotEmpty($response['payload']); + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('event', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertArrayHasKey('timestamp', $response['data']); + $this->assertCount(3, $response['data']['channels']); + $this->assertContains('documents', $response['data']['channels']); + $this->assertContains('documents.' . $document['body']['$id'], $response['data']['channels']); + $this->assertContains('collections.' . $actors['body']['$id'] . '.documents', $response['data']['channels']); + $this->assertEquals('database.documents.create', $response['data']['event']); + $this->assertNotEmpty($response['data']['payload']); $data['documentId'] = $document['body']['$id']; @@ -484,16 +589,20 @@ trait RealtimeBase $response = json_decode($client->receive(), true); - $this->assertArrayHasKey('timestamp', $response); - $this->assertCount(3, $response['channels']); - $this->assertContains('documents', $response['channels']); - $this->assertContains('documents.' . $data['documentId'], $response['channels']); - $this->assertContains('collections.' . $data['actorsId'] . '.documents', $response['channels']); - $this->assertEquals('database.documents.update', $response['event']); - $this->assertNotEmpty($response['payload']); + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('event', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertArrayHasKey('timestamp', $response['data']); + $this->assertCount(3, $response['data']['channels']); + $this->assertContains('documents', $response['data']['channels']); + $this->assertContains('documents.' . $data['documentId'], $response['data']['channels']); + $this->assertContains('collections.' . $data['actorsId'] . '.documents', $response['data']['channels']); + $this->assertEquals('database.documents.update', $response['data']['event']); + $this->assertNotEmpty($response['data']['payload']); - $this->assertEquals($response['payload']['firstName'], 'Chris1'); - $this->assertEquals($response['payload']['lastName'], 'Evans2'); + $this->assertEquals($response['data']['payload']['firstName'], 'Chris1'); + $this->assertEquals($response['data']['payload']['lastName'], 'Evans2'); /** * Test Document Delete @@ -520,13 +629,17 @@ trait RealtimeBase $response = json_decode($client->receive(), true); - $this->assertArrayHasKey('timestamp', $response); - $this->assertCount(3, $response['channels']); - $this->assertContains('documents', $response['channels']); - $this->assertContains('documents.' . $document['body']['$id'], $response['channels']); - $this->assertContains('collections.' . $data['actorsId'] . '.documents', $response['channels']); - $this->assertEquals('database.documents.delete', $response['event']); - $this->assertNotEmpty($response['payload']); + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('event', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertArrayHasKey('timestamp', $response['data']); + $this->assertCount(3, $response['data']['channels']); + $this->assertContains('documents', $response['data']['channels']); + $this->assertContains('documents.' . $document['body']['$id'], $response['data']['channels']); + $this->assertContains('collections.' . $data['actorsId'] . '.documents', $response['data']['channels']); + $this->assertEquals('database.documents.delete', $response['data']['event']); + $this->assertNotEmpty($response['data']['payload']); $client->close(); } @@ -560,12 +673,16 @@ trait RealtimeBase $response = json_decode($client->receive(), true); - $this->assertArrayHasKey('timestamp', $response); - $this->assertCount(2, $response['channels']); - $this->assertContains('files', $response['channels']); - $this->assertContains('files.' . $file['body']['$id'], $response['channels']); - $this->assertEquals('storage.files.create', $response['event']); - $this->assertNotEmpty($response['payload']); + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('event', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertArrayHasKey('timestamp', $response['data']); + $this->assertCount(2, $response['data']['channels']); + $this->assertContains('files', $response['data']['channels']); + $this->assertContains('files.' . $file['body']['$id'], $response['data']['channels']); + $this->assertEquals('storage.files.create', $response['data']['event']); + $this->assertNotEmpty($response['data']['payload']); $data = ['fileId' => $file['body']['$id']]; @@ -582,12 +699,16 @@ trait RealtimeBase $response = json_decode($client->receive(), true); - $this->assertArrayHasKey('timestamp', $response); - $this->assertCount(2, $response['channels']); - $this->assertContains('files', $response['channels']); - $this->assertContains('files.' . $file['body']['$id'], $response['channels']); - $this->assertEquals('storage.files.update', $response['event']); - $this->assertNotEmpty($response['payload']); + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('event', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertArrayHasKey('timestamp', $response['data']); + $this->assertCount(2, $response['data']['channels']); + $this->assertContains('files', $response['data']['channels']); + $this->assertContains('files.' . $file['body']['$id'], $response['data']['channels']); + $this->assertEquals('storage.files.update', $response['data']['event']); + $this->assertNotEmpty($response['data']['payload']); /** * Test File Delete @@ -599,12 +720,16 @@ trait RealtimeBase $response = json_decode($client->receive(), true); - $this->assertArrayHasKey('timestamp', $response); - $this->assertCount(2, $response['channels']); - $this->assertContains('files', $response['channels']); - $this->assertContains('files.' . $file['body']['$id'], $response['channels']); - $this->assertEquals('storage.files.delete', $response['event']); - $this->assertNotEmpty($response['payload']); + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('event', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertArrayHasKey('timestamp', $response['data']); + $this->assertCount(2, $response['data']['channels']); + $this->assertContains('files', $response['data']['channels']); + $this->assertContains('files.' . $file['body']['$id'], $response['data']['channels']); + $this->assertEquals('storage.files.delete', $response['data']['event']); + $this->assertNotEmpty($response['data']['payload']); $client->close(); } @@ -678,21 +803,29 @@ trait RealtimeBase $response = json_decode($client->receive(), true); $responseUpdate = json_decode($client->receive(), true); - $this->assertArrayHasKey('timestamp', $response); - $this->assertCount(3, $response['channels']); - $this->assertContains('executions', $response['channels']); - $this->assertContains('executions.' . $execution['body']['$id'], $response['channels']); - $this->assertContains('functions.' . $execution['body']['functionId'], $response['channels']); - $this->assertEquals('functions.executions.create', $response['event']); - $this->assertNotEmpty($response['payload']); + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('event', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertArrayHasKey('timestamp', $response['data']); + $this->assertCount(3, $response['data']['channels']); + $this->assertContains('executions', $response['data']['channels']); + $this->assertContains('executions.' . $execution['body']['$id'], $response['data']['channels']); + $this->assertContains('functions.' . $execution['body']['functionId'], $response['data']['channels']); + $this->assertEquals('functions.executions.create', $response['data']['event']); + $this->assertNotEmpty($response['data']['payload']); - $this->assertArrayHasKey('timestamp', $responseUpdate); - $this->assertCount(3, $responseUpdate['channels']); - $this->assertContains('executions', $responseUpdate['channels']); - $this->assertContains('executions.' . $execution['body']['$id'], $responseUpdate['channels']); - $this->assertContains('functions.' . $execution['body']['functionId'], $responseUpdate['channels']); - $this->assertEquals('functions.executions.update', $responseUpdate['event']); - $this->assertNotEmpty($responseUpdate['payload']); + $this->assertArrayHasKey('type', $responseUpdate); + $this->assertArrayHasKey('data', $responseUpdate); + $this->assertEquals('event', $responseUpdate['type']); + $this->assertNotEmpty($responseUpdate['data']); + $this->assertArrayHasKey('timestamp', $responseUpdate['data']); + $this->assertCount(3, $responseUpdate['data']['channels']); + $this->assertContains('executions', $responseUpdate['data']['channels']); + $this->assertContains('executions.' . $execution['body']['$id'], $responseUpdate['data']['channels']); + $this->assertContains('functions.' . $execution['body']['functionId'], $responseUpdate['data']['channels']); + $this->assertEquals('functions.executions.update', $responseUpdate['data']['event']); + $this->assertNotEmpty($responseUpdate['data']['payload']); $client->close(); } @@ -730,12 +863,16 @@ trait RealtimeBase $response = json_decode($client->receive(), true); - $this->assertArrayHasKey('timestamp', $response); - $this->assertCount(2, $response['channels']); - $this->assertContains('teams', $response['channels']); - $this->assertContains('teams.' . $teamId, $response['channels']); - $this->assertEquals('teams.create', $response['event']); - $this->assertNotEmpty($response['payload']); + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('event', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertArrayHasKey('timestamp', $response['data']); + $this->assertCount(2, $response['data']['channels']); + $this->assertContains('teams', $response['data']['channels']); + $this->assertContains('teams.' . $teamId, $response['data']['channels']); + $this->assertEquals('teams.create', $response['data']['event']); + $this->assertNotEmpty($response['data']['payload']); /** * Test Team Update @@ -752,12 +889,16 @@ trait RealtimeBase $response = json_decode($client->receive(), true); - $this->assertArrayHasKey('timestamp', $response); - $this->assertCount(2, $response['channels']); - $this->assertContains('teams', $response['channels']); - $this->assertContains('teams.' . $teamId, $response['channels']); - $this->assertEquals('teams.update', $response['event']); - $this->assertNotEmpty($response['payload']); + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('event', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertArrayHasKey('timestamp', $response['data']); + $this->assertCount(2, $response['data']['channels']); + $this->assertContains('teams', $response['data']['channels']); + $this->assertContains('teams.' . $teamId, $response['data']['channels']); + $this->assertEquals('teams.update', $response['data']['event']); + $this->assertNotEmpty($response['data']['payload']); $client->close(); @@ -805,12 +946,17 @@ trait RealtimeBase ]); $response = json_decode($client->receive(), true); - $this->assertArrayHasKey('timestamp', $response); - $this->assertCount(2, $response['channels']); - $this->assertContains('memberships', $response['channels']); - $this->assertContains('memberships.' . $membershipId, $response['channels']); - $this->assertEquals('teams.memberships.update', $response['event']); - $this->assertNotEmpty($response['payload']); + + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('event', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertArrayHasKey('timestamp', $response['data']); + $this->assertCount(2, $response['data']['channels']); + $this->assertContains('memberships', $response['data']['channels']); + $this->assertContains('memberships.' . $membershipId, $response['data']['channels']); + $this->assertEquals('teams.memberships.update', $response['data']['event']); + $this->assertNotEmpty($response['data']['payload']); $client->close(); } diff --git a/tests/unit/Messaging/MessagingTest.php b/tests/unit/Messaging/MessagingTest.php index 0d7aa4c3a..19285d030 100644 --- a/tests/unit/Messaging/MessagingTest.php +++ b/tests/unit/Messaging/MessagingTest.php @@ -149,11 +149,11 @@ class MessagingTest extends TestCase ]; $channels = Realtime::convertChannels($channels, $user->getId()); - $this->assertCount(3, $channels); + $this->assertCount(4, $channels); $this->assertArrayHasKey('files', $channels); $this->assertArrayHasKey('documents', $channels); $this->assertArrayHasKey('documents.789', $channels); - $this->assertArrayNotHasKey('account', $channels); + $this->assertArrayHasKey('account', $channels); $this->assertArrayNotHasKey('account.456', $channels); } @@ -187,12 +187,12 @@ class MessagingTest extends TestCase $channels = Realtime::convertChannels($channels, $user->getId()); - $this->assertCount(4, $channels); + $this->assertCount(5, $channels); $this->assertArrayHasKey('files', $channels); $this->assertArrayHasKey('documents', $channels); $this->assertArrayHasKey('documents.789', $channels); $this->assertArrayHasKey('account.123', $channels); - $this->assertArrayNotHasKey('account', $channels); + $this->assertArrayHasKey('account', $channels); $this->assertArrayNotHasKey('account.456', $channels); } } From dc79be6b50d37aa410ff36a058ae7e55df8947d0 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Fri, 27 Aug 2021 11:20:49 +0200 Subject: [PATCH 4/7] adapt messages and tests --- app/realtime.php | 22 ++- tests/e2e/Services/Realtime/RealtimeBase.php | 146 +++++++++++++------ 2 files changed, 120 insertions(+), 48 deletions(-) diff --git a/app/realtime.php b/app/realtime.php index 22efe787e..c574fda0f 100644 --- a/app/realtime.php +++ b/app/realtime.php @@ -8,6 +8,7 @@ use Appwrite\Database\Validator\Authorization; use Appwrite\Event\Event; use Appwrite\Messaging\Adapter\Realtime; use Appwrite\Network\Validator\Origin; +use Appwrite\Utopia\Response; use Swoole\Http\Request as SwooleRequest; use Swoole\Http\Response as SwooleResponse; use Swoole\Runtime; @@ -19,7 +20,6 @@ use Utopia\App; use Utopia\CLI\Console; use Utopia\Config\Config; use Utopia\Swoole\Request; -use Utopia\Swoole\Response; use Utopia\WebSocket\Server; use Utopia\WebSocket\Adapter; @@ -331,6 +331,7 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats, $server->onOpen(function (int $connection, SwooleRequest $request) use ($server, $register, $stats, &$realtime) { $app = new App('UTC'); $request = new Request($request); + $response = new Response(new SwooleResponse()); /** @var PDO $db */ $db = $register->get('dbPool')->get(); @@ -351,8 +352,8 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server, return $request; }); - App::setResource('response', function () { - return new Response(new SwooleResponse()); + App::setResource('response', function () use ($response) { + return $response; }); try { @@ -414,7 +415,15 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server, $realtime->subscribe($project->getId(), $connection, $roles, $channels); - $server->send([$connection], json_encode($channels)); + $user = empty($user->getId()) ? null : $response->output($user, Response::MODEL_USER); + + $server->send([$connection], json_encode([ + 'type' => 'connected', + 'data' => [ + 'channels' => array_keys($channels), + 'user' => $user + ] + ])); $stats->set($project->getId(), [ 'projectId' => $project->getId(), @@ -454,6 +463,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server, $server->onMessage(function (int $connection, string $message) use ($server, $register, $realtime, $containerId) { try { + $response = new Response(new SwooleResponse()); $db = $register->get('dbPool')->get(); $cache = $register->get('redisPool')->get(); @@ -510,11 +520,13 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re $channels = Realtime::convertChannels(array_flip($realtime->connections[$connection]['channels']), $user->getId()); $realtime->subscribe($realtime->connections[$connection]['projectId'], $connection, $roles, $channels); + $user = $response->output($user, Response::MODEL_USER); $server->send([$connection], json_encode([ 'type' => 'response', 'data' => [ 'to' => 'authentication', - 'success' => true + 'success' => true, + 'user' => $user ] ])); diff --git a/tests/e2e/Services/Realtime/RealtimeBase.php b/tests/e2e/Services/Realtime/RealtimeBase.php index 3716e38ff..50c004100 100644 --- a/tests/e2e/Services/Realtime/RealtimeBase.php +++ b/tests/e2e/Services/Realtime/RealtimeBase.php @@ -32,9 +32,7 @@ trait RealtimeBase * Test for SUCCESS */ $client = $this->getWebsocket(['documents']); - $this->assertEquals(json_encode([ - 'documents' => 0 - ]), $client->receive()); + $this->assertNotEmpty($client->receive()); $client->close(); /** @@ -103,25 +101,49 @@ trait RealtimeBase 'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session ]; - $client = $this->getWebsocket(['documents']); + $client = $this->getWebsocket(['documents'], $headers); $response = json_decode($client->receive(), true); - $this->assertCount(1, $response); - $this->assertArrayHasKey('documents', $response); + + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('connected', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertNotEmpty($response['data']['user']); + $this->assertCount(1, $response['data']['channels']); + $this->assertContains('documents', $response['data']['channels']); + $this->assertEquals($userId, $response['data']['user']['$id']); + $client->close(); $client = $this->getWebsocket(['account'], $headers); $response = json_decode($client->receive(), true); - $this->assertCount(2, $response); - $this->assertArrayHasKey('account', $response); - $this->assertArrayHasKey('account.' . $userId, $response); + + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('connected', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertNotEmpty($response['data']['user']); + $this->assertCount(2, $response['data']['channels']); + $this->assertContains('account', $response['data']['channels']); + $this->assertContains('account.' . $userId, $response['data']['channels']); + $this->assertEquals($userId, $response['data']['user']['$id']); + $client->close(); $client = $this->getWebsocket(['account', 'documents', 'account.123'], $headers); $response = json_decode($client->receive(), true); - $this->assertCount(3, $response); - $this->assertArrayHasKey('documents', $response); - $this->assertArrayHasKey('account', $response); - $this->assertArrayHasKey('account.' . $userId, $response); + + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('connected', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertNotEmpty($response['data']['user']); + $this->assertCount(3, $response['data']['channels']); + $this->assertContains('documents', $response['data']['channels']); + $this->assertContains('account', $response['data']['channels']); + $this->assertContains('account.' . $userId, $response['data']['channels']); + $this->assertEquals($userId, $response['data']['user']['$id']); + $client->close(); $client = $this->getWebsocket([ @@ -140,19 +162,26 @@ trait RealtimeBase $response = json_decode($client->receive(), true); - $this->assertCount(12, $response); - $this->assertArrayHasKey('account', $response); - $this->assertArrayHasKey('account.' . $userId, $response); - $this->assertArrayHasKey('files', $response); - $this->assertArrayHasKey('files.1', $response); - $this->assertArrayHasKey('collections', $response); - $this->assertArrayHasKey('collections.1', $response); - $this->assertArrayHasKey('collections.1.documents', $response); - $this->assertArrayHasKey('collections.2', $response); - $this->assertArrayHasKey('collections.2.documents', $response); - $this->assertArrayHasKey('documents', $response); - $this->assertArrayHasKey('documents.1', $response); - $this->assertArrayHasKey('documents.2', $response); + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('connected', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertNotEmpty($response['data']['user']); + $this->assertCount(12, $response['data']['channels']); + $this->assertContains('account', $response['data']['channels']); + $this->assertContains('account.' . $userId, $response['data']['channels']); + $this->assertContains('files', $response['data']['channels']); + $this->assertContains('files.1', $response['data']['channels']); + $this->assertContains('collections', $response['data']['channels']); + $this->assertContains('collections.1', $response['data']['channels']); + $this->assertContains('collections.1.documents', $response['data']['channels']); + $this->assertContains('collections.2', $response['data']['channels']); + $this->assertContains('collections.2.documents', $response['data']['channels']); + $this->assertContains('documents', $response['data']['channels']); + $this->assertContains('documents.1', $response['data']['channels']); + $this->assertContains('documents.2', $response['data']['channels']); + $this->assertEquals($userId, $response['data']['user']['$id']); + $client->close(); } @@ -168,8 +197,12 @@ trait RealtimeBase ]); $response = json_decode($client->receive(), true); - $this->assertCount(1, $response); - $this->assertArrayHasKey('account', $response); + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('connected', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertCount(1, $response['data']['channels']); + $this->assertContains('account', $response['data']['channels']); $client->send(\json_encode([ 'type' => 'authentication', @@ -203,10 +236,13 @@ trait RealtimeBase ]); $response = json_decode($client->receive(), true); - $this->assertCount(2, $response); - $this->assertArrayHasKey('account', $response); - $this->assertArrayHasKey('account.' . $userId, $response); - + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('connected', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertCount(2, $response['data']['channels']); + $this->assertContains('account', $response['data']['channels']); + $this->assertContains('account.' . $userId, $response['data']['channels']); /** * Test Account Name Event */ @@ -489,11 +525,16 @@ trait RealtimeBase 'origin' => 'http://localhost', 'cookie' => 'a_session_'.$projectId.'=' . $session ]); + $response = json_decode($client->receive(), true); - $this->assertCount(2, $response); - $this->assertArrayHasKey('documents', $response); - $this->assertArrayHasKey('collections', $response); + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('connected', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertCount(2, $response['data']['channels']); + $this->assertContains('documents', $response['data']['channels']); + $this->assertContains('collections', $response['data']['channels']); /** * Test Collection Create @@ -655,8 +696,13 @@ trait RealtimeBase 'cookie' => 'a_session_'.$projectId.'=' . $session ]); $response = json_decode($client->receive(), true); - $this->assertCount(1, $response); - $this->assertArrayHasKey('files', $response); + + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('connected', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertCount(1, $response['data']['channels']); + $this->assertContains('files', $response['data']['channels']); /** * Test File Create @@ -744,9 +790,15 @@ trait RealtimeBase 'origin' => 'http://localhost', 'cookie' => 'a_session_'.$projectId.'=' . $session ]); + $response = json_decode($client->receive(), true); - $this->assertCount(1, $response); - $this->assertArrayHasKey('executions', $response); + + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('connected', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertCount(1, $response['data']['channels']); + $this->assertContains('executions', $response['data']['channels']); /** * Test File Create @@ -843,8 +895,12 @@ trait RealtimeBase $response = json_decode($client->receive(), true); - $this->assertCount(1, $response); - $this->assertArrayHasKey('teams', $response); + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('connected', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertCount(1, $response['data']['channels']); + $this->assertContains('teams', $response['data']['channels']); /** * Test Team Create @@ -923,8 +979,12 @@ trait RealtimeBase $response = json_decode($client->receive(), true); - $this->assertCount(1, $response); - $this->assertArrayHasKey('memberships', $response); + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('connected', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertCount(1, $response['data']['channels']); + $this->assertContains('memberships', $response['data']['channels']); $response = $this->client->call(Client::METHOD_GET, '/teams/'.$teamId.'/memberships', array_merge([ 'content-type' => 'application/json', From 821aa45d7d6b48275d0523e84df78097aed93a9a Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Fri, 27 Aug 2021 11:31:26 +0200 Subject: [PATCH 5/7] add tests against failure --- app/realtime.php | 6 +- tests/e2e/Services/Realtime/RealtimeBase.php | 83 +++++++++++++++++++- 2 files changed, 85 insertions(+), 4 deletions(-) diff --git a/app/realtime.php b/app/realtime.php index c574fda0f..78c4f8d1d 100644 --- a/app/realtime.php +++ b/app/realtime.php @@ -498,7 +498,7 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re switch ($message['type']) { case 'authentication': if (!array_key_exists('session', $message['data'])) { - throw new Exception('Payload not valid.', 1003); + throw new Exception('Payload is not valid.', 1003); } $session = Auth::decodeSession($message['data']['session']); @@ -513,7 +513,7 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re || !Auth::sessionVerify($user->getAttribute('sessions', []), Auth::$secret) // Validate user has valid login token ) { // cookie not valid - throw new Exception('Session not valid.', 1003); + throw new Exception('Session is not valid.', 1003); } $roles = Auth::getRoles($user); @@ -533,7 +533,7 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re break; default: - throw new Exception('Message type not valid.', 1003); + throw new Exception('Message type is not valid.', 1003); break; } } catch (\Throwable $th) { diff --git a/tests/e2e/Services/Realtime/RealtimeBase.php b/tests/e2e/Services/Realtime/RealtimeBase.php index 50c004100..d1afacb7a 100644 --- a/tests/e2e/Services/Realtime/RealtimeBase.php +++ b/tests/e2e/Services/Realtime/RealtimeBase.php @@ -185,13 +185,16 @@ trait RealtimeBase $client->close(); } - public function testAuthenticateMessage() + public function testManualAuthentication() { $user = $this->getUser(); $userId = $user['$id'] ?? ''; $session = $user['session'] ?? ''; $projectId = $this->getProject()['$id']; + /** + * Test for SUCCESS + */ $client = $this->getWebsocket(['account'], [ 'origin' => 'http://localhost' ]); @@ -219,6 +222,71 @@ trait RealtimeBase $this->assertNotEmpty($response['data']); $this->assertEquals('authentication', $response['data']['to']); $this->assertTrue($response['data']['success']); + $this->assertNotEmpty($response['data']['user']); + $this->assertEquals($userId, $response['data']['user']['$id']); + + /** + * Test for FAILURE + */ + $client->send(\json_encode([ + 'type' => 'authentication', + 'data' => [ + 'session' => 'invalid_session' + ] + ])); + + $response = json_decode($client->receive(), true); + + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('error', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertEquals(1003, $response['data']['code']); + $this->assertEquals('Session is not valid.', $response['data']['message']); + + $client->send(\json_encode([ + 'type' => 'authentication', + 'data' => [] + ])); + + $response = json_decode($client->receive(), true); + + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('error', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertEquals(1003, $response['data']['code']); + $this->assertEquals('Payload is not valid.', $response['data']['message']); + + $client->send(\json_encode([ + 'type' => 'unknown', + 'data' => [ + 'session' => 'invalid_session' + ] + ])); + + $response = json_decode($client->receive(), true); + + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('error', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertEquals(1003, $response['data']['code']); + $this->assertEquals('Message type is not valid.', $response['data']['message']); + + $client->send(\json_encode([ + 'test' => '123', + ])); + + $response = json_decode($client->receive(), true); + + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('error', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertEquals(1003, $response['data']['code']); + $this->assertEquals('Message format is not valid.', $response['data']['message']); + $client->close(); } @@ -243,6 +311,9 @@ trait RealtimeBase $this->assertCount(2, $response['data']['channels']); $this->assertContains('account', $response['data']['channels']); $this->assertContains('account.' . $userId, $response['data']['channels']); + $this->assertNotEmpty($response['data']['user']); + $this->assertEquals($userId, $response['data']['user']['$id']); + /** * Test Account Name Event */ @@ -535,6 +606,8 @@ trait RealtimeBase $this->assertCount(2, $response['data']['channels']); $this->assertContains('documents', $response['data']['channels']); $this->assertContains('collections', $response['data']['channels']); + $this->assertNotEmpty($response['data']['user']); + $this->assertEquals($user['$id'], $response['data']['user']['$id']); /** * Test Collection Create @@ -703,6 +776,8 @@ trait RealtimeBase $this->assertNotEmpty($response['data']); $this->assertCount(1, $response['data']['channels']); $this->assertContains('files', $response['data']['channels']); + $this->assertNotEmpty($response['data']['user']); + $this->assertEquals($user['$id'], $response['data']['user']['$id']); /** * Test File Create @@ -799,6 +874,8 @@ trait RealtimeBase $this->assertNotEmpty($response['data']); $this->assertCount(1, $response['data']['channels']); $this->assertContains('executions', $response['data']['channels']); + $this->assertNotEmpty($response['data']['user']); + $this->assertEquals($user['$id'], $response['data']['user']['$id']); /** * Test File Create @@ -901,6 +978,8 @@ trait RealtimeBase $this->assertNotEmpty($response['data']); $this->assertCount(1, $response['data']['channels']); $this->assertContains('teams', $response['data']['channels']); + $this->assertNotEmpty($response['data']['user']); + $this->assertEquals($user['$id'], $response['data']['user']['$id']); /** * Test Team Create @@ -985,6 +1064,8 @@ trait RealtimeBase $this->assertNotEmpty($response['data']); $this->assertCount(1, $response['data']['channels']); $this->assertContains('memberships', $response['data']['channels']); + $this->assertNotEmpty($response['data']['user']); + $this->assertEquals($user['$id'], $response['data']['user']['$id']); $response = $this->client->call(Client::METHOD_GET, '/teams/'.$teamId.'/memberships', array_merge([ 'content-type' => 'application/json', From 6fa1a141b100511cd0bcde21dc55b31bf8956588 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Fri, 27 Aug 2021 12:29:03 +0200 Subject: [PATCH 6/7] fix(realtime): tests and stats channels --- app/realtime.php | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/app/realtime.php b/app/realtime.php index 78c4f8d1d..d355af4ae 100644 --- a/app/realtime.php +++ b/app/realtime.php @@ -208,11 +208,14 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats, 'project' => 'console', 'roles' => ['team:' . $value['teamId']], 'data' => [ - 'event' => 'stats.connections', - 'channels' => ['project'], - 'timestamp' => time(), - 'payload' => [ - $projectId => $payload[$projectId] + 'type' => 'event', + 'data' => [ + 'event' => 'stats.connections', + 'channels' => ['project'], + 'timestamp' => time(), + 'payload' => [ + $projectId => $payload[$projectId] + ] ] ] ]; @@ -233,10 +236,13 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats, 'project' => 'console', 'roles' => ['role:guest'], 'data' => [ - 'event' => 'test.event', - 'channels' => ['tests'], - 'timestamp' => time(), - 'payload' => $payload + 'type' => 'event', + 'data' => [ + 'event' => 'test.event', + 'channels' => ['tests'], + 'timestamp' => time(), + 'payload' => $payload + ] ] ]; From e1b448cdd87a10312d7f87ed8032615eb6f98821 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Fri, 27 Aug 2021 13:26:26 +0200 Subject: [PATCH 7/7] fix(realtime): test and stats message --- app/realtime.php | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/app/realtime.php b/app/realtime.php index d355af4ae..93d3fa359 100644 --- a/app/realtime.php +++ b/app/realtime.php @@ -208,19 +208,19 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats, 'project' => 'console', 'roles' => ['team:' . $value['teamId']], 'data' => [ - 'type' => 'event', - 'data' => [ - 'event' => 'stats.connections', - 'channels' => ['project'], - 'timestamp' => time(), - 'payload' => [ - $projectId => $payload[$projectId] - ] + 'event' => 'stats.connections', + 'channels' => ['project'], + 'timestamp' => time(), + 'payload' => [ + $projectId => $payload[$projectId] ] ] ]; - $server->send($realtime->getSubscribers($event), json_encode($event['data'])); + $server->send($realtime->getSubscribers($event), json_encode([ + 'type' => 'event', + 'data' => $event['data'] + ])); } $register->get('dbPool')->put($db); @@ -236,17 +236,17 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats, 'project' => 'console', 'roles' => ['role:guest'], 'data' => [ - 'type' => 'event', - 'data' => [ - 'event' => 'test.event', - 'channels' => ['tests'], - 'timestamp' => time(), - 'payload' => $payload - ] + 'event' => 'test.event', + 'channels' => ['tests'], + 'timestamp' => time(), + 'payload' => $payload ] ]; - $server->send($realtime->getSubscribers($event), json_encode($event['data'])); + $server->send($realtime->getSubscribers($event), json_encode([ + 'type' => 'event', + 'data' => $event['data'] + ])); } });