From 83dec0b5f17019eb9598bec5fecc4b878c0de34a Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Wed, 2 Jan 2019 22:30:57 +0100 Subject: [PATCH] Fix signature validation (#38) * Add failing test * Fix signature validation * Fix tests to generate correct signatures * StyleCI fix * Ignore route params when validating the signature * Fix tests to add route params next to signature * StyleCI fixes --- src/HttpApi/Controllers/Controller.php | 20 +++--- tests/HttpApi/FetchChannelTest.php | 61 ++++++---------- tests/HttpApi/FetchChannelsTest.php | 98 ++++++++++++++++---------- tests/HttpApi/FetchUsersTest.php | 81 ++++++++------------- 4 files changed, 122 insertions(+), 138 deletions(-) diff --git a/src/HttpApi/Controllers/Controller.php b/src/HttpApi/Controllers/Controller.php index f88c49c..43f2ebd 100644 --- a/src/HttpApi/Controllers/Controller.php +++ b/src/HttpApi/Controllers/Controller.php @@ -3,6 +3,7 @@ namespace BeyondCode\LaravelWebSockets\HttpApi\Controllers; use Exception; +use Pusher\Pusher; use Illuminate\Http\Request; use GuzzleHttp\Psr7\Response; use Ratchet\ConnectionInterface; @@ -84,18 +85,21 @@ abstract class Controller implements HttpServerInterface protected function ensureValidSignature(Request $request) { - $signature = - "{$request->getMethod()}\n/{$request->path()}\n". - "auth_key={$request->get('auth_key')}". - "&auth_timestamp={$request->get('auth_timestamp')}". - "&auth_version={$request->get('auth_version')}"; + /* + * The `auth_signature` & `body_md5` parameters are not included when calculating the `auth_signature` value. + * + * The `appId`, `appKey` & `channelName` parameters are actually route paramaters and are never supplied by the client. + */ + $params = array_except($request->query(), ['auth_signature', 'body_md5', 'appId', 'appKey', 'channelName']); if ($request->getContent() !== '') { - $bodyMd5 = md5($request->getContent()); - - $signature .= "&body_md5={$bodyMd5}"; + $params['body_md5'] = md5($request->getContent()); } + ksort($params); + + $signature = "{$request->getMethod()}\n/{$request->path()}\n".Pusher::array_implode('=', '&', $params); + $authSignature = hash_hmac('sha256', $signature, App::findById($request->get('appId'))->secret); if ($authSignature !== $request->get('auth_signature')) { diff --git a/tests/HttpApi/FetchChannelTest.php b/tests/HttpApi/FetchChannelTest.php index cabc459..50fcaf1 100644 --- a/tests/HttpApi/FetchChannelTest.php +++ b/tests/HttpApi/FetchChannelTest.php @@ -2,6 +2,7 @@ namespace BeyondCode\LaravelWebSockets\Tests\HttpApi; +use Pusher\Pusher; use GuzzleHttp\Psr7\Request; use Illuminate\Http\JsonResponse; use BeyondCode\LaravelWebSockets\Tests\TestCase; @@ -19,21 +20,15 @@ class FetchChannelTest extends TestCase $connection = new Connection(); - $auth_key = 'TestKey'; - $auth_timestamp = time(); - $auth_version = '1.0'; + $requestPath = '/apps/1234/channel/my-channel'; + $routeParams = [ + 'appId' => '1234', + 'channelName' => 'my-channel', + ]; - $queryParameters = http_build_query(compact('auth_key', 'auth_timestamp', 'auth_version')); + $queryString = Pusher::build_auth_query_string('TestKey', 'InvalidSecret', 'GET', $requestPath); - $signature = - "GET\n/apps/1234/channels\n". - "auth_key={$auth_key}". - "&auth_timestamp={$auth_timestamp}". - "&auth_version={$auth_version}"; - - $auth_signature = hash_hmac('sha256', $signature, 'InvalidSecret'); - - $request = new Request('GET', "/apps/1234/channel/my-channel?appId=1234&channelName=my-channel&auth_signature={$auth_signature}&{$queryParameters}"); + $request = new Request('GET', "{$requestPath}?{$queryString}&".http_build_query($routeParams)); $controller = app(FetchChannelController::class); @@ -48,21 +43,15 @@ class FetchChannelTest extends TestCase $connection = new Connection(); - $auth_key = 'TestKey'; - $auth_timestamp = time(); - $auth_version = '1.0'; + $requestPath = '/apps/1234/channel/my-channel'; + $routeParams = [ + 'appId' => '1234', + 'channelName' => 'my-channel', + ]; - $queryParameters = http_build_query(compact('auth_key', 'auth_timestamp', 'auth_version')); + $queryString = Pusher::build_auth_query_string('TestKey', 'TestSecret', 'GET', $requestPath); - $signature = - "GET\n/apps/1234/channel/my-channel\n". - "auth_key={$auth_key}". - "&auth_timestamp={$auth_timestamp}". - "&auth_version={$auth_version}"; - - $auth_signature = hash_hmac('sha256', $signature, 'TestSecret'); - - $request = new Request('GET', "/apps/1234/channel/my-channel?appId=1234&channelName=my-channel&auth_signature={$auth_signature}&{$queryParameters}"); + $request = new Request('GET', "{$requestPath}?{$queryString}&".http_build_query($routeParams)); $controller = app(FetchChannelController::class); @@ -87,21 +76,15 @@ class FetchChannelTest extends TestCase $connection = new Connection(); - $auth_key = 'TestKey'; - $auth_timestamp = time(); - $auth_version = '1.0'; + $requestPath = '/apps/1234/channel/invalid-channel'; + $routeParams = [ + 'appId' => '1234', + 'channelName' => 'invalid-channel', + ]; - $queryParameters = http_build_query(compact('auth_key', 'auth_timestamp', 'auth_version')); + $queryString = Pusher::build_auth_query_string('TestKey', 'TestSecret', 'GET', $requestPath); - $signature = - "GET\n/apps/1234/channel/my-channel\n". - "auth_key={$auth_key}". - "&auth_timestamp={$auth_timestamp}". - "&auth_version={$auth_version}"; - - $auth_signature = hash_hmac('sha256', $signature, 'TestSecret'); - - $request = new Request('GET', "/apps/1234/channel/my-channel?appId=1234&channelName=invalid-channel&auth_signature={$auth_signature}&{$queryParameters}"); + $request = new Request('GET', "{$requestPath}?{$queryString}&".http_build_query($routeParams)); $controller = app(FetchChannelController::class); diff --git a/tests/HttpApi/FetchChannelsTest.php b/tests/HttpApi/FetchChannelsTest.php index 94a6eb8..8092507 100644 --- a/tests/HttpApi/FetchChannelsTest.php +++ b/tests/HttpApi/FetchChannelsTest.php @@ -2,6 +2,7 @@ namespace BeyondCode\LaravelWebSockets\Tests\HttpApi; +use Pusher\Pusher; use GuzzleHttp\Psr7\Request; use Illuminate\Http\JsonResponse; use BeyondCode\LaravelWebSockets\Tests\TestCase; @@ -19,21 +20,14 @@ class FetchChannelsTest extends TestCase $connection = new Connection(); - $auth_key = 'TestKey'; - $auth_timestamp = time(); - $auth_version = '1.0'; + $requestPath = '/apps/1234/channels'; + $routeParams = [ + 'appId' => '1234', + ]; - $queryParameters = http_build_query(compact('auth_key', 'auth_timestamp', 'auth_version')); + $queryString = Pusher::build_auth_query_string('TestKey', 'InvalidSecret', 'GET', $requestPath); - $signature = - "GET\n/apps/1234/channels\n". - "auth_key={$auth_key}". - "&auth_timestamp={$auth_timestamp}". - "&auth_version={$auth_version}"; - - $auth_signature = hash_hmac('sha256', $signature, 'InvalidSecret'); - - $request = new Request('GET', "/apps/1234/channels?appId=1234&auth_signature={$auth_signature}&{$queryParameters}"); + $request = new Request('GET', "{$requestPath}?{$queryString}&".http_build_query($routeParams)); $controller = app(FetchChannelsController::class); @@ -49,21 +43,14 @@ class FetchChannelsTest extends TestCase $connection = new Connection(); - $auth_key = 'TestKey'; - $auth_timestamp = time(); - $auth_version = '1.0'; + $requestPath = '/apps/1234/channels'; + $routeParams = [ + 'appId' => '1234', + ]; - $queryParameters = http_build_query(compact('auth_key', 'auth_timestamp', 'auth_version')); + $queryString = Pusher::build_auth_query_string('TestKey', 'TestSecret', 'GET', $requestPath); - $signature = - "GET\n/apps/1234/channels\n". - "auth_key={$auth_key}". - "&auth_timestamp={$auth_timestamp}". - "&auth_version={$auth_version}"; - - $auth_signature = hash_hmac('sha256', $signature, 'TestSecret'); - - $request = new Request('GET', "/apps/1234/channels?appId=1234&auth_signature={$auth_signature}&{$queryParameters}"); + $request = new Request('GET', "{$requestPath}?{$queryString}&".http_build_query($routeParams)); $controller = app(FetchChannelsController::class); @@ -81,26 +68,59 @@ class FetchChannelsTest extends TestCase ], json_decode($response->getContent(), true)); } + /** @test */ + public function it_returns_the_channel_information_for_prefix() + { + $this->joinPresenceChannel('presence-global.1'); + $this->joinPresenceChannel('presence-global.1'); + $this->joinPresenceChannel('presence-global.2'); + $this->joinPresenceChannel('presence-notglobal.2'); + + $connection = new Connection(); + + $requestPath = '/apps/1234/channels'; + $routeParams = [ + 'appId' => '1234', + ]; + + $queryString = Pusher::build_auth_query_string('TestKey', 'TestSecret', 'GET', $requestPath, [ + 'filter_by_prefix' => 'presence-global', + ]); + + $request = new Request('GET', "{$requestPath}?{$queryString}&".http_build_query($routeParams)); + + $controller = app(FetchChannelsController::class); + + $controller->onOpen($connection, $request); + + /** @var JsonResponse $response */ + $response = array_pop($connection->sentRawData); + + $this->assertSame([ + 'channels' => [ + 'presence-global.1' => [ + 'user_count' => 2, + ], + 'presence-global.2' => [ + 'user_count' => 1, + ], + ], + ], json_decode($response->getContent(), true)); + } + /** @test */ public function it_returns_empty_object_for_no_channels_found() { $connection = new Connection(); - $auth_key = 'TestKey'; - $auth_timestamp = time(); - $auth_version = '1.0'; + $requestPath = '/apps/1234/channels'; + $routeParams = [ + 'appId' => '1234', + ]; - $queryParameters = http_build_query(compact('auth_key', 'auth_timestamp', 'auth_version')); + $queryString = Pusher::build_auth_query_string('TestKey', 'TestSecret', 'GET', $requestPath); - $signature = - "GET\n/apps/1234/channels\n". - "auth_key={$auth_key}". - "&auth_timestamp={$auth_timestamp}". - "&auth_version={$auth_version}"; - - $auth_signature = hash_hmac('sha256', $signature, 'TestSecret'); - - $request = new Request('GET', "/apps/1234/channels?appId=1234&auth_signature={$auth_signature}&{$queryParameters}"); + $request = new Request('GET', "{$requestPath}?{$queryString}&".http_build_query($routeParams)); $controller = app(FetchChannelsController::class); diff --git a/tests/HttpApi/FetchUsersTest.php b/tests/HttpApi/FetchUsersTest.php index 543aeb0..35a9744 100644 --- a/tests/HttpApi/FetchUsersTest.php +++ b/tests/HttpApi/FetchUsersTest.php @@ -2,6 +2,7 @@ namespace BeyondCode\LaravelWebSockets\Tests\HttpApi; +use Pusher\Pusher; use GuzzleHttp\Psr7\Request; use BeyondCode\LaravelWebSockets\Tests\TestCase; use BeyondCode\LaravelWebSockets\Tests\Mocks\Connection; @@ -18,21 +19,15 @@ class FetchUsersTest extends TestCase $connection = new Connection(); - $auth_key = 'TestKey'; - $auth_timestamp = time(); - $auth_version = '1.0'; + $requestPath = '/apps/1234/channel/my-channel'; + $routeParams = [ + 'appId' => '1234', + 'channelName' => 'my-channel', + ]; - $queryParameters = http_build_query(compact('auth_key', 'auth_timestamp', 'auth_version')); + $queryString = Pusher::build_auth_query_string('TestKey', 'InvalidSecret', 'GET', $requestPath); - $signature = - "GET\n/apps/1234/channels\n". - "auth_key={$auth_key}". - "&auth_timestamp={$auth_timestamp}". - "&auth_version={$auth_version}"; - - $auth_signature = hash_hmac('sha256', $signature, 'InvalidSecret'); - - $request = new Request('GET', "/apps/1234/channel/my-channel?appId=1234&channelName=my-channel&auth_signature={$auth_signature}&{$queryParameters}"); + $request = new Request('GET', "{$requestPath}?{$queryString}&".http_build_query($routeParams)); $controller = app(FetchUsersController::class); @@ -49,21 +44,15 @@ class FetchUsersTest extends TestCase $connection = new Connection(); - $auth_key = 'TestKey'; - $auth_timestamp = time(); - $auth_version = '1.0'; + $requestPath = '/apps/1234/channel/my-channel/users'; + $routeParams = [ + 'appId' => '1234', + 'channelName' => 'my-channel', + ]; - $queryParameters = http_build_query(compact('auth_key', 'auth_timestamp', 'auth_version')); + $queryString = Pusher::build_auth_query_string('TestKey', 'TestSecret', 'GET', $requestPath); - $signature = - "GET\n/apps/1234/channel/my-channel/users\n". - "auth_key={$auth_key}". - "&auth_timestamp={$auth_timestamp}". - "&auth_version={$auth_version}"; - - $auth_signature = hash_hmac('sha256', $signature, 'TestSecret'); - - $request = new Request('GET', "/apps/1234/channel/my-channel/users?appId=1234&channelName=my-channel&auth_signature={$auth_signature}&{$queryParameters}"); + $request = new Request('GET', "{$requestPath}?{$queryString}&".http_build_query($routeParams)); $controller = app(FetchUsersController::class); @@ -80,21 +69,15 @@ class FetchUsersTest extends TestCase $connection = new Connection(); - $auth_key = 'TestKey'; - $auth_timestamp = time(); - $auth_version = '1.0'; + $requestPath = '/apps/1234/channel/invalid-channel/users'; + $routeParams = [ + 'appId' => '1234', + 'channelName' => 'invalid-channel', + ]; - $queryParameters = http_build_query(compact('auth_key', 'auth_timestamp', 'auth_version')); + $queryString = Pusher::build_auth_query_string('TestKey', 'TestSecret', 'GET', $requestPath); - $signature = - "GET\n/apps/1234/channel/my-channel/users\n". - "auth_key={$auth_key}". - "&auth_timestamp={$auth_timestamp}". - "&auth_version={$auth_version}"; - - $auth_signature = hash_hmac('sha256', $signature, 'TestSecret'); - - $request = new Request('GET', "/apps/1234/channel/my-channel/users?appId=1234&channelName=invalid-channel&auth_signature={$auth_signature}&{$queryParameters}"); + $request = new Request('GET', "{$requestPath}?{$queryString}&".http_build_query($routeParams)); $controller = app(FetchUsersController::class); @@ -108,21 +91,15 @@ class FetchUsersTest extends TestCase $connection = new Connection(); - $auth_key = 'TestKey'; - $auth_timestamp = time(); - $auth_version = '1.0'; + $requestPath = '/apps/1234/channel/presence-channel/users'; + $routeParams = [ + 'appId' => '1234', + 'channelName' => 'presence-channel', + ]; - $queryParameters = http_build_query(compact('auth_key', 'auth_timestamp', 'auth_version')); + $queryString = Pusher::build_auth_query_string('TestKey', 'TestSecret', 'GET', $requestPath); - $signature = - "GET\n/apps/1234/channel/my-channel/users\n". - "auth_key={$auth_key}". - "&auth_timestamp={$auth_timestamp}". - "&auth_version={$auth_version}"; - - $auth_signature = hash_hmac('sha256', $signature, 'TestSecret'); - - $request = new Request('GET', "/apps/1234/channel/my-channel/users?appId=1234&channelName=presence-channel&auth_signature={$auth_signature}&{$queryParameters}"); + $request = new Request('GET', "{$requestPath}?{$queryString}&".http_build_query($routeParams)); $controller = app(FetchUsersController::class);