Skip to content

Commit

Permalink
Merge pull request #15 from stackkit/feature/use-google-own-openid-ve…
Browse files Browse the repository at this point in the history
…rification

Use Google SDK open id verification library
  • Loading branch information
marickvantuil authored Apr 23, 2022
2 parents 890ca99 + 72b2791 commit 578ffa1
Show file tree
Hide file tree
Showing 12 changed files with 89 additions and 452 deletions.
65 changes: 15 additions & 50 deletions .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,57 +10,22 @@ on:

jobs:
php-tests:
runs-on: ${{ matrix.os }}
runs-on: ubuntu-latest

strategy:
matrix:
php: [8.1, 8.0, 7.4, 7.3]
laravel: [9.*, 8.*, 7.*, 6.*, 5.8.*, 5.7.*, 5.6.*]
os: [ubuntu-latest]
include:
- laravel: 9.*
testbench: 7.*
- laravel: 8.*
testbench: 6.*
- laravel: 7.*
testbench: 5.*
- laravel: 6.*
testbench: 4.*
- laravel: 5.8.*
testbench: 3.8.*
- laravel: 5.7.*
testbench: 3.7.*
- laravel: 5.6.*
testbench: 3.6.*
exclude:
- laravel: 9.*
php: 7.3
- laravel: 9.*
php: 7.4
- laravel: 5.7.*
php: 7.4
- laravel: 5.6.*
php: 7.4
- laravel: 5.5.*
php: 7.4
- laravel: 5.8.*
php: 8.0
- laravel: 5.7.*
php: 8.0
- laravel: 5.6.*
php: 8.0
- laravel: 5.6.*
php: 8.1
- laravel: 5.7.*
php: 8.1
- laravel: 5.8.*
php: 8.1
- laravel: 6.*
php: 8.1
- laravel: 7.*
php: 8.1
payload:
- { queue: 'github-actions-laravel9-php81', laravel: '9.*', php: '8.1', 'testbench': '7.*'}
- { queue: 'github-actions-laravel9-php80', laravel: '9.*', php: '8.0', 'testbench': '7.*'}
- { queue: 'github-actions-laravel8-php81', laravel: '8.*', php: '8.1', 'testbench': '6.*'}
- { queue: 'github-actions-laravel8-php80', laravel: '8.*', php: '8.0', 'testbench': '6.*'}
- { queue: 'github-actions-laravel8-php74', laravel: '8.*', php: '7.4', 'testbench': '6.*'}
- { queue: 'github-actions-laravel7-php80', laravel: '7.*', php: '8.0', 'testbench': '5.*' }
- { queue: 'github-actions-laravel7-php74', laravel: '7.*', php: '7.4', 'testbench': '5.*' }
- { queue: 'github-actions-laravel6-php80', laravel: '6.*', php: '8.0', 'testbench': '4.*' }
- { queue: 'github-actions-laravel6-php74', laravel: '6.*', php: '7.4', 'testbench': '4.*' }

name: PHP ${{ matrix.php }} - Laravel ${{ matrix.laravel }}
name: PHP ${{ matrix.payload.php }} - Laravel ${{ matrix.payload.laravel }}

steps:
- name: Checkout code
Expand All @@ -69,13 +34,13 @@ jobs:
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
php-version: ${{ matrix.payload.php }}
extensions: mbstring, dom, fileinfo
coverage: none

- name: Install dependencies
run: |
composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-interaction --no-update
composer update --prefer-stable --prefer-dist --no-interaction --ignore-platform-reqs
composer require "laravel/framework:${{ matrix.payload.laravel }}" "orchestra/testbench:${{ matrix.payload.testbench }}" --no-interaction --no-update
composer update --prefer-stable --prefer-dist --no-interaction
- name: Execute tests
run: vendor/bin/phpunit
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

# 2.0.0 - 2022-04-23

**Changed**

- Dropped older PHP and Laravel support
- Bumped dependencies

## 1.1.0 - 2022-02-09

