Skip to content

Commit 4468d4a

Browse files
committed
feat(auth): bump jwt library version and extract its usage
1 parent cc0d789 commit 4468d4a

File tree

7 files changed

+409
-193
lines changed

7 files changed

+409
-193
lines changed

composer.json

+7-7
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,16 @@
1111
"guzzlehttp/psr7": "^2.0",
1212
"monolog/monolog": "^3.0",
1313
"nesbot/carbon": "^2.0",
14-
"psr/simple-cache": "^1.0|^2.0|^3.0",
14+
"psr/simple-cache": "^3.0",
1515
"psr/http-client": "^1.0",
1616
"psr/http-client-implementation": "*",
1717
"psr/http-factory": "^1.0",
1818
"psr/http-factory-implementation": "*",
19-
"psr/log": "^1.0|^2.0|^3.0",
20-
"web-token/jwt-easy": "^2.1",
21-
"web-token/jwt-signature-algorithm-hmac": "^2.1",
22-
"web-token/jwt-signature-algorithm-rsa": "^2.1",
23-
"web-token/jwt-signature-algorithm-ecdsa": "^2.1"
19+
"psr/log": "^3.0",
20+
"web-token/jwt-checker": "^3.0",
21+
"web-token/jwt-signature-algorithm-hmac": "^3.0",
22+
"web-token/jwt-signature-algorithm-rsa": "^3.0",
23+
"web-token/jwt-signature-algorithm-ecdsa": "^3.0"
2424
},
2525
"require-dev": {
2626
"ext-memcache": "*",
@@ -29,7 +29,7 @@
2929
"m6web/redis-mock": "^5.0",
3030
"mockery/mockery": "^1.0",
3131
"phpunit/phpunit": "^10.0",
32-
"predis/predis": "^1.0"
32+
"predis/predis": "^2.0"
3333
},
3434
"suggest": {
3535
"ext-memcache": "Required to use the memcache cache driver.",

src/Checker/Claim/ScpChecker.php

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
/*
4+
* This file is part of SeAT
5+
*
6+
* Copyright (C) 2015 to 2022 Leon Jacobs
7+
*
8+
* This program is free software; you can redistribute it and/or modify
9+
* it under the terms of the GNU General Public License as published by
10+
* the Free Software Foundation; either version 2 of the License, or
11+
* (at your option) any later version.
12+
*
13+
* This program is distributed in the hope that it will be useful,
14+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+
* GNU General Public License for more details.
17+
*
18+
* You should have received a copy of the GNU General Public License along
19+
* with this program; if not, write to the Free Software Foundation, Inc.,
20+
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21+
*/
22+
23+
namespace Seat\Eseye\Checker\Claim;
24+
25+
use Jose\Component\Checker\ClaimChecker;
26+
use Jose\Component\Checker\InvalidClaimException;
27+
28+
class ScpChecker implements ClaimChecker
29+
{
30+
private const NAME = 'scp';
31+
32+
/**
33+
* When the token has the applicable claim, the value is checked. If for some reason the value is not valid, an
34+
* InvalidClaimException must be thrown.
35+
*/
36+
public function checkClaim(mixed $value): void
37+
{
38+
if (! is_array($value))
39+
throw new InvalidClaimException(
40+
sprintf('"%s" must be of type array', self::NAME),
41+
self::NAME,
42+
$value);
43+
}
44+
45+
/**
46+
* The method returns the claim to be checked.
47+
*/
48+
public function supportedClaim(): string
49+
{
50+
return self::NAME;
51+
}
52+
}

src/Checker/EsiTokenValidator.php

+203
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
<?php
2+
3+
/*
4+
* This file is part of SeAT
5+
*
6+
* Copyright (C) 2015 to 2022 Leon Jacobs
7+
*
8+
* This program is free software; you can redistribute it and/or modify
9+
* it under the terms of the GNU General Public License as published by
10+
* the Free Software Foundation; either version 2 of the License, or
11+
* (at your option) any later version.
12+
*
13+
* This program is distributed in the hope that it will be useful,
14+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+
* GNU General Public License for more details.
17+
*
18+
* You should have received a copy of the GNU General Public License along
19+
* with this program; if not, write to the Free Software Foundation, Inc.,
20+
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21+
*/
22+
23+
namespace Seat\Eseye\Checker;
24+
25+
use Jose\Component\Checker\AlgorithmChecker;
26+
use Jose\Component\Checker\AudienceChecker;
27+
use Jose\Component\Checker\ClaimCheckerManager;
28+
use Jose\Component\Checker\ExpirationTimeChecker;
29+
use Jose\Component\Checker\HeaderCheckerManager;
30+
use Jose\Component\Checker\IssuerChecker;
31+
use Jose\Component\Core\AlgorithmManager;
32+
use Jose\Component\Core\JWKSet;
33+
use Jose\Component\Signature\Algorithm\RS256;
34+
use Jose\Component\Signature\JWSLoader;
35+
use Jose\Component\Signature\JWSTokenSupport;
36+
use Jose\Component\Signature\JWSVerifier;
37+
use Jose\Component\Signature\Serializer\CompactSerializer;
38+
use Jose\Component\Signature\Serializer\JWSSerializerManager;
39+
use Psr\Http\Client\ClientInterface;
40+
use Psr\Http\Message\RequestFactoryInterface;
41+
use Seat\Eseye\Checker\Claim\AzpChecker;
42+
use Seat\Eseye\Checker\Claim\NameChecker;
43+
use Seat\Eseye\Checker\Claim\OwnerChecker;
44+
use Seat\Eseye\Checker\Claim\ScpChecker;
45+
use Seat\Eseye\Checker\Claim\SubEveCharacterChecker;
46+
use Seat\Eseye\Configuration;
47+
use Seat\Eseye\Exceptions\DiscoverServiceNotAvailableException;
48+
use Seat\Eseye\Exceptions\InvalidAuthenticationException;
49+
50+
class EsiTokenValidator
51+
{
52+
private ClientInterface $client;
53+
private JWSLoader $loader;
54+
private RequestFactoryInterface $request_factory;
55+
56+
public function __construct()
57+
{
58+
$header_checker_manager = $this->getJWTHeadersPolicy();
59+
$serialize_manager = $this->getSerializerManager();
60+
$jws_verifier = $this->getJWTVerifier();
61+
62+
// Init the HTTP client
63+
$this->client = Configuration::getInstance()->getHttpClient();
64+
$this->request_factory = Configuration::getInstance()->getHttpRequestFactory();
65+
66+
$this->loader = new JWSLoader($serialize_manager, $jws_verifier, $header_checker_manager);
67+
}
68+
69+
/**
70+
* Validate provided access token and return its claims.
71+
*
72+
* @param string $access_token
73+
* @return array
74+
*
75+
* @throws \Psr\Http\Client\ClientExceptionInterface
76+
* @throws \Seat\Eseye\Exceptions\DiscoverServiceNotAvailableException
77+
* @throws \Seat\Eseye\Exceptions\InvalidAuthenticationException
78+
* @throws \Seat\Eseye\Exceptions\InvalidContainerDataException
79+
*/
80+
public function validateToken(string $client_id, string $access_token): array
81+
{
82+
$sets = $this->getJwkSets();
83+
84+
$jwk_sets = JWKSet::createFromKeyData($sets);
85+
86+
$claims_checker_manager = $this->getJWTClaimsPolicy($client_id);
87+
88+
// convert raw access token into a JWS Token object
89+
$jws = $this->loader->getSerializerManager()->unserialize($access_token);
90+
91+
// apply token headers policy
92+
$this->loader->getHeaderCheckerManager()->check($jws, 0, ['alg']);
93+
94+
// validate token signature
95+
if (! $this->loader->getJwsVerifier()->verifyWithKeySet($jws, $jwk_sets, 0))
96+
throw new InvalidAuthenticationException('Unable to verify access token.');
97+
98+
// apply claims policy
99+
$claims = json_decode($jws->getPayload(), true);
100+
101+
return $claims_checker_manager->check($claims, ['iss', 'exp', 'aud']);
102+
}
103+
104+
/**
105+
* @return \Jose\Component\Signature\Serializer\JWSSerializerManager
106+
*/
107+
private function getSerializerManager(): JWSSerializerManager
108+
{
109+
return new JWSSerializerManager([
110+
new CompactSerializer(),
111+
]);
112+
}
113+
114+
/**
115+
* @return \Jose\Component\Signature\JWSVerifier
116+
*/
117+
private function getJWTVerifier(): JWSVerifier
118+
{
119+
$algorithms_manager = new AlgorithmManager([
120+
new RS256(),
121+
]);
122+
123+
return new JWSVerifier($algorithms_manager);
124+
}
125+
126+
/**
127+
* @return \Jose\Component\Checker\HeaderCheckerManager
128+
*/
129+
private function getJWTHeadersPolicy(): HeaderCheckerManager
130+
{
131+
return new HeaderCheckerManager(
132+
[
133+
new AlgorithmChecker(['RS256']),
134+
],
135+
[
136+
new JWSTokenSupport(),
137+
]
138+
);
139+
}
140+
141+
/**
142+
* @param string $client_id
143+
* @return \Jose\Component\Checker\ClaimCheckerManager
144+
*
145+
* @throws \Seat\Eseye\Exceptions\InvalidContainerDataException
146+
*/
147+
private function getJWTClaimsPolicy(string $client_id): ClaimCheckerManager
148+
{
149+
return new ClaimCheckerManager([
150+
new IssuerChecker([
151+
Configuration::getInstance()->sso_host,
152+
]),
153+
new ExpirationTimeChecker(),
154+
new AudienceChecker('EVE Online'),
155+
new SubEveCharacterChecker(),
156+
new ScpChecker(),
157+
new AzpChecker($client_id),
158+
new NameChecker(),
159+
new OwnerChecker(),
160+
]);
161+
}
162+
163+
/**
164+
* @return array
165+
*
166+
* @throws \Psr\Http\Client\ClientExceptionInterface
167+
* @throws \Seat\Eseye\Exceptions\InvalidContainerDataException
168+
* @throws \Seat\Eseye\Exceptions\DiscoverServiceNotAvailableException
169+
*/
170+
private function getJwkSets(): array
171+
{
172+
$metadata = $this->getAuthServerMetadata();
173+
$jwk_set_uri = $metadata->jwks_uri;
174+
175+
$request = $this->request_factory->createRequest('GET', $jwk_set_uri);
176+
$response = $this->client->sendRequest($request);
177+
178+
return json_decode($response->getBody(), true);
179+
}
180+
181+
/**
182+
* @return object
183+
*
184+
* @throws \Psr\Http\Client\ClientExceptionInterface
185+
* @throws \Seat\Eseye\Exceptions\InvalidContainerDataException
186+
* @throws \Seat\Eseye\Exceptions\DiscoverServiceNotAvailableException
187+
*/
188+
private function getAuthServerMetadata(): object
189+
{
190+
$oauth_discovery = sprintf('%s://%s:%d/.well-known/oauth-authorization-server',
191+
Configuration::getInstance()->sso_scheme,
192+
Configuration::getInstance()->sso_host,
193+
Configuration::getInstance()->sso_port);
194+
195+
$request = $this->request_factory->createRequest('GET', $oauth_discovery);
196+
$response = $this->client->sendRequest($request);
197+
198+
if ($response->getStatusCode() >= 400)
199+
throw new DiscoverServiceNotAvailableException($response->getBody()->getContents());
200+
201+
return json_decode($response->getBody());
202+
}
203+
}

0 commit comments

Comments
 (0)