allow vendord
All checks were successful
Build, Push and Deploy / build-and-push (push) Successful in 3m3s
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 16s
Build, Push and Deploy / deploy-staging (push) Successful in 32s
Build, Push and Deploy / deploy-production (push) Has been skipped
Build, Push and Deploy / cleanup (push) Successful in 1s
All checks were successful
Build, Push and Deploy / build-and-push (push) Successful in 3m3s
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 16s
Build, Push and Deploy / deploy-staging (push) Successful in 32s
Build, Push and Deploy / deploy-production (push) Has been skipped
Build, Push and Deploy / cleanup (push) Successful in 1s
This commit is contained in:
123
vendor/kreait/firebase-tokens/src/JWT/Action/CreateCustomToken.php
vendored
Normal file
123
vendor/kreait/firebase-tokens/src/JWT/Action/CreateCustomToken.php
vendored
Normal file
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Kreait\Firebase\JWT\Action;
|
||||
|
||||
use DateInterval;
|
||||
use InvalidArgumentException;
|
||||
use Kreait\Firebase\JWT\Value\Duration;
|
||||
|
||||
final class CreateCustomToken
|
||||
{
|
||||
public const MINIMUM_TTL = 'PT1S';
|
||||
|
||||
public const MAXIMUM_TTL = 'PT1H';
|
||||
|
||||
public const DEFAULT_TTL = self::MAXIMUM_TTL;
|
||||
|
||||
private ?string $tenantId = null;
|
||||
|
||||
/** @var array<string, mixed> */
|
||||
private array $customClaims = [];
|
||||
|
||||
private Duration $ttl;
|
||||
|
||||
private function __construct(private string $uid)
|
||||
{
|
||||
$this->ttl = Duration::fromDateIntervalSpec(self::DEFAULT_TTL);
|
||||
}
|
||||
|
||||
public static function forUid(string $uid): self
|
||||
{
|
||||
return new self($uid);
|
||||
}
|
||||
|
||||
public function withTenantId(string $tenantId): self
|
||||
{
|
||||
$action = clone $this;
|
||||
$action->tenantId = $tenantId;
|
||||
|
||||
return $action;
|
||||
}
|
||||
|
||||
public function withChangedUid(string $uid): self
|
||||
{
|
||||
$action = clone $this;
|
||||
$action->uid = $uid;
|
||||
|
||||
return $action;
|
||||
}
|
||||
|
||||
public function withCustomClaim(string $name, mixed $value): self
|
||||
{
|
||||
$action = clone $this;
|
||||
$action->customClaims[$name] = $value;
|
||||
|
||||
return $action;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $claims
|
||||
*/
|
||||
public function withCustomClaims(array $claims): self
|
||||
{
|
||||
$action = clone $this;
|
||||
$action->customClaims = $claims;
|
||||
|
||||
return $action;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $claims
|
||||
*/
|
||||
public function withAddedCustomClaims(array $claims): self
|
||||
{
|
||||
$action = clone $this;
|
||||
$action->customClaims = [...$action->customClaims, ...$claims];
|
||||
|
||||
return $action;
|
||||
}
|
||||
|
||||
public function withTimeToLive(Duration|DateInterval|string|int $ttl): self
|
||||
{
|
||||
$ttl = Duration::make($ttl);
|
||||
|
||||
$minTtl = Duration::fromDateIntervalSpec(self::MINIMUM_TTL);
|
||||
$maxTtl = Duration::fromDateIntervalSpec(self::MAXIMUM_TTL);
|
||||
|
||||
if ($ttl->isSmallerThan($minTtl) || $ttl->isLargerThan($maxTtl)) {
|
||||
$message = 'The expiration time of a custom token must be between %s and %s, but got %s';
|
||||
|
||||
throw new InvalidArgumentException(sprintf($message, $minTtl, $maxTtl, $ttl));
|
||||
}
|
||||
|
||||
$action = clone $this;
|
||||
$action->ttl = $ttl;
|
||||
|
||||
return $action;
|
||||
}
|
||||
|
||||
public function uid(): string
|
||||
{
|
||||
return $this->uid;
|
||||
}
|
||||
|
||||
public function tenantId(): ?string
|
||||
{
|
||||
return $this->tenantId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function customClaims(): array
|
||||
{
|
||||
return $this->customClaims;
|
||||
}
|
||||
|
||||
public function timeToLive(): Duration
|
||||
{
|
||||
return $this->ttl;
|
||||
}
|
||||
}
|
||||
17
vendor/kreait/firebase-tokens/src/JWT/Action/CreateCustomToken/Handler.php
vendored
Normal file
17
vendor/kreait/firebase-tokens/src/JWT/Action/CreateCustomToken/Handler.php
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Kreait\Firebase\JWT\Action\CreateCustomToken;
|
||||
|
||||
use Kreait\Firebase\JWT\Action\CreateCustomToken;
|
||||
use Kreait\Firebase\JWT\Contract\Token;
|
||||
use Kreait\Firebase\JWT\Error\CustomTokenCreationFailed;
|
||||
|
||||
interface Handler
|
||||
{
|
||||
/**
|
||||
* @throws CustomTokenCreationFailed
|
||||
*/
|
||||
public function handle(CreateCustomToken $action): Token;
|
||||
}
|
||||
86
vendor/kreait/firebase-tokens/src/JWT/Action/CreateCustomToken/WithLcobucciJWT.php
vendored
Normal file
86
vendor/kreait/firebase-tokens/src/JWT/Action/CreateCustomToken/WithLcobucciJWT.php
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Kreait\Firebase\JWT\Action\CreateCustomToken;
|
||||
|
||||
use DateTimeInterface;
|
||||
use Kreait\Firebase\JWT\Action\CreateCustomToken;
|
||||
use Kreait\Firebase\JWT\Contract\Token;
|
||||
use Kreait\Firebase\JWT\Error\CustomTokenCreationFailed;
|
||||
use Kreait\Firebase\JWT\SecureToken;
|
||||
use Lcobucci\JWT\Configuration;
|
||||
use Lcobucci\JWT\Signer\Key\InMemory;
|
||||
use Lcobucci\JWT\Signer\Rsa\Sha256;
|
||||
use Psr\Clock\ClockInterface;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class WithLcobucciJWT implements Handler
|
||||
{
|
||||
private readonly Configuration $config;
|
||||
|
||||
/**
|
||||
* @param non-empty-string $clientEmail
|
||||
* @param non-empty-string $privateKey
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly string $clientEmail,
|
||||
string $privateKey,
|
||||
private readonly ClockInterface $clock,
|
||||
) {
|
||||
$this->config = Configuration::forSymmetricSigner(
|
||||
new Sha256(),
|
||||
InMemory::plainText($privateKey),
|
||||
);
|
||||
}
|
||||
|
||||
public function handle(CreateCustomToken $action): Token
|
||||
{
|
||||
$now = $this->clock->now();
|
||||
|
||||
$builder = $this->config->builder()
|
||||
->issuedAt($now)
|
||||
->issuedBy($this->clientEmail)
|
||||
->expiresAt($now->add($action->timeToLive()->value()))
|
||||
->relatedTo($this->clientEmail)
|
||||
->permittedFor('https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit')
|
||||
->withClaim('uid', $action->uid());
|
||||
|
||||
if ($tenantId = $action->tenantId()) {
|
||||
$builder = $builder->withClaim('tenant_id', $tenantId);
|
||||
}
|
||||
|
||||
if (!empty($customClaims = $action->customClaims())) {
|
||||
$builder = $builder->withClaim('claims', $customClaims);
|
||||
}
|
||||
|
||||
try {
|
||||
$token = $builder->getToken($this->config->signer(), $this->config->signingKey());
|
||||
} catch (Throwable $e) {
|
||||
throw CustomTokenCreationFailed::because($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
|
||||
$claims = $token->claims()->all();
|
||||
|
||||
foreach ($claims as &$claim) {
|
||||
if ($claim instanceof DateTimeInterface) {
|
||||
$claim = $claim->getTimestamp();
|
||||
}
|
||||
}
|
||||
unset($claim);
|
||||
|
||||
$headers = $token->headers()->all();
|
||||
|
||||
foreach ($headers as &$header) {
|
||||
if ($header instanceof DateTimeInterface) {
|
||||
$header = $header->getTimestamp();
|
||||
}
|
||||
}
|
||||
unset($header);
|
||||
|
||||
return SecureToken::withValues($token->toString(), $headers, $claims);
|
||||
}
|
||||
}
|
||||
74
vendor/kreait/firebase-tokens/src/JWT/Action/FetchGooglePublicKeys.php
vendored
Normal file
74
vendor/kreait/firebase-tokens/src/JWT/Action/FetchGooglePublicKeys.php
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Kreait\Firebase\JWT\Action;
|
||||
|
||||
use DateInterval;
|
||||
use Kreait\Firebase\JWT\Value\Duration;
|
||||
|
||||
final class FetchGooglePublicKeys
|
||||
{
|
||||
public const DEFAULT_URLS = [
|
||||
'https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com',
|
||||
'https://www.googleapis.com/oauth2/v1/certs',
|
||||
'https://www.googleapis.com/identitytoolkit/v3/relyingparty/publicKeys',
|
||||
];
|
||||
|
||||
public const DEFAULT_FALLBACK_CACHE_DURATION = 'PT1H';
|
||||
|
||||
/** @var array<int, non-empty-string> */
|
||||
private readonly array $urls;
|
||||
|
||||
/**
|
||||
* @param array<array-key, non-empty-string> $urls
|
||||
*/
|
||||
private function __construct(array $urls, private Duration $fallbackCacheDuration)
|
||||
{
|
||||
$this->urls = array_values($urls);
|
||||
}
|
||||
|
||||
public static function fromGoogle(): self
|
||||
{
|
||||
return new self(self::DEFAULT_URLS, Duration::fromDateIntervalSpec(self::DEFAULT_FALLBACK_CACHE_DURATION));
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this method only if Google has changed the default URL and the library hasn't been updated yet.
|
||||
*
|
||||
* @param non-empty-string $url
|
||||
*/
|
||||
public static function fromUrl(string $url): self
|
||||
{
|
||||
return new self([$url], Duration::fromDateIntervalSpec(self::DEFAULT_FALLBACK_CACHE_DURATION));
|
||||
}
|
||||
|
||||
/**
|
||||
* A response from the Google APIs should have a cache control header that determines when the keys expire.
|
||||
* If it doesn't have one, fall back to this value.
|
||||
*
|
||||
* @param Duration|DateInterval|non-empty-string|int $duration
|
||||
*/
|
||||
public function ifKeysDoNotExpireCacheFor(Duration|DateInterval|string|int $duration): self
|
||||
{
|
||||
$duration = Duration::make($duration);
|
||||
|
||||
$action = clone $this;
|
||||
$action->fallbackCacheDuration = $duration;
|
||||
|
||||
return $action;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function urls(): array
|
||||
{
|
||||
return $this->urls;
|
||||
}
|
||||
|
||||
public function getFallbackCacheDuration(): Duration
|
||||
{
|
||||
return $this->fallbackCacheDuration;
|
||||
}
|
||||
}
|
||||
17
vendor/kreait/firebase-tokens/src/JWT/Action/FetchGooglePublicKeys/Handler.php
vendored
Normal file
17
vendor/kreait/firebase-tokens/src/JWT/Action/FetchGooglePublicKeys/Handler.php
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Kreait\Firebase\JWT\Action\FetchGooglePublicKeys;
|
||||
|
||||
use Kreait\Firebase\JWT\Action\FetchGooglePublicKeys;
|
||||
use Kreait\Firebase\JWT\Contract\Keys;
|
||||
use Kreait\Firebase\JWT\Error\FetchingGooglePublicKeysFailed;
|
||||
|
||||
interface Handler
|
||||
{
|
||||
/**
|
||||
* @throws FetchingGooglePublicKeysFailed
|
||||
*/
|
||||
public function handle(FetchGooglePublicKeys $action): Keys;
|
||||
}
|
||||
101
vendor/kreait/firebase-tokens/src/JWT/Action/FetchGooglePublicKeys/WithGuzzle.php
vendored
Normal file
101
vendor/kreait/firebase-tokens/src/JWT/Action/FetchGooglePublicKeys/WithGuzzle.php
vendored
Normal file
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Kreait\Firebase\JWT\Action\FetchGooglePublicKeys;
|
||||
|
||||
use Fig\Http\Message\RequestMethodInterface as RequestMethod;
|
||||
use GuzzleHttp\ClientInterface;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use JsonException;
|
||||
use Kreait\Firebase\JWT\Action\FetchGooglePublicKeys;
|
||||
use Kreait\Firebase\JWT\Contract\Keys;
|
||||
use Kreait\Firebase\JWT\Error\FetchingGooglePublicKeysFailed;
|
||||
use Kreait\Firebase\JWT\Keys\ExpiringKeys;
|
||||
use Psr\Clock\ClockInterface;
|
||||
|
||||
use const JSON_THROW_ON_ERROR;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class WithGuzzle implements Handler
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ClientInterface $client,
|
||||
private readonly ClockInterface $clock,
|
||||
) {
|
||||
}
|
||||
|
||||
public function handle(FetchGooglePublicKeys $action): Keys
|
||||
{
|
||||
$keys = [];
|
||||
$ttls = [];
|
||||
|
||||
foreach ($action->urls() as $url) {
|
||||
$result = $this->fetchKeysFromUrl($url);
|
||||
|
||||
$keys[] = $result['keys'];
|
||||
$ttls[] = $result['ttl'];
|
||||
}
|
||||
|
||||
$keys = array_merge(...$keys);
|
||||
$ttl = $ttls !== [] ? min($ttls) : 0;
|
||||
$now = $this->clock->now();
|
||||
|
||||
$expiresAt = $ttl > 0
|
||||
? $now->setTimestamp($now->getTimestamp() + $ttl)
|
||||
: $now->add($action->getFallbackCacheDuration()->value());
|
||||
|
||||
return ExpiringKeys::withValuesAndExpirationTime($keys, $expiresAt);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{
|
||||
* keys: array<non-empty-string, non-empty-string>,
|
||||
* ttl: int
|
||||
* }
|
||||
*/
|
||||
private function fetchKeysFromUrl(string $url): array
|
||||
{
|
||||
try {
|
||||
$response = $this->client->request(RequestMethod::METHOD_GET, $url, [
|
||||
'http_errors' => false,
|
||||
'headers' => [
|
||||
'Content-Type' => 'Content-Type: application/json; charset=UTF-8',
|
||||
],
|
||||
]);
|
||||
} catch (GuzzleException $e) {
|
||||
throw FetchingGooglePublicKeysFailed::because("The connection to {$url} failed: ".$e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
|
||||
if (($statusCode = $response->getStatusCode()) !== 200) {
|
||||
throw FetchingGooglePublicKeysFailed::because(
|
||||
"Unexpected status code {$statusCode} when trying to fetch public keys from {$url}",
|
||||
);
|
||||
}
|
||||
|
||||
$ttl = preg_match('/max-age=(\d+)/i', $response->getHeaderLine('Cache-Control'), $matches)
|
||||
? (int) $matches[1]
|
||||
: 0;
|
||||
|
||||
try {
|
||||
$keys = json_decode((string) $response->getBody(), true, 512, JSON_THROW_ON_ERROR);
|
||||
} catch (JsonException $e) {
|
||||
throw FetchingGooglePublicKeysFailed::because('Unexpected response: '.$e->getMessage());
|
||||
}
|
||||
|
||||
if (!is_array($keys)) {
|
||||
$keys = [];
|
||||
}
|
||||
|
||||
$keys = array_filter($keys, fn(mixed $key): bool => is_string($key));
|
||||
$keys = array_map(fn(string $key): string => trim($key), $keys);
|
||||
$keys = array_filter($keys, fn(string $key): bool => $key !== '');
|
||||
|
||||
return [
|
||||
'keys' => $keys,
|
||||
'ttl' => $ttl,
|
||||
];
|
||||
}
|
||||
}
|
||||
77
vendor/kreait/firebase-tokens/src/JWT/Action/FetchGooglePublicKeys/WithPsr6Cache.php
vendored
Normal file
77
vendor/kreait/firebase-tokens/src/JWT/Action/FetchGooglePublicKeys/WithPsr6Cache.php
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Kreait\Firebase\JWT\Action\FetchGooglePublicKeys;
|
||||
|
||||
use Kreait\Firebase\JWT\Action\FetchGooglePublicKeys;
|
||||
use Kreait\Firebase\JWT\Contract\Expirable;
|
||||
use Kreait\Firebase\JWT\Contract\Keys;
|
||||
use Kreait\Firebase\JWT\Error\FetchingGooglePublicKeysFailed;
|
||||
use Psr\Cache\CacheItemPoolInterface;
|
||||
use Psr\Clock\ClockInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class WithPsr6Cache implements Handler
|
||||
{
|
||||
public function __construct(
|
||||
private readonly Handler $handler,
|
||||
private readonly CacheItemPoolInterface $cache,
|
||||
private readonly ClockInterface $clock,
|
||||
) {
|
||||
}
|
||||
|
||||
public function handle(FetchGooglePublicKeys $action): Keys
|
||||
{
|
||||
$now = $this->clock->now();
|
||||
$cacheKey = md5($action::class);
|
||||
|
||||
/** @noinspection PhpUnhandledExceptionInspection */
|
||||
$cacheItem = $this->cache->getItem($cacheKey);
|
||||
|
||||
/** @var Keys|Expirable|null $keys */
|
||||
$keys = $cacheItem->get();
|
||||
|
||||
// We deliberately don't care if the cache item is expired here, as long as the keys
|
||||
// themselves are not expired
|
||||
if ($keys instanceof Keys && $keys instanceof Expirable && !$keys->isExpiredAt($now)) {
|
||||
return $keys;
|
||||
}
|
||||
|
||||
// Non-expiring keys coming from a cache hit can be returned as well
|
||||
if ($keys instanceof Keys && !($keys instanceof Expirable) && $cacheItem->isHit()) {
|
||||
return $keys;
|
||||
}
|
||||
|
||||
// At this point, we have to re-fetch the keys, because either the cache item is a miss
|
||||
// or the value in the cache item is not a Keys object
|
||||
|
||||
// We need fresh keys
|
||||
try {
|
||||
$keys = $this->handler->handle($action);
|
||||
} catch (FetchingGooglePublicKeysFailed $e) {
|
||||
$reason = sprintf(
|
||||
'The inner handler of %s (%s) failed in fetching keys: %s',
|
||||
self::class,
|
||||
$this->handler::class,
|
||||
$e->getMessage(),
|
||||
);
|
||||
|
||||
throw FetchingGooglePublicKeysFailed::because($reason, $e->getCode(), $e);
|
||||
}
|
||||
|
||||
$cacheItem->set($keys);
|
||||
|
||||
if ($keys instanceof Expirable) {
|
||||
$cacheItem->expiresAt($keys->expiresAt());
|
||||
} else {
|
||||
$cacheItem->expiresAfter($action->getFallbackCacheDuration()->value());
|
||||
}
|
||||
|
||||
$this->cache->save($cacheItem);
|
||||
|
||||
return $keys;
|
||||
}
|
||||
}
|
||||
71
vendor/kreait/firebase-tokens/src/JWT/Action/VerifyIdToken.php
vendored
Normal file
71
vendor/kreait/firebase-tokens/src/JWT/Action/VerifyIdToken.php
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Kreait\Firebase\JWT\Action;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
final class VerifyIdToken
|
||||
{
|
||||
/**
|
||||
* @param non-empty-string $token
|
||||
* @param int<0, max> $leewayInSeconds
|
||||
* @param non-empty-string|null $expectedTenantId
|
||||
*/
|
||||
private function __construct(
|
||||
private readonly string $token,
|
||||
private readonly int $leewayInSeconds,
|
||||
private readonly ?string $expectedTenantId,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param non-empty-string $token
|
||||
*/
|
||||
public static function withToken(string $token): self
|
||||
{
|
||||
return new self($token, 0, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param non-empty-string $tenantId
|
||||
*/
|
||||
public function withExpectedTenantId(string $tenantId): self
|
||||
{
|
||||
return new self($this->token, $this->leewayInSeconds, $tenantId);
|
||||
}
|
||||
|
||||
public function withLeewayInSeconds(int $seconds): self
|
||||
{
|
||||
if ($seconds < 0) {
|
||||
throw new InvalidArgumentException('Leeway must not be negative');
|
||||
}
|
||||
|
||||
return new self($this->token, $seconds, $this->expectedTenantId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return non-empty-string
|
||||
*/
|
||||
public function token(): string
|
||||
{
|
||||
return $this->token;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return non-empty-string|null
|
||||
*/
|
||||
public function expectedTenantId(): ?string
|
||||
{
|
||||
return $this->expectedTenantId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int<0, max>
|
||||
*/
|
||||
public function leewayInSeconds(): int
|
||||
{
|
||||
return $this->leewayInSeconds;
|
||||
}
|
||||
}
|
||||
20
vendor/kreait/firebase-tokens/src/JWT/Action/VerifyIdToken/Handler.php
vendored
Normal file
20
vendor/kreait/firebase-tokens/src/JWT/Action/VerifyIdToken/Handler.php
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Kreait\Firebase\JWT\Action\VerifyIdToken;
|
||||
|
||||
use Kreait\Firebase\JWT\Action\VerifyIdToken;
|
||||
use Kreait\Firebase\JWT\Contract\Token;
|
||||
use Kreait\Firebase\JWT\Error\IdTokenVerificationFailed;
|
||||
|
||||
/**
|
||||
* @see https://firebase.google.com/docs/auth/admin/verify-id-tokens#verify_id_tokens_using_a_third-party_jwt_library
|
||||
*/
|
||||
interface Handler
|
||||
{
|
||||
/**
|
||||
* @throws IdTokenVerificationFailed
|
||||
*/
|
||||
public function handle(VerifyIdToken $action): Token;
|
||||
}
|
||||
203
vendor/kreait/firebase-tokens/src/JWT/Action/VerifyIdToken/WithLcobucciJWT.php
vendored
Normal file
203
vendor/kreait/firebase-tokens/src/JWT/Action/VerifyIdToken/WithLcobucciJWT.php
vendored
Normal file
@@ -0,0 +1,203 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Kreait\Firebase\JWT\Action\VerifyIdToken;
|
||||
|
||||
use Beste\Clock\FrozenClock;
|
||||
use DateInterval;
|
||||
use DateTimeImmutable;
|
||||
use DateTimeInterface;
|
||||
use Kreait\Firebase\JWT\Action\VerifyIdToken;
|
||||
use Kreait\Firebase\JWT\Contract\Keys;
|
||||
use Kreait\Firebase\JWT\Contract\Token;
|
||||
use Kreait\Firebase\JWT\Error\IdTokenVerificationFailed;
|
||||
use Kreait\Firebase\JWT\Error\SessionCookieVerificationFailed;
|
||||
use Kreait\Firebase\JWT\InsecureToken;
|
||||
use Kreait\Firebase\JWT\SecureToken;
|
||||
use Kreait\Firebase\JWT\Signer\None;
|
||||
use Kreait\Firebase\JWT\Token\Parser;
|
||||
use Kreait\Firebase\JWT\Util;
|
||||
use Lcobucci\JWT\Encoding\JoseEncoder;
|
||||
use Lcobucci\JWT\Signer;
|
||||
use Lcobucci\JWT\Signer\Key\InMemory;
|
||||
use Lcobucci\JWT\Signer\Rsa\Sha256;
|
||||
use Lcobucci\JWT\UnencryptedToken;
|
||||
use Lcobucci\JWT\Validation\Constraint\IssuedBy;
|
||||
use Lcobucci\JWT\Validation\Constraint\LooseValidAt;
|
||||
use Lcobucci\JWT\Validation\Constraint\PermittedFor;
|
||||
use Lcobucci\JWT\Validation\Constraint\SignedWith;
|
||||
use Lcobucci\JWT\Validation\ConstraintViolation;
|
||||
use Lcobucci\JWT\Validation\RequiredConstraintsViolated;
|
||||
use Lcobucci\JWT\Validation\Validator;
|
||||
use Psr\Clock\ClockInterface;
|
||||
use Throwable;
|
||||
|
||||
use function assert;
|
||||
use function is_string;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class WithLcobucciJWT implements Handler
|
||||
{
|
||||
private readonly Parser $parser;
|
||||
|
||||
private readonly Signer $signer;
|
||||
|
||||
private readonly Validator $validator;
|
||||
|
||||
private readonly bool $isRunOnEmulator;
|
||||
|
||||
/**
|
||||
* @param non-empty-string $projectId
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly string $projectId,
|
||||
private readonly Keys $keys,
|
||||
private readonly ClockInterface $clock,
|
||||
) {
|
||||
$this->parser = new Parser(new JoseEncoder());
|
||||
|
||||
$this->isRunOnEmulator = Util::authEmulatorHost() !== '';
|
||||
|
||||
$this->signer = $this->isRunOnEmulator ? new None() : new Sha256();
|
||||
$this->validator = new Validator();
|
||||
}
|
||||
|
||||
public function handle(VerifyIdToken $action): Token
|
||||
{
|
||||
$tokenString = $action->token();
|
||||
|
||||
try {
|
||||
$token = $this->parser->parse($tokenString);
|
||||
assert($token instanceof UnencryptedToken);
|
||||
} catch (Throwable $e) {
|
||||
throw IdTokenVerificationFailed::withTokenAndReasons($tokenString, ['The token is invalid'.$e->getMessage()]);
|
||||
}
|
||||
|
||||
$key = $this->getKey($token);
|
||||
$clock = FrozenClock::at($this->clock->now());
|
||||
$leeway = new DateInterval('PT'.$action->leewayInSeconds().'S');
|
||||
$errors = [];
|
||||
|
||||
$constraints = [
|
||||
new LooseValidAt($clock, $leeway),
|
||||
new IssuedBy(...["https://securetoken.google.com/{$this->projectId}"]),
|
||||
new PermittedFor($this->projectId),
|
||||
];
|
||||
|
||||
if ($key !== '' && !$this->isRunOnEmulator) {
|
||||
$constraints[] = new SignedWith($this->signer, InMemory::plainText($key));
|
||||
}
|
||||
|
||||
try {
|
||||
$this->validator->assert($token, ...$constraints);
|
||||
|
||||
$this->assertUserAuthedAt($token, $clock->now()->add($leeway));
|
||||
|
||||
if ($tenantId = $action->expectedTenantId()) {
|
||||
$this->assertTenantId($token, $tenantId);
|
||||
}
|
||||
} catch (RequiredConstraintsViolated $e) {
|
||||
$errors = array_filter(array_map(
|
||||
static fn(ConstraintViolation $violation): string => $violation->getMessage(),
|
||||
$e->violations(),
|
||||
));
|
||||
}
|
||||
|
||||
if (!empty($errors)) {
|
||||
throw IdTokenVerificationFailed::withTokenAndReasons($tokenString, $errors);
|
||||
}
|
||||
|
||||
$claims = $token->claims()->all();
|
||||
|
||||
foreach ($claims as &$claim) {
|
||||
if ($claim instanceof DateTimeInterface) {
|
||||
$claim = $claim->getTimestamp();
|
||||
}
|
||||
}
|
||||
unset($claim);
|
||||
|
||||
$headers = $token->headers()->all();
|
||||
|
||||
foreach ($headers as &$header) {
|
||||
if ($header instanceof DateTimeInterface) {
|
||||
$header = $header->getTimestamp();
|
||||
}
|
||||
}
|
||||
unset($header);
|
||||
|
||||
if ($this->isRunOnEmulator) {
|
||||
return InsecureToken::withValues($tokenString, $headers, $claims);
|
||||
}
|
||||
|
||||
return SecureToken::withValues($tokenString, $headers, $claims);
|
||||
}
|
||||
|
||||
private function getKey(UnencryptedToken $token): string
|
||||
{
|
||||
$keys = $this->keys->all();
|
||||
if ($keys === []) {
|
||||
throw IdTokenVerificationFailed::withTokenAndReasons($token->toString(), ['No keys are available to verify the tokens signature.']);
|
||||
}
|
||||
|
||||
if ($this->isRunOnEmulator && ($this->signer instanceof None)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$keyId = $token->headers()->get('kid');
|
||||
if (!is_string($keyId) || $keyId === '') {
|
||||
throw IdTokenVerificationFailed::withTokenAndReasons($token->toString(), ['No key ID was found to verify the signature of this token.']);
|
||||
}
|
||||
|
||||
$key = $keys[$keyId] ?? null;
|
||||
|
||||
if ($key === null) {
|
||||
throw IdTokenVerificationFailed::withTokenAndReasons($token->toString(), ["No public key matching the key ID '{$keyId}' was found to verify the signature of this token."]);
|
||||
}
|
||||
|
||||
return $key;
|
||||
}
|
||||
|
||||
private function assertUserAuthedAt(UnencryptedToken $token, DateTimeInterface $now): void
|
||||
{
|
||||
/** @var int|DateTimeImmutable $authTime */
|
||||
$authTime = $token->claims()->get('auth_time');
|
||||
|
||||
if (!$authTime) {
|
||||
throw RequiredConstraintsViolated::fromViolations(
|
||||
new ConstraintViolation('The token is missing the "auth_time" claim.'),
|
||||
);
|
||||
}
|
||||
|
||||
if (is_numeric($authTime)) {
|
||||
$authTime = new DateTimeImmutable('@'.((int) $authTime));
|
||||
}
|
||||
|
||||
if ($now < $authTime) {
|
||||
throw RequiredConstraintsViolated::fromViolations(
|
||||
new ConstraintViolation("The token's user must have authenticated in the past"),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function assertTenantId(UnencryptedToken $token, string $tenantId): void
|
||||
{
|
||||
$claim = (array) $token->claims()->get('firebase', []);
|
||||
|
||||
$tenant = $claim['tenant'] ?? null;
|
||||
|
||||
if (!is_string($tenant)) {
|
||||
throw RequiredConstraintsViolated::fromViolations(
|
||||
new ConstraintViolation('The ID token does not contain a tenant identifier'),
|
||||
);
|
||||
}
|
||||
|
||||
if ($tenant !== $tenantId) {
|
||||
throw RequiredConstraintsViolated::fromViolations(
|
||||
new ConstraintViolation("The token's tenant ID did not match with the expected tenant ID"),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
71
vendor/kreait/firebase-tokens/src/JWT/Action/VerifySessionCookie.php
vendored
Normal file
71
vendor/kreait/firebase-tokens/src/JWT/Action/VerifySessionCookie.php
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Kreait\Firebase\JWT\Action;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
final class VerifySessionCookie
|
||||
{
|
||||
/**
|
||||
* @param non-empty-string $sessionCookie
|
||||
* @param int<0, max> $leewayInSeconds
|
||||
* @param non-empty-string|null $expectedTenantId
|
||||
*/
|
||||
private function __construct(
|
||||
private readonly string $sessionCookie,
|
||||
private readonly int $leewayInSeconds,
|
||||
private readonly ?string $expectedTenantId,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param non-empty-string $sessionCookie
|
||||
*/
|
||||
public static function withSessionCookie(string $sessionCookie): self
|
||||
{
|
||||
return new self($sessionCookie, 0, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param non-empty-string $tenantId
|
||||
*/
|
||||
public function withExpectedTenantId(string $tenantId): self
|
||||
{
|
||||
return new self($this->sessionCookie, $this->leewayInSeconds, $tenantId);
|
||||
}
|
||||
|
||||
public function withLeewayInSeconds(int $seconds): self
|
||||
{
|
||||
if ($seconds < 0) {
|
||||
throw new InvalidArgumentException('Leeway must not be negative');
|
||||
}
|
||||
|
||||
return new self($this->sessionCookie, $seconds, $this->expectedTenantId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return non-empty-string
|
||||
*/
|
||||
public function sessionCookie(): string
|
||||
{
|
||||
return $this->sessionCookie;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return non-empty-string|null
|
||||
*/
|
||||
public function expectedTenantId(): ?string
|
||||
{
|
||||
return $this->expectedTenantId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int<0, max>
|
||||
*/
|
||||
public function leewayInSeconds(): int
|
||||
{
|
||||
return $this->leewayInSeconds;
|
||||
}
|
||||
}
|
||||
20
vendor/kreait/firebase-tokens/src/JWT/Action/VerifySessionCookie/Handler.php
vendored
Normal file
20
vendor/kreait/firebase-tokens/src/JWT/Action/VerifySessionCookie/Handler.php
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Kreait\Firebase\JWT\Action\VerifySessionCookie;
|
||||
|
||||
use Kreait\Firebase\JWT\Action\VerifySessionCookie;
|
||||
use Kreait\Firebase\JWT\Contract\Token;
|
||||
use Kreait\Firebase\JWT\Error\SessionCookieVerificationFailed;
|
||||
|
||||
/**
|
||||
* @see https://firebase.google.com/docs/auth/admin/manage-cookies#verify_session_cookies_using_a_third-party_jwt_library
|
||||
*/
|
||||
interface Handler
|
||||
{
|
||||
/**
|
||||
* @throws SessionCookieVerificationFailed
|
||||
*/
|
||||
public function handle(VerifySessionCookie $action): Token;
|
||||
}
|
||||
201
vendor/kreait/firebase-tokens/src/JWT/Action/VerifySessionCookie/WithLcobucciJWT.php
vendored
Normal file
201
vendor/kreait/firebase-tokens/src/JWT/Action/VerifySessionCookie/WithLcobucciJWT.php
vendored
Normal file
@@ -0,0 +1,201 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Kreait\Firebase\JWT\Action\VerifySessionCookie;
|
||||
|
||||
use Beste\Clock\FrozenClock;
|
||||
use DateInterval;
|
||||
use DateTimeImmutable;
|
||||
use DateTimeInterface;
|
||||
use Kreait\Firebase\JWT\Action\VerifySessionCookie;
|
||||
use Kreait\Firebase\JWT\Contract\Keys;
|
||||
use Kreait\Firebase\JWT\Contract\Token;
|
||||
use Kreait\Firebase\JWT\Error\SessionCookieVerificationFailed;
|
||||
use Kreait\Firebase\JWT\InsecureToken;
|
||||
use Kreait\Firebase\JWT\SecureToken;
|
||||
use Kreait\Firebase\JWT\Signer\None;
|
||||
use Kreait\Firebase\JWT\Token\Parser;
|
||||
use Kreait\Firebase\JWT\Util;
|
||||
use Lcobucci\JWT\Encoding\JoseEncoder;
|
||||
use Lcobucci\JWT\Signer;
|
||||
use Lcobucci\JWT\Signer\Key\InMemory;
|
||||
use Lcobucci\JWT\Signer\Rsa\Sha256;
|
||||
use Lcobucci\JWT\UnencryptedToken;
|
||||
use Lcobucci\JWT\Validation\Constraint\IssuedBy;
|
||||
use Lcobucci\JWT\Validation\Constraint\LooseValidAt;
|
||||
use Lcobucci\JWT\Validation\Constraint\PermittedFor;
|
||||
use Lcobucci\JWT\Validation\Constraint\SignedWith;
|
||||
use Lcobucci\JWT\Validation\ConstraintViolation;
|
||||
use Lcobucci\JWT\Validation\RequiredConstraintsViolated;
|
||||
use Lcobucci\JWT\Validation\Validator;
|
||||
use Psr\Clock\ClockInterface;
|
||||
use Throwable;
|
||||
|
||||
use function assert;
|
||||
use function is_string;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class WithLcobucciJWT implements Handler
|
||||
{
|
||||
private readonly Parser $parser;
|
||||
|
||||
private Signer $signer;
|
||||
|
||||
private readonly Validator $validator;
|
||||
|
||||
private readonly bool $isRunOnEmulator;
|
||||
|
||||
/**
|
||||
* @param non-empty-string $projectId
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly string $projectId,
|
||||
private readonly Keys $keys,
|
||||
private readonly ClockInterface $clock,
|
||||
) {
|
||||
$this->parser = new Parser(new JoseEncoder());
|
||||
|
||||
$this->isRunOnEmulator = Util::authEmulatorHost() !== '';
|
||||
|
||||
$this->signer = $this->isRunOnEmulator ? new None() : new Sha256();
|
||||
$this->validator = new Validator();
|
||||
}
|
||||
|
||||
public function handle(VerifySessionCookie $action): Token
|
||||
{
|
||||
$cookieString = $action->sessionCookie();
|
||||
|
||||
try {
|
||||
$token = $this->parser->parse($cookieString);
|
||||
assert($token instanceof UnencryptedToken);
|
||||
} catch (Throwable $e) {
|
||||
throw SessionCookieVerificationFailed::withSessionCookieAndReasons($cookieString, ['The token is invalid', $e->getMessage()]);
|
||||
}
|
||||
|
||||
$key = $this->getKey($token);
|
||||
$clock = FrozenClock::at($this->clock->now());
|
||||
$leeway = new DateInterval('PT'.$action->leewayInSeconds().'S');
|
||||
$errors = [];
|
||||
$constraints = [
|
||||
new LooseValidAt($clock, $leeway),
|
||||
new IssuedBy(...["https://session.firebase.google.com/{$this->projectId}"]),
|
||||
new PermittedFor($this->projectId),
|
||||
];
|
||||
|
||||
if ($key !== '' && Util::authEmulatorHost() === '') {
|
||||
$constraints[] = new SignedWith($this->signer, InMemory::plainText($key));
|
||||
}
|
||||
|
||||
try {
|
||||
$this->validator->assert($token, ...$constraints);
|
||||
|
||||
$this->assertUserAuthedAt($token, $clock->now()->add($leeway));
|
||||
|
||||
if ($tenantId = $action->expectedTenantId()) {
|
||||
$this->assertTenantId($token, $tenantId);
|
||||
}
|
||||
} catch (RequiredConstraintsViolated $e) {
|
||||
$errors = array_map(
|
||||
static fn(ConstraintViolation $violation): string => '- '.$violation->getMessage(),
|
||||
$e->violations(),
|
||||
);
|
||||
}
|
||||
|
||||
if (!empty($errors)) {
|
||||
throw SessionCookieVerificationFailed::withSessionCookieAndReasons($cookieString, $errors);
|
||||
}
|
||||
|
||||
$claims = $token->claims()->all();
|
||||
|
||||
foreach ($claims as &$claim) {
|
||||
if ($claim instanceof DateTimeInterface) {
|
||||
$claim = $claim->getTimestamp();
|
||||
}
|
||||
}
|
||||
unset($claim);
|
||||
|
||||
$headers = $token->headers()->all();
|
||||
|
||||
foreach ($headers as &$header) {
|
||||
if ($header instanceof DateTimeInterface) {
|
||||
$header = $header->getTimestamp();
|
||||
}
|
||||
}
|
||||
unset($header);
|
||||
|
||||
if (Util::authEmulatorHost() !== '') {
|
||||
return InsecureToken::withValues($cookieString, $headers, $claims);
|
||||
}
|
||||
|
||||
return SecureToken::withValues($cookieString, $headers, $claims);
|
||||
}
|
||||
|
||||
private function getKey(UnencryptedToken $token): string
|
||||
{
|
||||
$keys = $this->keys->all();
|
||||
if ($keys === []) {
|
||||
throw SessionCookieVerificationFailed::withSessionCookieAndReasons($token->toString(), ["No keys are available to verify the token's signature."]);
|
||||
}
|
||||
|
||||
if ($this->isRunOnEmulator && ($this->signer instanceof None)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$keyId = $token->headers()->get('kid');
|
||||
if (!is_string($keyId) || $keyId === '') {
|
||||
throw SessionCookieVerificationFailed::withSessionCookieAndReasons($token->toString(), ["The session cookie doesn't include a `kid` header."]);
|
||||
}
|
||||
|
||||
$key = $keys[$keyId] ?? null;
|
||||
|
||||
if ($key === null) {
|
||||
throw SessionCookieVerificationFailed::withSessionCookieAndReasons($token->toString(), ["The `kid` header of the given token is missing or empty.No public key matching the key ID `{$keyId}` was found to verify the signature of this session cookie."]);
|
||||
}
|
||||
|
||||
return $key;
|
||||
}
|
||||
|
||||
private function assertUserAuthedAt(UnencryptedToken $token, DateTimeInterface $now): void
|
||||
{
|
||||
/** @var int|DateTimeImmutable $authTime */
|
||||
$authTime = $token->claims()->get('auth_time');
|
||||
|
||||
if (!$authTime) {
|
||||
throw RequiredConstraintsViolated::fromViolations(
|
||||
new ConstraintViolation('The token is missing the "auth_time" claim.'),
|
||||
);
|
||||
}
|
||||
|
||||
if (is_numeric($authTime)) {
|
||||
$authTime = new DateTimeImmutable('@'.((int) $authTime));
|
||||
}
|
||||
|
||||
if ($now < $authTime) {
|
||||
throw RequiredConstraintsViolated::fromViolations(
|
||||
new ConstraintViolation("The token's user must have authenticated in the past"),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function assertTenantId(UnencryptedToken $token, string $tenantId): void
|
||||
{
|
||||
$claim = (array) $token->claims()->get('firebase', []);
|
||||
|
||||
$tenant = $claim['tenant'] ?? null;
|
||||
|
||||
if (!is_string($tenant)) {
|
||||
throw RequiredConstraintsViolated::fromViolations(
|
||||
new ConstraintViolation('The ID token does not contain a tenant identifier'),
|
||||
);
|
||||
}
|
||||
|
||||
if ($tenant !== $tenantId) {
|
||||
throw RequiredConstraintsViolated::fromViolations(
|
||||
new ConstraintViolation("The token's tenant ID did not match with the expected tenant ID"),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user