Skip to content

Commit

Permalink
feat: Allow insecure fields in optional
Browse files Browse the repository at this point in the history
  • Loading branch information
siketyan committed Jun 1, 2023
1 parent 12c4eb5 commit fb31574
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 26 deletions.
49 changes: 26 additions & 23 deletions src/Claims.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,41 +10,21 @@
class Claims
{
/**
* @var array{
* @param array{
* exp: positive-int,
* iat: positive-int,
* aud: non-empty-string,
* iss: non-empty-string,
* hd: non-empty-string,
* sub: non-empty-string,
* email: non-empty-string,
* }
* } $claims
*
* @see https://cloud.google.com/iap/docs/signed-headers-howto#verifying_the_jwt_payload
*/
public readonly array $claims;

/**
* @param array<array-key, mixed> $claims
*
* @throws MalformedClaimsException
*/
public function __construct(
array $claims,
public readonly array $claims,
) {
try {
$this->claims = [
'exp' => Assert::positiveInt(Assert::in('exp', $claims)),
'iat' => Assert::positiveInt(Assert::in('iat', $claims)),
'aud' => Assert::nonEmptyString(Assert::in('aud', $claims)),
'iss' => Assert::nonEmptyString(Assert::in('iss', $claims)),
'hd' => Assert::nonEmptyString(Assert::in('hd', $claims)),
'sub' => Assert::nonEmptyString(Assert::in('sub', $claims)),
'email' => Assert::nonEmptyString(Assert::in('email', $claims)),
];
} catch (AssertionException $e) {
throw new MalformedClaimsException($e);
}
}

/**
Expand Down Expand Up @@ -141,4 +121,27 @@ public function id(): string
throw new MalformedClaimsException($e);
}
}

/**
* @param array<array-key, mixed> $claims
*
* @throws MalformedClaimsException
*/
public static function from(
array $claims,
): self {
try {
return new self([
'exp' => Assert::positiveInt(Assert::in('exp', $claims)),
'iat' => Assert::positiveInt(Assert::in('iat', $claims)),
'aud' => Assert::nonEmptyString(Assert::in('aud', $claims)),
'iss' => Assert::nonEmptyString(Assert::in('iss', $claims)),
'hd' => Assert::nonEmptyString(Assert::in('hd', $claims)),
'sub' => Assert::nonEmptyString(Assert::in('sub', $claims)),
'email' => Assert::nonEmptyString(Assert::in('email', $claims)),
]);
} catch (AssertionException $e) {
throw new MalformedClaimsException($e);
}
}
}
2 changes: 1 addition & 1 deletion src/GoogleIdTokenVerifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public function verify(string $jwt): ?Claims
return null;
}

$claims = new Claims($claims);
$claims = Claims::from($claims);

if ($this->issuer !== null && $claims->iss() !== $this->issuer) {
// Issuer verification failed.
Expand Down
26 changes: 25 additions & 1 deletion src/Http/GoogleIapGuard.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,20 @@
use YumemiInc\GoogleIapLaravel\DefaultGoogleUserResolver;
use YumemiInc\GoogleIapLaravel\GoogleIdTokenVerifier;
use YumemiInc\GoogleIapLaravel\GoogleUserResolver;
use YumemiInc\GoogleIapLaravel\Internal\Assert;
use YumemiInc\GoogleIapLaravel\Internal\AssertionException;
use YumemiInc\GoogleIapLaravel\MalformedClaimsException;

class GoogleIapGuard extends RequestGuard
{
/**
* @param array{allow_insecure_headers?: bool} $options
*/
public function __construct(
Request $request,
private readonly GoogleIdTokenVerifier $googleIdTokenVerifier = new GoogleIdTokenVerifier(),
private readonly GoogleUserResolver $userProviderAdapter = new DefaultGoogleUserResolver(),
private readonly array $options = [],
) {
parent::__construct(static::callback(...), $request);
}
Expand All @@ -33,7 +39,25 @@ public function callback(): ?Authenticatable
return null;
}

if (!($claims = $this->googleIdTokenVerifier->verify($jwt)) instanceof Claims) {
try {
$id = Assert::nonEmptyStringOrNull($this->request->header('x-goog-authenticated-user-id'));
$email = Assert::nonEmptyStringOrNull($this->request->header('x-goog-authenticated-user-email'));
$hd = ($email === null ? null : Assert::nonEmptyString(explode('@', $email)[1])) ?? 'example.com';
} catch (AssertionException $e) {
throw new MalformedClaimsException($e);
}

if (($this->options['allow_insecure_headers'] ?? false) && ($id !== null || $email !== null)) {
$claims = new Claims([
'exp' => \PHP_INT_MAX,
'iat' => 1,
'aud' => 'insecure',
'iss' => 'https://cloud.google.com/iap',
'hd' => $hd,
'sub' => $id ?? 'accounts.google.com:0',
'email' => $email ?? 'accounts.google.com:[email protected]',
]);
} elseif (!($claims = $this->googleIdTokenVerifier->verify($jwt)) instanceof Claims) {
return null;
}

Expand Down
28 changes: 28 additions & 0 deletions src/Internal/Assert.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,20 @@ public static function nonEmptyString(mixed $value): string
return $s;
}

/**
* @phpstan-return ($value is non-empty-string ? non-empty-string : ($value is null ? null : never))
*
* @throws AssertionException
*/
public static function nonEmptyStringOrNull(mixed $value): null|string
{
try {
return self::nonEmptyString($value);
} catch (AssertionException) {
return self::null($value);
}
}

/**
* @template T
*
Expand All @@ -80,6 +94,20 @@ public static function nonNull(mixed $value): mixed
return $value;
}

/**
* @phpstan-return ($value is null ? null : never)
*
* @throws AssertionException
*/
public static function null(mixed $value): mixed
{
if ($value !== null) {
throw new AssertionException('null', 'non-null value');
}

return null;
}

/**
* @template TKey of array-key
* @template TValue
Expand Down
8 changes: 7 additions & 1 deletion src/Providers/GoogleIapServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,13 @@ protected function extendComponents(AuthManager $auth): void
$auth->extend(
'google-iap',
function (Container $app, string $name, array $config) use ($auth): GoogleIapGuard {
$guard = $this->app->make(GoogleIapGuard::class);
$guard = new GoogleIapGuard(
$app->make('request'),
$app->make(GoogleIdTokenVerifier::class),
$app->make(GoogleUserResolver::class),
$config['options'] ?? [],
);

$guard->setProvider(Assert::nonNull($auth->createUserProvider($config['provider'] ?? null)));

return $guard;
Expand Down

0 comments on commit fb31574

Please sign in to comment.