Skip to content

Commit 2def466

Browse files
committed
Verify OpenID aud claim and added tests
1 parent 26da272 commit 2def466

File tree

6 files changed

+95
-14
lines changed

6 files changed

+95
-14
lines changed

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,19 @@ Please check the table below for supported Laravel and PHP versions:
4141
composer require stackkit/laravel-google-cloud-scheduler
4242
```
4343

44+
(2) Define the `STACKKIT_CLOUD_SCHEDULER_APP_URL` environment variable. This should be the URL defined in the `URL` field of your Cloud Scheduler job.
45+
46+
```
47+
STACKKIT_CLOUD_SCHEDULER_APP_URL=https://yourdomainname.com/cloud-scheduler-job
48+
```
49+
4450
# Cloud Scheduler Example
4551

4652
Here is an example job that will run `php artisan inspire` every minute.
4753

4854
These are the most important settings:
4955
- Target must be `HTTP`
50-
- URL must be `yourdomainname.com/cloud-scheduler-job`
56+
- URL must be `https://yourdomainname.com/cloud-scheduler-job`
5157
- Auth header must be OIDC token!
5258

5359
<img src="/example.png">
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?php
2+
3+
return [
4+
'app_url' => env('STACKKIT_CLOUD_SCHEDULER_APP_URL'),
5+
];

src/CloudSchedulerServiceProvider.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ public function boot(Router $router)
1212
$this->registerRoutes($router);
1313
}
1414

15+
public function register()
16+
{
17+
$this->mergeConfigFrom(__DIR__ . '/../config/laravel-google-cloud-scheduler.php', 'laravel-google-cloud-scheduler');
18+
}
19+
1520
private function registerRoutes(Router $router)
1621
{
1722
$router->post('cloud-scheduler-job', [TaskHandler::class, 'handle']);

src/OpenIdVerificator.php

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use Illuminate\Support\Facades\Cache;
99
use phpseclib\Crypt\RSA;
1010
use phpseclib\Math\BigInteger;
11+
use Throwable;
1112

1213
class OpenIdVerificator
1314
{
@@ -26,13 +27,8 @@ public function __construct(Client $guzzle, RSA $rsa, JWT $jwt)
2627
$this->jwt = $jwt;
2728
}
2829

29-
public function guardAgainstInvalidOpenIdToken($token)
30+
public function guardAgainstInvalidOpenIdToken($decodedToken)
3031
{
31-
$kid = $this->getKidFromOpenIdToken($token);
32-
$publicKey = $this->getPublicKey($kid);
33-
34-
$decodedToken = $this->jwt->decode($token, $publicKey, ['RS256']);
35-
3632
/**
3733
* https://developers.google.com/identity/protocols/oauth2/openid-connect#validatinganidtoken
3834
*/
@@ -44,6 +40,22 @@ public function guardAgainstInvalidOpenIdToken($token)
4440
if ($decodedToken->exp < time()) {
4541
throw new CloudSchedulerException('The given OpenID token has expired');
4642
}
43+
44+
if ($decodedToken->aud !== config('laravel-google-cloud-scheduler.app_url')) {
45+
throw new CloudSchedulerException('The given OpenID token is not valid');
46+
}
47+
}
48+
49+
public function decodeToken($token)
50+
{
51+
try {
52+
$kid = $this->getKidFromOpenIdToken($token);
53+
$publicKey = $this->getPublicKey($kid);
54+
55+
return $this->jwt->decode($token, $publicKey, ['RS256']);
56+
} catch (Throwable $e) {
57+
throw new CloudSchedulerException('Could not decode token');
58+
}
4759
}
4860

4961
public function getPublicKey($kid = null)

src/TaskHandler.php

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
use Illuminate\Http\Request;
66
use Illuminate\Support\Facades\Artisan;
7-
use Throwable;
87

98
class TaskHandler
109
{
@@ -46,11 +45,9 @@ private function authorizeRequest()
4645

4746
$openIdToken = $this->request->bearerToken();
4847

49-
try {
50-
$this->openId->guardAgainstInvalidOpenIdToken($openIdToken);
51-
} catch (Throwable $e) {
52-
throw new CloudSchedulerException('Unauthorized');
53-
}
48+
$decodedToken = $this->openId->decodeToken($openIdToken);
49+
50+
$this->openId->guardAgainstInvalidOpenIdToken($decodedToken);
5451
}
5552

5653
private function cleanOutput($output)

tests/TaskHandlerTest.php

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use Stackkit\LaravelGoogleCloudScheduler\Command;
1111
use Stackkit\LaravelGoogleCloudScheduler\OpenIdVerificator;
1212
use Stackkit\LaravelGoogleCloudScheduler\TaskHandler;
13+
use Throwable;
1314

1415
class TaskHandlerTest extends TestCase
1516
{
@@ -43,6 +44,7 @@ public function it_executes_the_incoming_command()
4344
{
4445
$this->fakeCommand->shouldReceive('capture')->andReturn('env');
4546
$this->openId->shouldReceive('guardAgainstInvalidOpenIdToken')->andReturnNull();
47+
$this->openId->shouldReceive('decodeToken')->andReturnNull();
4648

4749
$output = $this->taskHandler->handle();
4850

@@ -72,7 +74,61 @@ public function it_requires_a_jwt_signed_by_google()
7274
$this->request->headers->add(['Authorization' => 'Bearer ' . $dummyJwt]);
7375

7476
$this->expectException(CloudSchedulerException::class);
75-
$this->expectExceptionMessage('Unauthorized');
77+
$this->expectExceptionMessage('Could not decode token');
78+
79+
$this->taskHandler->handle();
80+
}
81+
82+
/** @test */
83+
public function the_issue_identifier_should_be_google()
84+
{
85+
$this->expectExceptionMessage('The given OpenID token is not valid');
86+
87+
$this->openId->shouldReceive('decodeToken')->andReturn((object) [
88+
'iss' => 'accounts.not-google.com',
89+
]);
90+
91+
$this->taskHandler->handle();
92+
}
93+
94+
/** @test */
95+
public function the_token_must_not_be_expired()
96+
{
97+
$this->expectExceptionMessage('The given OpenID token has expired');
98+
99+
$this->openId->shouldReceive('decodeToken')->andReturn((object) [
100+
'iss' => 'accounts.google.com',
101+
'exp' => time() - 10,
102+
]);
103+
104+
$this->taskHandler->handle();
105+
}
106+
107+
/** @test */
108+
public function the_aud_claim_must_be_the_same_as_the_app_id()
109+
{
110+
config()->set('laravel-google-cloud-scheduler.app_url', 'my-application.com');
111+
$this->fakeCommand->shouldReceive('capture')->andReturn('env');
112+
$this->openId->shouldReceive('decodeToken')->andReturn((object) [
113+
'iss' => 'accounts.google.com',
114+
'exp' => time() + 10,
115+
'aud' => 'my-application.com',
116+
])->byDefault();
117+
118+
try {
119+
$this->taskHandler->handle();
120+
} catch (Throwable $e) {
121+
$this->fail('The command should not have thrown an exception');
122+
}
123+
124+
$this->openId->shouldReceive('decodeToken')->andReturn((object) [
125+
'iss' => 'accounts.google.com',
126+
'exp' => time() + 10,
127+
'aud' => 'my-other-application.com',
128+
]);
129+
130+
$this->expectException(CloudSchedulerException::class);
131+
$this->expectExceptionMessage('The given OpenID token is not valid');
76132

77133
$this->taskHandler->handle();
78134
}

0 commit comments

Comments
 (0)