Skip to content

Commit

Permalink
Feat: Query authorization (#32)
Browse files Browse the repository at this point in the history
---------
Signed-off-by: azjezz <azjezz@protonmail.com>
Co-authored-by: azjezz <azjezz@protonmail.com>
  • Loading branch information
bpolaszek committed Mar 20, 2024
1 parent 4219fff commit d2fae04
Show file tree
Hide file tree
Showing 8 changed files with 120 additions and 15 deletions.
14 changes: 9 additions & 5 deletions src/Security/JWT/Extractor/AuthorizationHeaderTokenExtractor.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 ',
) {
}

Expand All @@ -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;
}
}
1 change: 1 addition & 0 deletions src/Security/JWT/Extractor/ChainTokenExtractor.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ final class ChainTokenExtractor implements PSR7TokenExtractorInterface
*/
public function __construct(
private iterable $tokenExtractors = [
new QueryTokenExtractor(),
new CookieTokenExtractor(),
new AuthorizationHeaderTokenExtractor(),
],
Expand Down
9 changes: 7 additions & 2 deletions src/Security/JWT/Extractor/CookieTokenExtractor.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -36,6 +37,10 @@ public function extract(ServerRequestInterface $request): ?string
),
);

return nullify($token);
if (!is_string($token) || strlen($token) < 41) {
return null;
}

return $token;
}
}
30 changes: 30 additions & 0 deletions src/Security/JWT/Extractor/QueryTokenExtractor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace Freddie\Security\JWT\Extractor;

use Psr\Http\Message\ServerRequestInterface;

use function BenTools\QueryString\query_string;
use function is_string;
use function strlen;

final class QueryTokenExtractor implements PSR7TokenExtractorInterface
{
public function __construct(
private string $name = 'authorization',
) {
}

public function extract(ServerRequestInterface $request): ?string
{
$qs = query_string($request->getUri());
$authorizationQuery = $qs->getParam($this->name);
if (!is_string($authorizationQuery) || strlen($authorizationQuery) < 41) {
return null;
}

return $authorizationQuery;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down
42 changes: 39 additions & 3 deletions tests/Unit/Security/JWT/Extractor/ChainTokenExtractorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand Down
14 changes: 10 additions & 4 deletions tests/Unit/Security/JWT/Extractor/CookieExtractorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
21 changes: 21 additions & 0 deletions tests/Unit/Security/JWT/Extractor/QueryTokenExtractorTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

declare(strict_types=1);

namespace Freddie\Tests\Unit\Security\JWT\Extractor;

use Freddie\Security\JWT\Extractor\QueryTokenExtractor;
use React\Http\Message\ServerRequest;

it('extracts token from authorization query parameter', function (string $uri, ?string $expected) {
$extractor = new QueryTokenExtractor();
$request = new ServerRequest('GET', $uri);
expect($extractor->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];
});

0 comments on commit d2fae04

Please sign in to comment.