mirror of
https://github.com/coollabsio/coolify.git
synced 2026-08-24 10:05:47 -05:00
120 lines
4.5 KiB
PHP
120 lines
4.5 KiB
PHP
<?php
|
|
|
|
use App\Auth\Oidc\Exceptions\OidcDiscoveryException;
|
|
use App\Auth\Oidc\Exceptions\OidcJwksException;
|
|
use App\Auth\Oidc\OidcDiscoveryService;
|
|
use Illuminate\Support\Facades\Cache;
|
|
use Illuminate\Support\Facades\Http;
|
|
use Tests\TestCase;
|
|
|
|
uses(TestCase::class);
|
|
|
|
it('fetches and caches discovery documents and jwks', function () {
|
|
Cache::flush();
|
|
Http::fake([
|
|
'https://idp.example.com/.well-known/openid-configuration' => Http::response([
|
|
'issuer' => 'https://idp.example.com',
|
|
'authorization_endpoint' => 'https://idp.example.com/auth',
|
|
'token_endpoint' => 'https://idp.example.com/token',
|
|
'userinfo_endpoint' => 'https://idp.example.com/userinfo',
|
|
'jwks_uri' => 'https://idp.example.com/jwks',
|
|
]),
|
|
'https://idp.example.com/jwks' => Http::response(['keys' => [['kid' => 'one']]]),
|
|
]);
|
|
|
|
$service = app(OidcDiscoveryService::class);
|
|
|
|
$discovery = $service->discover('https://idp.example.com');
|
|
$jwks = $service->jwks($discovery->jwksUri);
|
|
|
|
expect($discovery->issuer)->toBe('https://idp.example.com')
|
|
->and($jwks['keys'][0]['kid'])->toBe('one');
|
|
|
|
Http::assertSentCount(2);
|
|
|
|
$service->discover('https://idp.example.com');
|
|
$service->jwks('https://idp.example.com/jwks');
|
|
|
|
Http::assertSentCount(2);
|
|
});
|
|
|
|
it('does not cache discovery documents with mismatched issuers', function () {
|
|
Cache::flush();
|
|
Http::fakeSequence('https://idp.example.com/.well-known/openid-configuration')
|
|
->push([
|
|
'issuer' => 'https://evil.example.com',
|
|
'authorization_endpoint' => 'https://idp.example.com/auth',
|
|
'token_endpoint' => 'https://idp.example.com/token',
|
|
'userinfo_endpoint' => 'https://idp.example.com/userinfo',
|
|
'jwks_uri' => 'https://idp.example.com/jwks',
|
|
])
|
|
->push([
|
|
'issuer' => 'https://idp.example.com',
|
|
'authorization_endpoint' => 'https://idp.example.com/auth',
|
|
'token_endpoint' => 'https://idp.example.com/token',
|
|
'userinfo_endpoint' => 'https://idp.example.com/userinfo',
|
|
'jwks_uri' => 'https://idp.example.com/jwks',
|
|
]);
|
|
|
|
$service = app(OidcDiscoveryService::class);
|
|
$cacheKey = 'oidc:discovery:'.hash('sha256', 'https://idp.example.com');
|
|
|
|
expect(fn () => $service->discover('https://idp.example.com'))
|
|
->toThrow(OidcDiscoveryException::class, 'Discovery issuer does not match the configured issuer URL.')
|
|
->and(Cache::has($cacheKey))->toBeFalse()
|
|
->and($service->discover('https://idp.example.com')->issuer)->toBe('https://idp.example.com');
|
|
|
|
Http::assertSentCount(2);
|
|
});
|
|
|
|
it('refetches jwks once on forced refresh to pick up rotated keys', function () {
|
|
Cache::flush();
|
|
Http::fakeSequence('https://idp.example.com/jwks')
|
|
->push(['keys' => [['kid' => 'old']]])
|
|
->push(['keys' => [['kid' => 'new']]]);
|
|
|
|
$service = app(OidcDiscoveryService::class);
|
|
|
|
expect($service->jwks('https://idp.example.com/jwks')['keys'][0]['kid'])->toBe('old');
|
|
|
|
// Forced refresh bypasses the cache and sees the rotated key.
|
|
expect($service->jwks('https://idp.example.com/jwks', true)['keys'][0]['kid'])->toBe('new');
|
|
Http::assertSentCount(2);
|
|
|
|
// Cooldown prevents a second immediate upstream fetch; cached value returned.
|
|
expect($service->jwks('https://idp.example.com/jwks', true)['keys'][0]['kid'])->toBe('new');
|
|
Http::assertSentCount(2);
|
|
});
|
|
|
|
it('rejects invalid discovery and jwks payloads', function () {
|
|
Cache::flush();
|
|
Http::fake([
|
|
'https://bad.example.com/.well-known/openid-configuration' => Http::response(['issuer' => 'https://bad.example.com']),
|
|
]);
|
|
|
|
app(OidcDiscoveryService::class)->discover('https://bad.example.com');
|
|
})->throws(OidcDiscoveryException::class);
|
|
|
|
it('rejects jwks responses without keys', function () {
|
|
Cache::flush();
|
|
Http::fake([
|
|
'https://idp.example.com/jwks' => Http::response(['empty' => true]),
|
|
]);
|
|
|
|
app(OidcDiscoveryService::class)->jwks('https://idp.example.com/jwks');
|
|
})->throws(OidcJwksException::class);
|
|
|
|
it('rejects non-https issuer urls', function () {
|
|
Cache::flush();
|
|
Http::fake();
|
|
|
|
app(OidcDiscoveryService::class)->discover('http://idp.example.com');
|
|
})->throws(OidcDiscoveryException::class, 'Issuer URL must be an absolute HTTPS URL.');
|
|
|
|
it('rejects non-https jwks uris', function () {
|
|
Cache::flush();
|
|
Http::fake();
|
|
|
|
app(OidcDiscoveryService::class)->jwks('http://idp.example.com/jwks');
|
|
})->throws(OidcJwksException::class, 'JWKS URI must be an absolute HTTPS URL.');
|