Skip to content
This repository was archived by the owner on Feb 7, 2024. It is now read-only.

Commit 83dec0b

Browse files
stayallivempociot
authored andcommitted
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
1 parent c1f6ffa commit 83dec0b

File tree

4 files changed

+121
-137
lines changed

4 files changed

+121
-137
lines changed

src/HttpApi/Controllers/Controller.php

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace BeyondCode\LaravelWebSockets\HttpApi\Controllers;
44

55
use Exception;
6+
use Pusher\Pusher;
67
use Illuminate\Http\Request;
78
use GuzzleHttp\Psr7\Response;
89
use Ratchet\ConnectionInterface;
@@ -84,18 +85,21 @@ public function ensureValidAppId(string $appId)
8485

8586
protected function ensureValidSignature(Request $request)
8687
{
87-
$signature =
88-
"{$request->getMethod()}\n/{$request->path()}\n".
89-
"auth_key={$request->get('auth_key')}".
90-
"&auth_timestamp={$request->get('auth_timestamp')}".
91-
"&auth_version={$request->get('auth_version')}";
88+
/*
89+
* The `auth_signature` & `body_md5` parameters are not included when calculating the `auth_signature` value.
90+
*
91+
* The `appId`, `appKey` & `channelName` parameters are actually route paramaters and are never supplied by the client.
92+
*/
93+
$params = array_except($request->query(), ['auth_signature', 'body_md5', 'appId', 'appKey', 'channelName']);
9294

9395
if ($request->getContent() !== '') {
94-
$bodyMd5 = md5($request->getContent());
95-
96-
$signature .= "&body_md5={$bodyMd5}";
96+
$params['body_md5'] = md5($request->getContent());
9797
}
9898

99+
ksort($params);
100+
101+
$signature = "{$request->getMethod()}\n/{$request->path()}\n".Pusher::array_implode('=', '&', $params);
102+
99103
$authSignature = hash_hmac('sha256', $signature, App::findById($request->get('appId'))->secret);
100104

101105
if ($authSignature !== $request->get('auth_signature')) {

tests/HttpApi/FetchChannelTest.php

Lines changed: 22 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace BeyondCode\LaravelWebSockets\Tests\HttpApi;
44

5+
use Pusher\Pusher;
56
use GuzzleHttp\Psr7\Request;
67
use Illuminate\Http\JsonResponse;
78
use BeyondCode\LaravelWebSockets\Tests\TestCase;
@@ -19,21 +20,15 @@ public function invalid_signatures_can_not_access_the_api()
1920

2021
$connection = new Connection();
2122

22-
$auth_key = 'TestKey';
23-
$auth_timestamp = time();
24-
$auth_version = '1.0';
23+
$requestPath = '/apps/1234/channel/my-channel';
24+
$routeParams = [
25+
'appId' => '1234',
26+
'channelName' => 'my-channel',
27+
];
2528

26-
$queryParameters = http_build_query(compact('auth_key', 'auth_timestamp', 'auth_version'));
29+
$queryString = Pusher::build_auth_query_string('TestKey', 'InvalidSecret', 'GET', $requestPath);
2730

28-
$signature =
29-
"GET\n/apps/1234/channels\n".
30-
"auth_key={$auth_key}".
31-
"&auth_timestamp={$auth_timestamp}".
32-
"&auth_version={$auth_version}";
33-
34-
$auth_signature = hash_hmac('sha256', $signature, 'InvalidSecret');
35-
36-
$request = new Request('GET', "/apps/1234/channel/my-channel?appId=1234&channelName=my-channel&auth_signature={$auth_signature}&{$queryParameters}");
31+
$request = new Request('GET', "{$requestPath}?{$queryString}&".http_build_query($routeParams));
3732

3833
$controller = app(FetchChannelController::class);
3934

@@ -48,21 +43,15 @@ public function it_returns_the_channel_information()
4843

4944
$connection = new Connection();
5045

51-
$auth_key = 'TestKey';
52-
$auth_timestamp = time();
53-
$auth_version = '1.0';
54-
55-
$queryParameters = http_build_query(compact('auth_key', 'auth_timestamp', 'auth_version'));
46+
$requestPath = '/apps/1234/channel/my-channel';
47+
$routeParams = [
48+
'appId' => '1234',
49+
'channelName' => 'my-channel',
50+
];
5651

57-
$signature =
58-
"GET\n/apps/1234/channel/my-channel\n".
59-
"auth_key={$auth_key}".
60-
"&auth_timestamp={$auth_timestamp}".
61-
"&auth_version={$auth_version}";
52+
$queryString = Pusher::build_auth_query_string('TestKey', 'TestSecret', 'GET', $requestPath);
6253

63-
$auth_signature = hash_hmac('sha256', $signature, 'TestSecret');
64-
65-
$request = new Request('GET', "/apps/1234/channel/my-channel?appId=1234&channelName=my-channel&auth_signature={$auth_signature}&{$queryParameters}");
54+
$request = new Request('GET', "{$requestPath}?{$queryString}&".http_build_query($routeParams));
6655

6756
$controller = app(FetchChannelController::class);
6857

@@ -87,21 +76,15 @@ public function it_returns_404_for_invalid_channels()
8776

8877
$connection = new Connection();
8978

90-
$auth_key = 'TestKey';
91-
$auth_timestamp = time();
92-
$auth_version = '1.0';
93-
94-
$queryParameters = http_build_query(compact('auth_key', 'auth_timestamp', 'auth_version'));
95-
96-
$signature =
97-
"GET\n/apps/1234/channel/my-channel\n".
98-
"auth_key={$auth_key}".
99-
"&auth_timestamp={$auth_timestamp}".
100-
"&auth_version={$auth_version}";
79+
$requestPath = '/apps/1234/channel/invalid-channel';
80+
$routeParams = [
81+
'appId' => '1234',
82+
'channelName' => 'invalid-channel',
83+
];
10184

102-
$auth_signature = hash_hmac('sha256', $signature, 'TestSecret');
85+
$queryString = Pusher::build_auth_query_string('TestKey', 'TestSecret', 'GET', $requestPath);
10386

104-
$request = new Request('GET', "/apps/1234/channel/my-channel?appId=1234&channelName=invalid-channel&auth_signature={$auth_signature}&{$queryParameters}");
87+
$request = new Request('GET', "{$requestPath}?{$queryString}&".http_build_query($routeParams));
10588

10689
$controller = app(FetchChannelController::class);
10790

tests/HttpApi/FetchChannelsTest.php

Lines changed: 58 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace BeyondCode\LaravelWebSockets\Tests\HttpApi;
44

5+
use Pusher\Pusher;
56
use GuzzleHttp\Psr7\Request;
67
use Illuminate\Http\JsonResponse;
78
use BeyondCode\LaravelWebSockets\Tests\TestCase;
@@ -19,21 +20,14 @@ public function invalid_signatures_can_not_access_the_api()
1920

2021
$connection = new Connection();
2122

22-
$auth_key = 'TestKey';
23-
$auth_timestamp = time();
24-
$auth_version = '1.0';
23+
$requestPath = '/apps/1234/channels';
24+
$routeParams = [
25+
'appId' => '1234',
26+
];
2527

26-
$queryParameters = http_build_query(compact('auth_key', 'auth_timestamp', 'auth_version'));
28+
$queryString = Pusher::build_auth_query_string('TestKey', 'InvalidSecret', 'GET', $requestPath);
2729

28-
$signature =
29-
"GET\n/apps/1234/channels\n".
30-
"auth_key={$auth_key}".
31-
"&auth_timestamp={$auth_timestamp}".
32-
"&auth_version={$auth_version}";
33-
34-
$auth_signature = hash_hmac('sha256', $signature, 'InvalidSecret');
35-
36-
$request = new Request('GET', "/apps/1234/channels?appId=1234&auth_signature={$auth_signature}&{$queryParameters}");
30+
$request = new Request('GET', "{$requestPath}?{$queryString}&".http_build_query($routeParams));
3731

3832
$controller = app(FetchChannelsController::class);
3933

@@ -49,21 +43,14 @@ public function it_returns_the_channel_information()
4943

5044
$connection = new Connection();
5145

52-
$auth_key = 'TestKey';
53-
$auth_timestamp = time();
54-
$auth_version = '1.0';
55-
56-
$queryParameters = http_build_query(compact('auth_key', 'auth_timestamp', 'auth_version'));
57-
58-
$signature =
59-
"GET\n/apps/1234/channels\n".
60-
"auth_key={$auth_key}".
61-
"&auth_timestamp={$auth_timestamp}".
62-
"&auth_version={$auth_version}";
46+
$requestPath = '/apps/1234/channels';
47+
$routeParams = [
48+
'appId' => '1234',
49+
];
6350

64-
$auth_signature = hash_hmac('sha256', $signature, 'TestSecret');
51+
$queryString = Pusher::build_auth_query_string('TestKey', 'TestSecret', 'GET', $requestPath);
6552

66-
$request = new Request('GET', "/apps/1234/channels?appId=1234&auth_signature={$auth_signature}&{$queryParameters}");
53+
$request = new Request('GET', "{$requestPath}?{$queryString}&".http_build_query($routeParams));
6754

6855
$controller = app(FetchChannelsController::class);
6956

@@ -82,25 +69,58 @@ public function it_returns_the_channel_information()
8269
}
8370

8471
/** @test */
85-
public function it_returns_empty_object_for_no_channels_found()
72+
public function it_returns_the_channel_information_for_prefix()
8673
{
74+
$this->joinPresenceChannel('presence-global.1');
75+
$this->joinPresenceChannel('presence-global.1');
76+
$this->joinPresenceChannel('presence-global.2');
77+
$this->joinPresenceChannel('presence-notglobal.2');
78+
8779
$connection = new Connection();
8880

89-
$auth_key = 'TestKey';
90-
$auth_timestamp = time();
91-
$auth_version = '1.0';
81+
$requestPath = '/apps/1234/channels';
82+
$routeParams = [
83+
'appId' => '1234',
84+
];
85+
86+
$queryString = Pusher::build_auth_query_string('TestKey', 'TestSecret', 'GET', $requestPath, [
87+
'filter_by_prefix' => 'presence-global',
88+
]);
9289

93-
$queryParameters = http_build_query(compact('auth_key', 'auth_timestamp', 'auth_version'));
90+
$request = new Request('GET', "{$requestPath}?{$queryString}&".http_build_query($routeParams));
91+
92+
$controller = app(FetchChannelsController::class);
93+
94+
$controller->onOpen($connection, $request);
95+
96+
/** @var JsonResponse $response */
97+
$response = array_pop($connection->sentRawData);
98+
99+
$this->assertSame([
100+
'channels' => [
101+
'presence-global.1' => [
102+
'user_count' => 2,
103+
],
104+
'presence-global.2' => [
105+
'user_count' => 1,
106+
],
107+
],
108+
], json_decode($response->getContent(), true));
109+
}
110+
111+
/** @test */
112+
public function it_returns_empty_object_for_no_channels_found()
113+
{
114+
$connection = new Connection();
94115

95-
$signature =
96-
"GET\n/apps/1234/channels\n".
97-
"auth_key={$auth_key}".
98-
"&auth_timestamp={$auth_timestamp}".
99-
"&auth_version={$auth_version}";
116+
$requestPath = '/apps/1234/channels';
117+
$routeParams = [
118+
'appId' => '1234',
119+
];
100120

101-
$auth_signature = hash_hmac('sha256', $signature, 'TestSecret');
121+
$queryString = Pusher::build_auth_query_string('TestKey', 'TestSecret', 'GET', $requestPath);
102122

103-
$request = new Request('GET', "/apps/1234/channels?appId=1234&auth_signature={$auth_signature}&{$queryParameters}");
123+
$request = new Request('GET', "{$requestPath}?{$queryString}&".http_build_query($routeParams));
104124

105125
$controller = app(FetchChannelsController::class);
106126

tests/HttpApi/FetchUsersTest.php

Lines changed: 29 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace BeyondCode\LaravelWebSockets\Tests\HttpApi;
44

5+
use Pusher\Pusher;
56
use GuzzleHttp\Psr7\Request;
67
use BeyondCode\LaravelWebSockets\Tests\TestCase;
78
use BeyondCode\LaravelWebSockets\Tests\Mocks\Connection;
@@ -18,21 +19,15 @@ public function invalid_signatures_can_not_access_the_api()
1819

1920
$connection = new Connection();
2021

21-
$auth_key = 'TestKey';
22-
$auth_timestamp = time();
23-
$auth_version = '1.0';
22+
$requestPath = '/apps/1234/channel/my-channel';
23+
$routeParams = [
24+
'appId' => '1234',
25+
'channelName' => 'my-channel',
26+
];
2427

25-
$queryParameters = http_build_query(compact('auth_key', 'auth_timestamp', 'auth_version'));
28+
$queryString = Pusher::build_auth_query_string('TestKey', 'InvalidSecret', 'GET', $requestPath);
2629

27-
$signature =
28-
"GET\n/apps/1234/channels\n".
29-
"auth_key={$auth_key}".
30-
"&auth_timestamp={$auth_timestamp}".
31-
"&auth_version={$auth_version}";
32-
33-
$auth_signature = hash_hmac('sha256', $signature, 'InvalidSecret');
34-
35-
$request = new Request('GET', "/apps/1234/channel/my-channel?appId=1234&channelName=my-channel&auth_signature={$auth_signature}&{$queryParameters}");
30+
$request = new Request('GET', "{$requestPath}?{$queryString}&".http_build_query($routeParams));
3631

3732
$controller = app(FetchUsersController::class);
3833

@@ -49,21 +44,15 @@ public function it_only_returns_data_for_presence_channels()
4944

5045
$connection = new Connection();
5146

52-
$auth_key = 'TestKey';
53-
$auth_timestamp = time();
54-
$auth_version = '1.0';
55-
56-
$queryParameters = http_build_query(compact('auth_key', 'auth_timestamp', 'auth_version'));
57-
58-
$signature =
59-
"GET\n/apps/1234/channel/my-channel/users\n".
60-
"auth_key={$auth_key}".
61-
"&auth_timestamp={$auth_timestamp}".
62-
"&auth_version={$auth_version}";
47+
$requestPath = '/apps/1234/channel/my-channel/users';
48+
$routeParams = [
49+
'appId' => '1234',
50+
'channelName' => 'my-channel',
51+
];
6352

64-
$auth_signature = hash_hmac('sha256', $signature, 'TestSecret');
53+
$queryString = Pusher::build_auth_query_string('TestKey', 'TestSecret', 'GET', $requestPath);
6554

66-
$request = new Request('GET', "/apps/1234/channel/my-channel/users?appId=1234&channelName=my-channel&auth_signature={$auth_signature}&{$queryParameters}");
55+
$request = new Request('GET', "{$requestPath}?{$queryString}&".http_build_query($routeParams));
6756

6857
$controller = app(FetchUsersController::class);
6958

@@ -80,21 +69,15 @@ public function it_returns_404_for_invalid_channels()
8069

8170
$connection = new Connection();
8271

83-
$auth_key = 'TestKey';
84-
$auth_timestamp = time();
85-
$auth_version = '1.0';
72+
$requestPath = '/apps/1234/channel/invalid-channel/users';
73+
$routeParams = [
74+
'appId' => '1234',
75+
'channelName' => 'invalid-channel',
76+
];
8677

87-
$queryParameters = http_build_query(compact('auth_key', 'auth_timestamp', 'auth_version'));
78+
$queryString = Pusher::build_auth_query_string('TestKey', 'TestSecret', 'GET', $requestPath);
8879

89-
$signature =
90-
"GET\n/apps/1234/channel/my-channel/users\n".
91-
"auth_key={$auth_key}".
92-
"&auth_timestamp={$auth_timestamp}".
93-
"&auth_version={$auth_version}";
94-
95-
$auth_signature = hash_hmac('sha256', $signature, 'TestSecret');
96-
97-
$request = new Request('GET', "/apps/1234/channel/my-channel/users?appId=1234&channelName=invalid-channel&auth_signature={$auth_signature}&{$queryParameters}");
80+
$request = new Request('GET', "{$requestPath}?{$queryString}&".http_build_query($routeParams));
9881

9982
$controller = app(FetchUsersController::class);
10083

@@ -108,21 +91,15 @@ public function it_returns_connected_user_information()
10891

10992
$connection = new Connection();
11093

111-
$auth_key = 'TestKey';
112-
$auth_timestamp = time();
113-
$auth_version = '1.0';
114-
115-
$queryParameters = http_build_query(compact('auth_key', 'auth_timestamp', 'auth_version'));
116-
117-
$signature =
118-
"GET\n/apps/1234/channel/my-channel/users\n".
119-
"auth_key={$auth_key}".
120-
"&auth_timestamp={$auth_timestamp}".
121-
"&auth_version={$auth_version}";
94+
$requestPath = '/apps/1234/channel/presence-channel/users';
95+
$routeParams = [
96+
'appId' => '1234',
97+
'channelName' => 'presence-channel',
98+
];
12299

123-
$auth_signature = hash_hmac('sha256', $signature, 'TestSecret');
100+
$queryString = Pusher::build_auth_query_string('TestKey', 'TestSecret', 'GET', $requestPath);
124101

125-
$request = new Request('GET', "/apps/1234/channel/my-channel/users?appId=1234&channelName=presence-channel&auth_signature={$auth_signature}&{$queryParameters}");
102+
$request = new Request('GET', "{$requestPath}?{$queryString}&".http_build_query($routeParams));
126103

127104
$controller = app(FetchUsersController::class);
128105

0 commit comments

Comments
 (0)