From f9827c545c1b8e0b5741ddea3279be8dfa277c78 Mon Sep 17 00:00:00 2001 From: azjezz Date: Mon, 20 Jun 2022 20:01:54 +0100 Subject: [PATCH] feat: allow using authorization query parameter Signed-off-by: azjezz --- .../AuthorizationHeaderTokenExtractor.php | 14 ++++--- .../JWT/Extractor/ChainTokenExtractor.php | 1 + .../JWT/Extractor/CookieTokenExtractor.php | 9 +++- .../JWT/Extractor/QueryTokenExtractor.php | 31 ++++++++++++++ .../AuthorizationHeaderExtractorTest.php | 4 +- .../JWT/Extractor/ChainTokenExtractorTest.php | 42 +++++++++++++++++-- .../JWT/Extractor/CookieExtractorTest.php | 14 +++++-- .../JWT/Extractor/QueryTokenExtractorTest.php | 21 ++++++++++ 8 files changed, 121 insertions(+), 15 deletions(-) create mode 100644 src/Security/JWT/Extractor/QueryTokenExtractor.php create mode 100644 tests/Unit/Security/JWT/Extractor/QueryTokenExtractorTest.php diff --git a/src/Security/JWT/Extractor/AuthorizationHeaderTokenExtractor.php b/src/Security/JWT/Extractor/AuthorizationHeaderTokenExtractor.php index 95b3bc3..f23f347 100644 --- a/src/Security/JWT/Extractor/AuthorizationHeaderTokenExtractor.php +++ b/src/Security/JWT/Extractor/AuthorizationHeaderTokenExtractor.php @@ -6,11 +6,15 @@ use Psr\Http\Message\ServerRequestInterface; +use function strlen; +use function str_starts_with; +use function substr; + final class AuthorizationHeaderTokenExtractor implements PSR7TokenExtractorInterface { public function __construct( private string $name = 'Authorization', - private string $prefix = 'Bearer', + private string $prefix = 'Bearer ', ) { } @@ -21,12 +25,12 @@ public function extract(ServerRequestInterface $request): ?string } $authorizationHeader = $request->getHeaderLine($this->name); - $headerParts = explode(' ', $authorizationHeader); - - if (!(2 === count($headerParts) && 0 === strcasecmp($headerParts[0], $this->prefix))) { + if (!str_starts_with($authorizationHeader, $this->prefix)) { return null; } - return $headerParts[1]; + $token = substr($authorizationHeader, strlen($this->prefix)); + + return strlen($token) < 41 ? null : $token; } } diff --git a/src/Security/JWT/Extractor/ChainTokenExtractor.php b/src/Security/JWT/Extractor/ChainTokenExtractor.php index d645929..6255896 100644 --- a/src/Security/JWT/Extractor/ChainTokenExtractor.php +++ b/src/Security/JWT/Extractor/ChainTokenExtractor.php @@ -18,6 +18,7 @@ public function __construct( private iterable $tokenExtractors = [ new CookieTokenExtractor(), new AuthorizationHeaderTokenExtractor(), + new QueryTokenExtractor(), ], ) { } diff --git a/src/Security/JWT/Extractor/CookieTokenExtractor.php b/src/Security/JWT/Extractor/CookieTokenExtractor.php index 3a4fd0b..1ec921b 100644 --- a/src/Security/JWT/Extractor/CookieTokenExtractor.php +++ b/src/Security/JWT/Extractor/CookieTokenExtractor.php @@ -7,8 +7,9 @@ use Psr\Http\Message\ServerRequestInterface; use function array_map; -use function Freddie\nullify; use function implode; +use function is_string; +use function strlen; final class CookieTokenExtractor implements PSR7TokenExtractorInterface { @@ -36,6 +37,10 @@ public function extract(ServerRequestInterface $request): ?string ), ); - return nullify($token); + if (!is_string($token) || strlen($token) < 41) { + return null; + } + + return $token; } } diff --git a/src/Security/JWT/Extractor/QueryTokenExtractor.php b/src/Security/JWT/Extractor/QueryTokenExtractor.php new file mode 100644 index 0000000..f6d222a --- /dev/null +++ b/src/Security/JWT/Extractor/QueryTokenExtractor.php @@ -0,0 +1,31 @@ +getUri(), new FlatQueryParser()); + $authorizationQuery = $qs->getParam($this->name); + if (!is_string($authorizationQuery) || strlen($authorizationQuery) < 41) { + return null; + } + + return $authorizationQuery; + } +} diff --git a/tests/Unit/Security/JWT/Extractor/AuthorizationHeaderExtractorTest.php b/tests/Unit/Security/JWT/Extractor/AuthorizationHeaderExtractorTest.php index 5d7dd92..215702f 100644 --- a/tests/Unit/Security/JWT/Extractor/AuthorizationHeaderExtractorTest.php +++ b/tests/Unit/Security/JWT/Extractor/AuthorizationHeaderExtractorTest.php @@ -12,7 +12,9 @@ $request = new ServerRequest('GET', '/.well-known/mercure', $headers); expect($extractor->extract($request))->toBe($expected); })->with(function () { - yield [['Authorization' => 'Bearer foobar'], 'foobar']; + $validToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30._esyynAyo2Z6PyGe0mM_SuQ3c-C7sMQJ1YxVLvlj80A'; + + yield [['Authorization' => 'Bearer ' . $validToken], $validToken]; yield [['Authorization' => 'foobar'], null]; yield [['Authorization' => ''], null]; yield [[], null]; diff --git a/tests/Unit/Security/JWT/Extractor/ChainTokenExtractorTest.php b/tests/Unit/Security/JWT/Extractor/ChainTokenExtractorTest.php index b6e416d..c670b03 100644 --- a/tests/Unit/Security/JWT/Extractor/ChainTokenExtractorTest.php +++ b/tests/Unit/Security/JWT/Extractor/ChainTokenExtractorTest.php @@ -15,24 +15,60 @@ $extractor = new ChainTokenExtractor(); expect($extractor->extract($request))->toBe($expected); })->with(function () { + $validToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30._esyynAyo2Z6PyGe0mM_SuQ3c-C7sMQJ1YxVLvlj80A'; + + yield [ + 'request' => new ServerRequest('GET', '/.well-known/mercure', [ + 'Cookie' => 'mercureAuthorization=' . $validToken, + ]), + 'expected' => $validToken, + ]; + yield [ + 'request' => new ServerRequest('GET', '/.well-known/mercure?authorization=' . $validToken), + 'expected' => $validToken, + ]; + yield [ + 'request' => new ServerRequest('GET', '/.well-known/mercure', [ + 'Cookie' => 'mercureAuthorization=' . $validToken, + 'Authorization' => 'Bearer foo', + ]), + 'expected' => $validToken, + ]; + yield [ + 'request' => new ServerRequest('GET', '/.well-known/mercure', [ + 'Cookie' => 'mercureAuthorization=foo', + 'Authorization' => 'Bearer ' . $validToken, + ]), + 'expected' => $validToken, + ]; + yield [ + 'request' => new ServerRequest('GET', '/.well-known/mercure', [ + 'Authorization' => 'Bearer ' . $validToken, + ]), + 'expected' => $validToken, + ]; yield [ 'request' => new ServerRequest('GET', '/.well-known/mercure', [ 'Cookie' => 'mercureAuthorization=foobar', ]), - 'expected' => 'foobar', + 'expected' => null, ]; yield [ 'request' => new ServerRequest('GET', '/.well-known/mercure', [ 'Cookie' => 'mercureAuthorization=foo', 'Authorization' => 'Bearer bar', ]), - 'expected' => 'foo', + 'expected' => null, ]; yield [ 'request' => new ServerRequest('GET', '/.well-known/mercure', [ 'Authorization' => 'Bearer foobar', ]), - 'expected' => 'foobar', + 'expected' => null, + ]; + yield [ + 'request' => new ServerRequest('GET', '/.well-known/mercure?authorization=foobar'), + 'expected' => null, ]; yield [ 'request' => new ServerRequest('GET', '/.well-known/mercure'), diff --git a/tests/Unit/Security/JWT/Extractor/CookieExtractorTest.php b/tests/Unit/Security/JWT/Extractor/CookieExtractorTest.php index f2618c7..1998dcb 100644 --- a/tests/Unit/Security/JWT/Extractor/CookieExtractorTest.php +++ b/tests/Unit/Security/JWT/Extractor/CookieExtractorTest.php @@ -8,17 +8,23 @@ use React\Http\Message\ServerRequest; it('extracts token from cookies', function () { + $validToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30._esyynAyo2Z6PyGe0mM_SuQ3c-C7sMQJ1YxVLvlj80A'; + $extractor = new CookieTokenExtractor(); $request = new ServerRequest('GET', '/.well-known/mercure', [ - 'Cookie' => 'foo=bar; mercureAuthorization=foobar; bar=foo', + 'Cookie' => 'foo=bar; mercureAuthorization=' . $validToken . '; bar=foo', ]); - expect($extractor->extract($request))->toBe('foobar'); + expect($extractor->extract($request))->toBe($validToken); }); it('is compliant with the split-cookie strategy', function () { + $jwt_hp = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30'; + $jwt_s = '_esyynAyo2Z6PyGe0mM_SuQ3c-C7sMQJ1YxVLvlj80A'; + $validToken = $jwt_hp . '.' . $jwt_s; + $extractor = new CookieTokenExtractor(['jwt_hp', 'jwt_s']); $request = new ServerRequest('GET', '/.well-known/mercure', [ - 'Cookie' => 'foo=bar; jwt_hp=foobar; bar=foo; jwt_s=signed', + 'Cookie' => 'foo=bar; jwt_hp=' . $jwt_hp . '; bar=foo; jwt_s=' . $jwt_s, ]); - expect($extractor->extract($request))->toBe('foobar.signed'); + expect($extractor->extract($request))->toBe($validToken); }); diff --git a/tests/Unit/Security/JWT/Extractor/QueryTokenExtractorTest.php b/tests/Unit/Security/JWT/Extractor/QueryTokenExtractorTest.php new file mode 100644 index 0000000..b4537f6 --- /dev/null +++ b/tests/Unit/Security/JWT/Extractor/QueryTokenExtractorTest.php @@ -0,0 +1,21 @@ +extract($request))->toBe($expected); +})->with(function () { + $validToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30._esyynAyo2Z6PyGe0mM_SuQ3c-C7sMQJ1YxVLvlj80A'; + + yield ['/.well-known/mercure?authorization=' . $validToken, $validToken]; + yield ['/.well-known/mercure?authorization=foo', null]; + yield ['/.well-known/mercure?authorization', null]; + yield ['/.well-known/mercure', null]; +});