This commit is contained in:
Alex Renoki 2020-08-23 19:12:22 +03:00
parent b46cfadaa2
commit f62ac8fd56
11 changed files with 257 additions and 35 deletions

1
.gitignore vendored
View File

@ -4,3 +4,4 @@ vendor
coverage
.phpunit.result.cache
.idea/
database.sqlite

View File

@ -0,0 +1,32 @@
<?php
namespace BeyondCode\LaravelWebSockets\Contracts;
use BeyondCode\LaravelWebSockets\PubSub\Broadcasters\RedisPusherBroadcaster;
use Illuminate\Broadcasting\Broadcasters\PusherBroadcaster;
use Pusher\Pusher;
trait PushesToPusher
{
/**
* Get the right Pusher broadcaster for the used driver.
*
* @param array $app
* @return \Illuminate\Broadcasting\Broadcasters\Broadcaster
*/
public function getPusherBroadcaster(array $app)
{
if (config('websockets.replication.driver') === 'redis') {
return new RedisPusherBroadcaster(
new Pusher($app['key'], $app['secret'], $app['id'], config('broadcasting.connections.websockets.options', [])),
$app['id'],
app('redis'),
config('broadcasting.connections.websockets.connection', null)
);
}
return new PusherBroadcaster(
new Pusher($app['key'], $app['secret'], $app['id'], config('broadcasting.connections.pusher.options', [])),
);
}
}

View File

@ -3,12 +3,15 @@
namespace BeyondCode\LaravelWebSockets\Dashboard\Http\Controllers;
use BeyondCode\LaravelWebSockets\Apps\App;
use BeyondCode\LaravelWebSockets\Contracts\PushesToPusher;
use Illuminate\Broadcasting\Broadcasters\PusherBroadcaster;
use Illuminate\Http\Request;
use Pusher\Pusher;
class AuthenticateDashboard
{
use PushesToPusher;
/**
* Find the app by using the header
* and then reconstruct the PusherBroadcaster
@ -21,12 +24,11 @@ class AuthenticateDashboard
{
$app = App::findById($request->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

View File

@ -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);
}
}

View File

@ -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;

View File

@ -0,0 +1,100 @@
<?php
namespace BeyondCode\LaravelWebSockets\Tests\Dashboard;
use BeyondCode\LaravelWebSockets\Tests\TestCase;
use BeyondCode\LaravelWebSockets\Tests\Models\User;
use BeyondCode\LaravelWebSockets\Tests\Mocks\Message;
class AuthTest extends TestCase
{
/** @test */
public function can_authenticate_dashboard_over_channel()
{
$connection = $this->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',
]);
}
}

View File

@ -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');
}

16
tests/Models/User.php Normal file
View File

@ -0,0 +1,16 @@
<?php
namespace BeyondCode\LaravelWebSockets\Tests\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
protected $fillable = [
'name', 'email', 'password',
];
protected $hidden = [
'password', 'remember_token',
];
}

View File

@ -46,6 +46,12 @@ abstract class TestCase extends BaseTestCase
{
parent::setUp();
$this->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);
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace BeyondCode\LaravelWebSockets\Tests;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\ServiceProvider;
class TestServiceProvider extends ServiceProvider
{
/**
* Boot the service provider.
*
* @return void
*/
public function boot()
{
Gate::define('viewWebSocketsDashboard', function ($user = null) {
return ! is_null($user);
});
}
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
//
}
}

View File

@ -0,0 +1,22 @@
<?php
/*
|--------------------------------------------------------------------------
| Model Factories
|--------------------------------------------------------------------------
|
| This directory should contain each of the model factory definitions for
| your application. Factories provide a convenient way to generate new
| model instances for testing / seeding your application's database.
|
*/
use Illuminate\Support\Str;
$factory->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),
];
});