**Changed**
Expand Down
11 changes: 4 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,15 @@ All these features are supported. This package scans your console kernel (`app/C

# Requirements

This package requires Laravel 5.6 or higher.
This package requires Laravel 6 or higher.

Please check the table below for supported Laravel and PHP versions:

|Laravel Version| PHP Version |
|---|---|
| 5.6 | 7.3
| 5.7 | 7.3
| 5.8 | 7.3 or 7.4
| 6.x | 7.3 or 7.4 or 8.0
| 7.x | 7.3 or 7.4 or 8.0
| 8.x | 7.3 or 7.4 or 8.0
| 6.x | 7.4 or 8.0
| 7.x | 7.4 or 8.0
| 8.x | 7.4 or 8.0
| 9.x | 8.0 or 8.1

# Installation
Expand Down
9 changes: 3 additions & 6 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,12 @@
],
"require": {
"ext-json": "*",
"google/cloud-scheduler": "^1.5",
"firebase/php-jwt": "^5.5",
"google/cloud-scheduler": "^1.6",
"phpseclib/phpseclib": "~2.0"
},
"require-dev": {
"mockery/mockery": "^1.3",
"orchestra/testbench": "^3.5 || ^3.6 || ^3.7 || ^3.8 || ^4.0 || ^5.0",
"psr/log": "^1.1",
"spatie/macroable": "^1.0"
"mockery/mockery": "^1.5",
"orchestra/testbench": "^4.0 || ^5.0 || ^6.0 || ^7.0"
},
"autoload": {
"psr-4": {
Expand Down
6 changes: 6 additions & 0 deletions src/CloudSchedulerServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class CloudSchedulerServiceProvider extends LaravelServiceProvider
public function boot(Router $router)
{
$this->registerRoutes($router);
$this->registerClient();
}

public function register()
Expand All @@ -21,4 +22,9 @@ private function registerRoutes(Router $router)
{
$router->post('cloud-scheduler-job', [TaskHandler::class, 'handle']);
}

private function registerClient()
{
$this->app->bind('open-id-verificator', OpenIdVerificatorConcrete::class);
}
}
143 changes: 6 additions & 137 deletions src/OpenIdVerificator.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,148 +2,17 @@

namespace Stackkit\LaravelGoogleCloudScheduler;

use Carbon\Carbon;
use Firebase\JWT\JWT;
use Firebase\JWT\SignatureInvalidException;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ServerException;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Cache;
use phpseclib\Crypt\RSA;
use phpseclib\Math\BigInteger;
use Throwable;
use Illuminate\Support\Facades\Facade;

class OpenIdVerificator
class OpenIdVerificator extends Facade
{
private const V3_CERTS = 'GOOGLE_V3_CERTS';
private const URL_OPENID_CONFIG = 'https://accounts.google.com/.well-known/openid-configuration';
private const URL_TOKEN_INFO = 'https://www.googleapis.com/oauth2/v3/tokeninfo';

private $guzzle;
private $rsa;
private $jwt;
private $maxAge = [];

public function __construct(Client $guzzle, RSA $rsa, JWT $jwt)
{
$this->guzzle = $guzzle;
$this->rsa = $rsa;
$this->jwt = $jwt;
}

public function guardAgainstInvalidOpenIdToken($decodedToken)
{
/**
* https://developers.google.com/identity/protocols/oauth2/openid-connect#validatinganidtoken
*/
if (!in_array($decodedToken->iss, ['https://accounts.google.com', 'accounts.google.com'])) {
throw new CloudSchedulerException('The given OpenID token is not valid');
}

if ($decodedToken->exp < time()) {
throw new CloudSchedulerException('The given OpenID token has expired');
}

if ($decodedToken->aud !== config('laravel-google-cloud-scheduler.app_url')) {
throw new CloudSchedulerException('The given OpenID token is not valid');
}
}

public function decodeOpenIdToken($openIdToken, $kid, $cache = true)
{
if (!$cache) {
$this->forgetFromCache();
}

$publicKey = $this->getPublicKey($kid);

try {
return $this->jwt->decode($openIdToken, $publicKey, ['RS256']);
} catch (SignatureInvalidException $e) {
if (!$cache) {
throw $e;
}

return $this->decodeOpenIdToken($openIdToken, $kid, false);
}
}

public function getPublicKey($kid = null)
{
if (Cache::has(self::V3_CERTS)) {
$v3Certs = Cache::get(self::V3_CERTS);
} else {
$v3Certs = $this->getFreshCertificates();
Cache::put(self::V3_CERTS, $v3Certs, Carbon::now()->addSeconds($this->maxAge[self::URL_OPENID_CONFIG]));
}

$cert = $kid ? collect($v3Certs)->firstWhere('kid', '=', $kid) : $v3Certs[0];

return $this->extractPublicKeyFromCertificate($cert);
}

private function getFreshCertificates()
{
$jwksUri = $this->callApiAndReturnValue(self::URL_OPENID_CONFIG, 'jwks_uri');

return $this->callApiAndReturnValue($jwksUri, 'keys');
}

private function extractPublicKeyFromCertificate($certificate)
{
$modulus = new BigInteger(JWT::urlsafeB64Decode($certificate['n']), 256);
$exponent = new BigInteger(JWT::urlsafeB64Decode($certificate['e']), 256);

$this->rsa->loadKey(compact('modulus', 'exponent'));

return $this->rsa->getPublicKey();
}

public function getKidFromOpenIdToken($openIdToken)
{
return $this->callApiAndReturnValue(self::URL_TOKEN_INFO . '?id_token=' . $openIdToken, 'kid');
}

private function callApiAndReturnValue($url, $value)
{
$attempts = 0;

while (true) {
try {
$response = $this->guzzle->get($url);

break;
} catch (ServerException $e) {
$attempts++;

if ($attempts >= 3) {
throw $e;
}

sleep(1);
}
}

$data = json_decode($response->getBody(), true);

$maxAge = 0;
foreach ($response->getHeader('Cache-Control') as $line) {
preg_match('/max-age=(\d+)/', $line, $matches);
$maxAge = isset($matches[1]) ? (int) $matches[1] : 0;
}

$this->maxAge[$url] = $maxAge;

return Arr::get($data, $value);
}

public function isCached()
protected static function getFacadeAccessor()
{
return Cache::has(self::V3_CERTS);
return 'open-id-verificator';
}

public function forgetFromCache()
public static function fake(): void
{
Cache::forget(self::V3_CERTS);
self::swap(new OpenIdVerificatorFake());
}
}
24 changes: 24 additions & 0 deletions src/OpenIdVerificatorConcrete.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

namespace Stackkit\LaravelGoogleCloudScheduler;

use Google\Auth\AccessToken;
use Illuminate\Support\Facades\Facade;

class OpenIdVerificatorConcrete extends Facade
{
public function verify(?string $token, array $config): void
{
if (!$token) {
throw new CloudSchedulerException('Missing [Authorization] header');
}

(new AccessToken())->verify(
$token,
[
'audience' => config('laravel-google-cloud-scheduler.app_url'),
'throwException' => true,
]
);
}
}
11 changes: 11 additions & 0 deletions src/OpenIdVerificatorFake.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace Stackkit\LaravelGoogleCloudScheduler;

class OpenIdVerificatorFake
{
public function verify(?string $token, array $config): void
{
//
}
}
Loading

0 comments on commit 578ffa1

Please sign in to comment.