Skip to content

Commit 26129e6

Browse files
committed
Use Google SDK open id verification library
1 parent 890ca99 commit 26129e6

9 files changed

+60
-390
lines changed

composer.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
"require": {
1111
"ext-json": "*",
1212
"google/cloud-scheduler": "^1.5",
13-
"firebase/php-jwt": "^5.5",
1413
"phpseclib/phpseclib": "~2.0"
1514
},
1615
"require-dev": {

src/CloudSchedulerServiceProvider.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ class CloudSchedulerServiceProvider extends LaravelServiceProvider
1010
public function boot(Router $router)
1111
{
1212
$this->registerRoutes($router);
13+
$this->registerClient();
1314
}
1415

1516
public function register()
@@ -21,4 +22,9 @@ private function registerRoutes(Router $router)
2122
{
2223
$router->post('cloud-scheduler-job', [TaskHandler::class, 'handle']);
2324
}
25+
26+
private function registerClient()
27+
{
28+
$this->app->bind('open-id-verificator', OpenIdVerificatorConcrete::class);
29+
}
2430
}

src/OpenIdVerificator.php

Lines changed: 6 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -2,148 +2,17 @@
22

33
namespace Stackkit\LaravelGoogleCloudScheduler;
44

5-
use Carbon\Carbon;
6-
use Firebase\JWT\JWT;
7-
use Firebase\JWT\SignatureInvalidException;
8-
use GuzzleHttp\Client;
9-
use GuzzleHttp\Exception\ServerException;
10-
use Illuminate\Support\Arr;
11-
use Illuminate\Support\Facades\Cache;
12-
use phpseclib\Crypt\RSA;
13-
use phpseclib\Math\BigInteger;
14-
use Throwable;
5+
use Illuminate\Support\Facades\Facade;
156

16-
class OpenIdVerificator
7+
class OpenIdVerificator extends Facade
178
{
18-
private const V3_CERTS = 'GOOGLE_V3_CERTS';
19-
private const URL_OPENID_CONFIG = 'https://accounts.google.com/.well-known/openid-configuration';
20-
private const URL_TOKEN_INFO = 'https://www.googleapis.com/oauth2/v3/tokeninfo';
21-
22-
private $guzzle;
23-
private $rsa;
24-
private $jwt;
25-
private $maxAge = [];
26-
27-
public function __construct(Client $guzzle, RSA $rsa, JWT $jwt)
28-
{
29-
$this->guzzle = $guzzle;
30-
$this->rsa = $rsa;
31-
$this->jwt = $jwt;
32-
}
33-
34-
public function guardAgainstInvalidOpenIdToken($decodedToken)
35-
{
36-
/**
37-
* https://developers.google.com/identity/protocols/oauth2/openid-connect#validatinganidtoken
38-
*/
39-
if (!in_array($decodedToken->iss, ['https://accounts.google.com', 'accounts.google.com'])) {
40-
throw new CloudSchedulerException('The given OpenID token is not valid');
41-
}
42-
43-
if ($decodedToken->exp < time()) {
44-
throw new CloudSchedulerException('The given OpenID token has expired');
45-
}
46-
47-
if ($decodedToken->aud !== config('laravel-google-cloud-scheduler.app_url')) {
48-
throw new CloudSchedulerException('The given OpenID token is not valid');
49-
}
50-
}
51-
52-
public function decodeOpenIdToken($openIdToken, $kid, $cache = true)
53-
{
54-
if (!$cache) {
55-
$this->forgetFromCache();
56-
}
57-
58-
$publicKey = $this->getPublicKey($kid);
59-
60-
try {
61-
return $this->jwt->decode($openIdToken, $publicKey, ['RS256']);
62-
} catch (SignatureInvalidException $e) {
63-
if (!$cache) {
64-
throw $e;
65-
}
66-
67-
return $this->decodeOpenIdToken($openIdToken, $kid, false);
68-
}
69-
}
70-
71-
public function getPublicKey($kid = null)
72-
{
73-
if (Cache::has(self::V3_CERTS)) {
74-
$v3Certs = Cache::get(self::V3_CERTS);
75-
} else {
76-
$v3Certs = $this->getFreshCertificates();
77-
Cache::put(self::V3_CERTS, $v3Certs, Carbon::now()->addSeconds($this->maxAge[self::URL_OPENID_CONFIG]));
78-
}
79-
80-
$cert = $kid ? collect($v3Certs)->firstWhere('kid', '=', $kid) : $v3Certs[0];
81-
82-
return $this->extractPublicKeyFromCertificate($cert);
83-
}
84-
85-
private function getFreshCertificates()
86-
{
87-
$jwksUri = $this->callApiAndReturnValue(self::URL_OPENID_CONFIG, 'jwks_uri');
88-
89-
return $this->callApiAndReturnValue($jwksUri, 'keys');
90-
}
91-
92-
private function extractPublicKeyFromCertificate($certificate)
93-
{
94-
$modulus = new BigInteger(JWT::urlsafeB64Decode($certificate['n']), 256);
95-
$exponent = new BigInteger(JWT::urlsafeB64Decode($certificate['e']), 256);
96-
97-
$this->rsa->loadKey(compact('modulus', 'exponent'));
98-
99-
return $this->rsa->getPublicKey();
100-
}
101-
102-
public function getKidFromOpenIdToken($openIdToken)
103-
{
104-
return $this->callApiAndReturnValue(self::URL_TOKEN_INFO . '?id_token=' . $openIdToken, 'kid');
105-
}
106-
107-
private function callApiAndReturnValue($url, $value)
108-
{
109-
$attempts = 0;
110-
111-
while (true) {
112-
try {
113-
$response = $this->guzzle->get($url);
114-
115-
break;
116-
} catch (ServerException $e) {
117-
$attempts++;
118-
119-
if ($attempts >= 3) {
120-
throw $e;
121-
}
122-
123-
sleep(1);
124-
}
125-
}
126-
127-
$data = json_decode($response->getBody(), true);
128-
129-
$maxAge = 0;
130-
foreach ($response->getHeader('Cache-Control') as $line) {
131-
preg_match('/max-age=(\d+)/', $line, $matches);
132-
$maxAge = isset($matches[1]) ? (int) $matches[1] : 0;
133-
}
134-
135-
$this->maxAge[$url] = $maxAge;
136-
137-
return Arr::get($data, $value);
138-
}
139-
140-
public function isCached()
9+
protected static function getFacadeAccessor()
14110
{
142-
return Cache::has(self::V3_CERTS);
11+
return 'open-id-verificator';
14312
}
14413

145-
public function forgetFromCache()
14+
public static function fake(): void
14615
{
147-
Cache::forget(self::V3_CERTS);
16+
self::swap(new OpenIdVerificatorFake());
14817
}
14918
}

