Skip to content

Commit 1205342

Browse files
committed
SignUrl: Allowed more methods
1 parent dac4d7c commit 1205342

File tree

2 files changed

+23
-22
lines changed

2 files changed

+23
-22
lines changed

src/Plugin/SignedUrl.php

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
/**
1515
* @phpstan-type ParsedUrl array{'scheme'?: string, 'host'?: string, 'port'?: int, 'user'?: string, 'pass'?: string, 'path'?: string, 'query'?: string, 'fragment'?: string}
16-
* @phpstan-type ClaimsSet array{'iss': string, 'aud': string|null, 'iat': int, 'exp': int, 'sub': string, 'meth': string, 'mod': int, 'val': int}
16+
* @phpstan-type ClaimsSet array{'iss': string, 'aud': string|null, 'iat': int, 'exp': int, 'sub': string, 'meth': array<int, string>, 'mod': int, 'val': int}
1717
*/
1818
class SignedUrl implements Plugin
1919
{
@@ -65,6 +65,7 @@ public function signUrl(
6565
int $mode = self::MODE_REQUEST,
6666
int $value = self::VALUE_ENABLE
6767
): string {
68+
/** @var ParsedUrl|false $parsedUrl */
6869
$parsedUrl = parse_url($url);
6970

7071
if ($parsedUrl === false) {
@@ -100,7 +101,7 @@ public function getToken(
100101
'iat' => $this->timestamp ?? time(),
101102
'exp' => $expire,
102103
'sub' => $url,
103-
'meth' => self::HTTP_METHOD_GET,
104+
'meth' => [self::HTTP_METHOD_GET],
104105
'mod' => $mode,
105106
'val' => $value,
106107
];
@@ -143,30 +144,30 @@ public function verifyRequest(bool $allowRedirect = false, ?string $url = null,
143144
$url = $url ?? $this->urlFromGlobal();
144145
$method = $method ?? $_SERVER['REQUEST_METHOD'];
145146

146-
[$allowedMethod, $mode, $value, $expires] = $this->verifyUrl($url);
147+
[$allowedMethods, $mode, $value, $expires] = $this->verifyUrl($url);
147148

148-
if (strcasecmp($method, $allowedMethod) !== 0) {
149+
if (in_array(strtolower($method), $allowedMethods, true) === false) {
149150
throw new SignedUrlVerificationException('HTTP method doesn\'t match signed HTTP method');
150151
}
151152

152153
return [$mode, $value, $expires];
153154
}
154155

155156
/**
156-
* @return array{string, int, int, int}
157+
* @return array{array<int, string>, int, int, int}
157158
*/
158159
public function verifyUrl(string $url, bool $allowRedirect = false): array
159160
{
160-
/** @noinspection CallableParameterUseCaseInTypeContextInspection */
161-
$url = parse_url($url);
161+
/** @var ParsedUrl|false $parsedUrl */
162+
$parsedUrl = parse_url($url);
162163

163-
if ($url === false) {
164+
if ($parsedUrl === false) {
164165
throw new SignedUrlVerificationException('Url is invalid');
165166
}
166167

167168
// Parse token from Query string
168169
// Note: Not parsed by parse_str() to prevent broke URL (repeated arguments like `?same_arg=1&same_arg=2`)
169-
$query = $url['query'] ?? '';
170+
$query = $parsedUrl['query'] ?? '';
170171
if (preg_match(
171172
'/(?<token_key>[?&]' . self::URL_QUERY_TOKEN_KEY . '=)(?<token>[^&]+)(?:$|(?<remaining>&.*$))/D',
172173
$query,
@@ -181,34 +182,34 @@ public function verifyUrl(string $url, bool $allowRedirect = false): array
181182
$remainingOffset = $matches['remaining'][1] ?? null;
182183

183184
// Parse token – when token invalid, no URL canonicalization proceed
184-
[$allowedUrl, $allowedMethod, $mode, $value, $expires] = $this->verifyToken($token);
185+
[$allowedUrl, $allowedMethods, $mode, $value, $expires] = $this->verifyToken($token);
185186

186187
// Some apps modifing URL
187188
if ($remainingOffset !== null) {
188189
if ($allowRedirect === false) {
189190
throw new SignedUrlVerificationException('URL contains unallowed queries after Signing Token');
190191
}
191192

192-
$canonicalUrl = $this->buildUrl(['query' => substr($query, 0, $remainingOffset)] + $url);
193+
$canonicalUrl = $this->buildUrl(['query' => substr($query, 0, $remainingOffset)] + $parsedUrl);
193194
$this->sendRedirectResponse($canonicalUrl);
194195
}
195196

196-
$signedUrl = $this->buildUrl(['query' => substr($query, 0, $tokenOffset)] + $url);
197+
$signedUrl = $this->buildUrl(['query' => substr($query, 0, $tokenOffset)] + $parsedUrl);
197198

198199
if ($signedUrl !== $allowedUrl) {
199200
throw new SignedUrlVerificationException('URL doesn\'t match signed URL');
200201
}
201202

202-
return [$allowedMethod, $mode, $value, $expires];
203+
return [$allowedMethods, $mode, $value, $expires];
203204
}
204205

205206
/**
206-
* @return array{string, string, int, int, int}
207+
* @return array{string, array<int, string>, int, int, int}
207208
*/
208209
public function verifyToken(string $token): array
209210
{
210211
try {
211-
/** @var ClaimsSet */
212+
/** @var ClaimsSet $payload */
212213
$payload = JWT::decode($token, $this->key, [$this->algorithm]);
213214
} catch (RuntimeException $e) {
214215
throw new SignedUrlVerificationException('JWT Token invalid', 0, $e);
@@ -247,11 +248,11 @@ public function verifyToken(string $token): array
247248
}
248249

249250
return [
250-
(string)$payload->sub,
251-
(string)$payload->meth,
251+
$payload->sub,
252+
$payload->meth,
252253
$payload->mod,
253254
$payload->val,
254-
(int)$payload->exp
255+
$payload->exp
255256
];
256257
}
257258

tests/Plugin/SignUrlTest.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public function testSign(): void
2424
$plugin = new SignedUrl(self::KEY_HS256, 'HS256', $audience);
2525
$plugin->setTimestamp(1600000000);
2626
$token = $plugin->signUrl('https://host.tld/path?query=value', 1600000600);
27-
$expected = 'https://host.tld/path?query=value&_debug=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJjei5yZWRiaXQuZGVidWcudXJsIiwiYXVkIjoidGVzdC50ZXN0U2lnbiIsImlhdCI6MTYwMDAwMDAwMCwiZXhwIjoxNjAwMDAwNjAwLCJzdWIiOiJodHRwczpcL1wvaG9zdC50bGRcL3BhdGg_cXVlcnk9dmFsdWUiLCJtZXRoIjoiZ2V0IiwibW9kIjowLCJ2YWwiOjF9.61Z0pPW3lJN2WDoUhOfsZ4m16Q3hjtVFJep_t_qoQ5c';
27+
$expected = 'https://host.tld/path?query=value&_debug=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJjei5yZWRiaXQuZGVidWcudXJsIiwiYXVkIjoidGVzdC50ZXN0U2lnbiIsImlhdCI6MTYwMDAwMDAwMCwiZXhwIjoxNjAwMDAwNjAwLCJzdWIiOiJodHRwczpcL1wvaG9zdC50bGRcL3BhdGg_cXVlcnk9dmFsdWUiLCJtZXRoIjpbImdldCJdLCJtb2QiOjAsInZhbCI6MX0.E2__15ZLCOvLRA1jG3tq47DctbZ44sLYYDLXGElMrDs';
2828
Assert::equal($expected, $token);
2929
}
3030

@@ -35,7 +35,7 @@ public function testGetToken(): void
3535
$plugin = new SignedUrl(self::KEY_HS256, 'HS256', $audience);
3636
$plugin->setTimestamp(1600000000);
3737
$token = $plugin->getToken('https://host.tld/path?query=value', 1600000600);
38-
$expected = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJjei5yZWRiaXQuZGVidWcudXJsIiwiYXVkIjoidGVzdC50ZXN0R2V0VG9rZW4iLCJpYXQiOjE2MDAwMDAwMDAsImV4cCI6MTYwMDAwMDYwMCwic3ViIjoiaHR0cHM6XC9cL2hvc3QudGxkXC9wYXRoP3F1ZXJ5PXZhbHVlIiwibWV0aCI6ImdldCIsIm1vZCI6MCwidmFsIjoxfQ.KO0DRN8hsn_MYZI3iRMpw5uRJ9hKh1taex-6k02BwMk';
38+
$expected = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJjei5yZWRiaXQuZGVidWcudXJsIiwiYXVkIjoidGVzdC50ZXN0R2V0VG9rZW4iLCJpYXQiOjE2MDAwMDAwMDAsImV4cCI6MTYwMDAwMDYwMCwic3ViIjoiaHR0cHM6XC9cL2hvc3QudGxkXC9wYXRoP3F1ZXJ5PXZhbHVlIiwibWV0aCI6WyJnZXQiXSwibW9kIjowLCJ2YWwiOjF9.I6tEfFneSxuY9qAjRf5esYFPonChbliZqGoijtv2iHw';
3939
Assert::equal($expected, $token);
4040
}
4141

@@ -52,7 +52,7 @@ public function testVerifyToken(): void
5252
$plugin->setTimestamp($timestamp);
5353
JWT::$timestamp = $timestamp;
5454
$parsed = $plugin->verifyToken($token);
55-
$expected = ['https://host.tld/path?query=value', 'get', 0, 1, 1600000600];
55+
$expected = ['https://host.tld/path?query=value', ['get'], 0, 1, 1600000600];
5656
Assert::equal($expected, $parsed);
5757
}
5858

@@ -70,7 +70,7 @@ public function testVerifyUrl(): void
7070
$plugin->setTimestamp($timestamp);
7171
JWT::$timestamp = $timestamp;
7272
$parsed = $plugin->verifyUrl($tokenUrl);
73-
$expected = ['get', 0, 1, 1600000600];
73+
$expected = [['get'], 0, 1, 1600000600];
7474
Assert::equal($expected, $parsed);
7575
}
7676

0 commit comments

Comments
 (0)