From f62ac8fd56cce4006c3a233324f8552ddc93c9f1 Mon Sep 17 00:00:00 2001 From: Alex Renoki Date: Sun, 23 Aug 2020 19:12:22 +0300 Subject: [PATCH] wip --- .gitignore | 1 + src/Contracts/PushesToPusher.php | 32 ++++++ .../Controllers/AuthenticateDashboard.php | 14 +-- .../Http/Controllers/SendMessage.php | 37 +++---- src/WebSocketsServiceProvider.php | 6 +- tests/Dashboard/AuthTest.php | 100 ++++++++++++++++++ tests/Dashboard/DashboardTest.php | 6 +- tests/Models/User.php | 16 +++ tests/TestCase.php | 27 +++++ tests/TestServiceProvider.php | 31 ++++++ tests/database/factories/UserFactory.php | 22 ++++ 11 files changed, 257 insertions(+), 35 deletions(-) create mode 100644 src/Contracts/PushesToPusher.php create mode 100644 tests/Dashboard/AuthTest.php create mode 100644 tests/Models/User.php create mode 100644 tests/TestServiceProvider.php create mode 100644 tests/database/factories/UserFactory.php diff --git a/.gitignore b/.gitignore index f423e5b..a4753bd 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ vendor coverage .phpunit.result.cache .idea/ +database.sqlite diff --git a/src/Contracts/PushesToPusher.php b/src/Contracts/PushesToPusher.php new file mode 100644 index 0000000..0a3b092 --- /dev/null +++ b/src/Contracts/PushesToPusher.php @@ -0,0 +1,32 @@ +header('x-app-id')); - $broadcaster = new PusherBroadcaster(new Pusher( - $app->key, - $app->secret, - $app->id, - [] - )); + $broadcaster = $this->getPusherBroadcaster([ + 'key' => $app->key, + 'secret' => $app->secret, + 'id' =>$app->id, + ]); /* * Since the dashboard itself is already secured by the diff --git a/src/Dashboard/Http/Controllers/SendMessage.php b/src/Dashboard/Http/Controllers/SendMessage.php index 92777e4..e3eb1cd 100644 --- a/src/Dashboard/Http/Controllers/SendMessage.php +++ b/src/Dashboard/Http/Controllers/SendMessage.php @@ -3,12 +3,15 @@ namespace BeyondCode\LaravelWebSockets\Dashboard\Http\Controllers; use BeyondCode\LaravelWebSockets\Statistics\Rules\AppId; +use BeyondCode\LaravelWebSockets\Contracts\PushesToPusher; use Illuminate\Broadcasting\Broadcasters\PusherBroadcaster; use Illuminate\Http\Request; use Pusher\Pusher; class SendMessage { + use PushesToPusher; + /** * Send the message to the requested channel. * @@ -17,7 +20,7 @@ class SendMessage */ public function __invoke(Request $request) { - $validated = $request->validate([ + $request->validate([ 'appId' => ['required', new AppId], 'key' => 'required|string', 'secret' => 'required|string', @@ -26,30 +29,18 @@ class SendMessage 'data' => 'required|json', ]); - $this->getPusherBroadcaster($validated)->broadcast( - [$validated['channel']], - $validated['event'], - json_decode($validated['data'], true) + $broadcaster = $this->getPusherBroadcaster([ + 'key' => $request->key, + 'secret' => $request->secret, + 'id' => $request->appId, + ]); + + $broadcaster->broadcast( + [$request->channel], + $request->event, + json_decode($request->data, true) ); return 'ok'; } - - /** - * Get the pusher broadcaster for the current request. - * - * @param array $validated - * @return \Illuminate\Broadcasting\Broadcasters\PusherBroadcaster - */ - protected function getPusherBroadcaster(array $validated): PusherBroadcaster - { - $pusher = new Pusher( - $validated['key'], - $validated['secret'], - $validated['appId'], - config('broadcasting.connections.pusher.options', []) - ); - - return new PusherBroadcaster($pusher); - } } diff --git a/src/WebSocketsServiceProvider.php b/src/WebSocketsServiceProvider.php index f03a504..fd9853b 100644 --- a/src/WebSocketsServiceProvider.php +++ b/src/WebSocketsServiceProvider.php @@ -125,8 +125,8 @@ class WebSocketsServiceProvider extends ServiceProvider ], function () { Route::get('/', ShowDashboard::class)->name('dashboard'); Route::get('/api/{appId}/statistics', [DashboardApiController::class, 'getStatistics'])->name('statistics'); - Route::post('auth', AuthenticateDashboard::class)->name('auth'); - Route::post('event', SendMessage::class)->name('send'); + Route::post('/auth', AuthenticateDashboard::class)->name('auth'); + Route::post('/event', SendMessage::class)->name('send'); }); return $this; @@ -140,7 +140,7 @@ class WebSocketsServiceProvider extends ServiceProvider protected function registerDashboardGate() { Gate::define('viewWebSocketsDashboard', function ($user = null) { - return $this->app->environment(['local', 'testing']); + return $this->app->environment('local'); }); return $this; diff --git a/tests/Dashboard/AuthTest.php b/tests/Dashboard/AuthTest.php new file mode 100644 index 0000000..f1214d4 --- /dev/null +++ b/tests/Dashboard/AuthTest.php @@ -0,0 +1,100 @@ +getConnectedWebSocketConnection(['test-channel']); + + $this->pusherServer->onOpen($connection); + + $this->actingAs(factory(User::class)->create()) + ->json('POST', route('laravel-websockets.auth'), [ + 'socket_id' => $connection->socketId, + 'channel_name' => 'test-channel', + ], ['x-app-id' => '1234']) + ->seeJsonStructure([ + 'auth', + 'channel_data', + ]); + } + + /** @test */ + public function can_authenticate_dashboard_over_private_channel() + { + $connection = $this->getWebSocketConnection(); + + $this->pusherServer->onOpen($connection); + + $signature = "{$connection->socketId}:private-channel"; + + $hashedAppSecret = hash_hmac('sha256', $signature, $connection->app->secret); + + $message = new Message([ + 'event' => 'pusher:subscribe', + 'data' => [ + 'auth' => "{$connection->app->key}:{$hashedAppSecret}", + 'channel' => 'private-channel', + ], + ]); + + $this->pusherServer->onMessage($connection, $message); + + $connection->assertSentEvent('pusher_internal:subscription_succeeded', [ + 'channel' => 'private-channel', + ]); + + $this->actingAs(factory(User::class)->create()) + ->json('POST', route('laravel-websockets.auth'), [ + 'socket_id' => $connection->socketId, + 'channel_name' => 'private-test-channel', + ], ['x-app-id' => '1234']) + ->seeJsonStructure([ + 'auth', + ]); + } + + /** @test */ + public function can_authenticate_dashboard_over_presence_channel() + { + $connection = $this->getWebSocketConnection(); + + $this->pusherServer->onOpen($connection); + + $channelData = [ + 'user_id' => 1, + 'user_info' => [ + 'name' => 'Marcel', + ], + ]; + + $signature = "{$connection->socketId}:presence-channel:".json_encode($channelData); + + $message = new Message([ + 'event' => 'pusher:subscribe', + 'data' => [ + 'auth' => $connection->app->key.':'.hash_hmac('sha256', $signature, $connection->app->secret), + 'channel' => 'presence-channel', + 'channel_data' => json_encode($channelData), + ], + ]); + + $this->pusherServer->onMessage($connection, $message); + + $this->actingAs(factory(User::class)->create()) + ->json('POST', route('laravel-websockets.auth'), [ + 'socket_id' => $connection->socketId, + 'channel_name' => 'presence-channel', + ], ['x-app-id' => '1234']) + ->seeJsonStructure([ + 'auth', + ]); + } +} diff --git a/tests/Dashboard/DashboardTest.php b/tests/Dashboard/DashboardTest.php index ce098ca..7ea6bc0 100644 --- a/tests/Dashboard/DashboardTest.php +++ b/tests/Dashboard/DashboardTest.php @@ -3,14 +3,13 @@ namespace BeyondCode\LaravelWebSockets\Tests\Dashboard; use BeyondCode\LaravelWebSockets\Tests\TestCase; +use BeyondCode\LaravelWebSockets\Tests\Models\User; class DashboardTest extends TestCase { /** @test */ public function cant_see_dashboard_without_authorization() { - config(['app.env' => 'production']); - $this->get(route('laravel-websockets.dashboard')) ->assertResponseStatus(403); } @@ -18,7 +17,8 @@ class DashboardTest extends TestCase /** @test */ public function can_see_dashboard() { - $this->get(route('laravel-websockets.dashboard')) + $this->actingAs(factory(User::class)->create()) + ->get(route('laravel-websockets.dashboard')) ->assertResponseOk() ->see('WebSockets Dashboard'); } diff --git a/tests/Models/User.php b/tests/Models/User.php new file mode 100644 index 0000000..1f134fb --- /dev/null +++ b/tests/Models/User.php @@ -0,0 +1,16 @@ +resetDatabase(); + + $this->loadLaravelMigrations(['--database' => 'sqlite']); + + $this->withFactories(__DIR__.'/database/factories'); + $this->pusherServer = $this->app->make(config('websockets.handlers.websocket')); $this->channelManager = $this->app->make(ChannelManager::class); @@ -69,6 +75,7 @@ abstract class TestCase extends BaseTestCase { return [ \BeyondCode\LaravelWebSockets\WebSocketsServiceProvider::class, + TestServiceProvider::class, ]; } @@ -79,6 +86,16 @@ abstract class TestCase extends BaseTestCase { $app['config']->set('app.key', 'wslxrEFGWY6GfGhvN9L3wH3KSRJQQpBD'); + $app['config']->set('auth.providers.users.model', Models\User::class); + + $app['config']->set('database.default', 'sqlite'); + + $app['config']->set('database.connections.sqlite', [ + 'driver' => 'sqlite', + 'database' => __DIR__.'/database.sqlite', + 'prefix' => '', + ]); + $app['config']->set('websockets.apps', [ [ 'name' => 'Test App', @@ -307,4 +324,14 @@ abstract class TestCase extends BaseTestCase ->make(ReplicationInterface::class) ->getPublishClient(); } + + /** + * Reset the database. + * + * @return void + */ + protected function resetDatabase() + { + file_put_contents(__DIR__.'/database.sqlite', null); + } } diff --git a/tests/TestServiceProvider.php b/tests/TestServiceProvider.php new file mode 100644 index 0000000..958086e --- /dev/null +++ b/tests/TestServiceProvider.php @@ -0,0 +1,31 @@ +define(\BeyondCode\LaravelWebSockets\Tests\Models\User::class, function () { + return [ + 'name' => 'Name'.Str::random(5), + 'email' => Str::random(5).'@gmail.com', + 'password' => '$2y$10$TKh8H1.PfQx37YgCzwiKb.KjNyWgaHb9cbcoQgdIVFlYg7B77UdFm', // secret + 'remember_token' => Str::random(10), + ]; +});