src/OpenIdVerificatorConcrete.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
namespace Stackkit\LaravelGoogleCloudScheduler;
4+
5+
use Google\Auth\AccessToken;
6+
use Illuminate\Support\Facades\Facade;
7+
8+
class OpenIdVerificatorConcrete extends Facade
9+
{
10+
public function verify(?string $token, array $config): void
11+
{
12+
if (!$token) {
13+
throw new CloudSchedulerException('Missing [Authorization] header');
14+
}
15+
16+
(new AccessToken())->verify(
17+
$token,
18+
[
19+
'audience' => config('laravel-google-cloud-scheduler.app_url'),
20+
'throwException' => true,
21+
]
22+
);
23+
}
24+
}

src/OpenIdVerificatorFake.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
namespace Stackkit\LaravelGoogleCloudScheduler;
4+
5+
class OpenIdVerificatorFake
6+
{
7+
public function verify(?string $token, array $config): void
8+
{
9+
//
10+
}
11+
}

src/TaskHandler.php

Lines changed: 1 addition & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,31 +4,20 @@
44

55
use Illuminate\Console\Scheduling\Schedule;
66
use Illuminate\Container\Container;
7-
use Illuminate\Contracts\Console\Kernel;
8-
use Illuminate\Http\Request;
97
use Illuminate\Support\Facades\Artisan;
108

119
class TaskHandler
1210
{
1311
private $command;
14-
private $request;
15-
private $openId;
16-
private $kernel;
1712
private $schedule;
1813
private $container;
1914

2015
public function __construct(
2116
Command $command,
22-
Request $request,
23-
OpenIdVerificator $openId,
24-
Kernel $kernel,
2517
Schedule $schedule,
2618
Container $container
2719
) {
2820
$this->command = $command;
29-
$this->request = $request;
30-
$this->openId = $openId;
31-
$this->kernel = $kernel;
3221
$this->schedule = $schedule;
3322
$this->container = $container;
3423
}
@@ -38,7 +27,7 @@ public function __construct(
3827
*/
3928
public function handle()
4029
{
41-
$this->authorizeRequest();
30+
OpenIdVerificator::verify(request()->bearerToken(), []);
4231

4332
set_time_limit(0);
4433

@@ -47,24 +36,6 @@ public function handle()
4736
return $this->cleanOutput($output);
4837
}
4938

50-
/**
51-
* @throws CloudSchedulerException
52-
*/
53-
private function authorizeRequest()
54-
{
55-
if (!$this->request->hasHeader('Authorization')) {
56-
throw new CloudSchedulerException('Unauthorized');
57-
}
58-
59-
$openIdToken = $this->request->bearerToken();
60-
61-
$kid = $this->openId->getKidFromOpenIdToken($openIdToken);
62-
63-
$decodedToken = $this->openId->decodeOpenIdToken($openIdToken, $kid);
64-
65-
$this->openId->guardAgainstInvalidOpenIdToken($decodedToken);
66-
}
67-
6839
private function runCommand($command)
6940
{
7041
if ($this->isScheduledCommand($command)) {

tests/GooglePublicKeyTest.php

Lines changed: 0 additions & 89 deletions
This file was deleted.

0 commit comments

Comments
 (0)