Skip to content

Commit 01ad3c5

Browse files
committed
Add VerifyLogoutToken tests
1 parent 3a0c8db commit 01ad3c5

File tree

1 file changed

+306
-0
lines changed

1 file changed

+306
-0
lines changed

tests/OpenIDConnectClientTest.php

Lines changed: 306 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2471,5 +2471,311 @@ public function testRequestUserInfoSignedEncrypted()
24712471
$this->assertEquals($email, $userData->email);
24722472
}
24732473

2474+
public function testVerifyLogoutTokenThrowsExceptionWithoutToken()
2475+
{
2476+
$client = new OpenIDConnectClient(
2477+
'https://example.org',
2478+
'fake-client-id',
2479+
'fake-client-secret',
2480+
);
2481+
2482+
// Call the verifyLogoutToken method without a token
2483+
$this->expectException(OpenIDConnectClientException::class);
2484+
$client->verifyLogoutToken();
2485+
}
2486+
2487+
public function testVerifyLogoutTokenFailsWithInvalidClaims()
2488+
{
2489+
// Create a new RSA key pair for signing the logout token
2490+
$private_key = JWKFactory::createRSAKey(
2491+
4096,
2492+
[
2493+
'alg' => 'RS256',
2494+
'use' => 'sig'
2495+
]
2496+
);
2497+
$public_key = $private_key->toPublic();
2498+
2499+
// Generate random values for the logout token
2500+
$kid = bin2hex(random_bytes(6));
2501+
$jti = bin2hex(random_bytes(6));
2502+
2503+
// Create claims for the logout token
2504+
// Missing 'sub' and 'sid' claims
2505+
$claims = [
2506+
'exp' => time() + 60,
2507+
'iat' => time(),
2508+
'iss' => 'https://example.org',
2509+
'aud' => 'fake-client-id',
2510+
'jti' => $jti,
2511+
'events' => json_decode('{"http://schemas.openid.net/event/backchannel-logout": {}}')
2512+
];
2513+
2514+
// Create logout token
2515+
$logoutToken = $this->signClaims($claims, $private_key, 'RS256', ['kid' => $kid]);
2516+
2517+
// List of JWKs to be returned by the JWKS endpoint
2518+
$jwks = [[
2519+
'kid' => $kid,
2520+
...$public_key->jsonSerialize()
2521+
]];
2522+
2523+
// Mock the OpenIDConnectClient, only mocking the fetchURL method
2524+
$client = $this->getMockBuilder(OpenIDConnectClient::class)
2525+
->setConstructorArgs([
2526+
'https://example.org',
2527+
'fake-client-id',
2528+
'fake-client-secret',
2529+
])
2530+
->onlyMethods(['fetchURL'])
2531+
->getMock();
2532+
2533+
$client->expects($this->any())
2534+
->method('fetchURL')
2535+
->with($this->anything())
2536+
->will($this->returnCallback(function (string$url, ?string $post_body = null, array $headers = []) use ($jwks) {
2537+
switch ($url) {
2538+
case 'https://example.org/.well-known/openid-configuration':
2539+
return new Response(200, 'application/json', json_encode([
2540+
'issuer' => 'https://example.org/',
2541+
'authorization_endpoint' => 'https://example.org/authorize',
2542+
'token_endpoint' => 'https://example.org/token',
2543+
'userinfo_endpoint' => 'https://example.org/userinfo',
2544+
'jwks_uri' => 'https://example.org/jwks',
2545+
'response_types_supported' => ['code', 'id_token'],
2546+
'subject_types_supported' => ['public'],
2547+
'id_token_signing_alg_values_supported' => ['RS256'],
2548+
]));
2549+
case 'https://example.org/jwks':
2550+
return new Response(200, 'application/json', json_encode([
2551+
'keys' => $jwks
2552+
]));
2553+
default:
2554+
throw new Exception("Unexpected request: $url");
2555+
}
2556+
}));
2557+
2558+
2559+
// Call the verifyLogoutToken
2560+
$_REQUEST['logout_token'] = $logoutToken;
2561+
$this->assertFalse($client->verifyLogoutToken());
2562+
}
2563+
2564+
public function testVerifyLogoutTokenSigned()
2565+
{
2566+
// Create a new RSA key pair for signing the logout token
2567+
$private_key = JWKFactory::createRSAKey(
2568+
4096,
2569+
[
2570+
'alg' => 'RS256',
2571+
'use' => 'sig'
2572+
]
2573+
);
2574+
$public_key = $private_key->toPublic();
2575+
2576+
// Generate random values for the logout token
2577+
$kid = bin2hex(random_bytes(6));
2578+
$jti = bin2hex(random_bytes(6));
2579+
$sub = $this->faker->uuid();
2580+
$sid = $this->faker->uuid();
2581+
2582+
// Create claims for the logout token
2583+
$claims = [
2584+
'exp' => time() + 60,
2585+
'iat' => time(),
2586+
'iss' => 'https://example.org',
2587+
'aud' => 'fake-client-id',
2588+
'sub' => $sub,
2589+
'sid' => $sid,
2590+
'jti' => $jti,
2591+
'events' => json_decode('{"http://schemas.openid.net/event/backchannel-logout": {}}')
2592+
];
2593+
2594+
// Create logout token
2595+
$logoutToken = $this->signClaims($claims, $private_key, 'RS256', ['kid' => $kid]);
2596+
2597+
// List of JWKs to be returned by the JWKS endpoint
2598+
$jwks = [[
2599+
'kid' => $kid,
2600+
...$public_key->jsonSerialize()
2601+
]];
2602+
2603+
// Mock the OpenIDConnectClient, only mocking the fetchURL method
2604+
$client = $this->getMockBuilder(OpenIDConnectClient::class)
2605+
->setConstructorArgs([
2606+
'https://example.org',
2607+
'fake-client-id',
2608+
'fake-client-secret',
2609+
])
2610+
->onlyMethods(['fetchURL'])
2611+
->getMock();
2612+
2613+
$client->expects($this->any())
2614+
->method('fetchURL')
2615+
->with($this->anything())
2616+
->will($this->returnCallback(function (string$url, ?string $post_body = null, array $headers = []) use ($jwks) {
2617+
switch ($url) {
2618+
case 'https://example.org/.well-known/openid-configuration':
2619+
return new Response(200, 'application/json', json_encode([
2620+
'issuer' => 'https://example.org/',
2621+
'authorization_endpoint' => 'https://example.org/authorize',
2622+
'token_endpoint' => 'https://example.org/token',
2623+
'userinfo_endpoint' => 'https://example.org/userinfo',
2624+
'jwks_uri' => 'https://example.org/jwks',
2625+
'response_types_supported' => ['code', 'id_token'],
2626+
'subject_types_supported' => ['public'],
2627+
'id_token_signing_alg_values_supported' => ['RS256'],
2628+
]));
2629+
case 'https://example.org/jwks':
2630+
return new Response(200, 'application/json', json_encode([
2631+
'keys' => $jwks
2632+
]));
2633+
default:
2634+
throw new Exception("Unexpected request: $url");
2635+
}
2636+
}));
2637+
2638+
2639+
// Call the verifyLogoutToken
2640+
$_REQUEST['logout_token'] = $logoutToken;
2641+
$this->assertTrue($client->verifyLogoutToken());
2642+
2643+
// Get claims
2644+
$this->assertEquals($sub, $client->getVerifiedClaims('sub'));
2645+
$this->assertEquals($sid, $client->getVerifiedClaims('sid'));
2646+
$this->assertEquals($jti, $client->getVerifiedClaims('jti'));
2647+
$this->assertEquals('https://example.org', $client->getVerifiedClaims('iss'));
2648+
2649+
// Get sid
2650+
$this->assertEquals($sid, $client->getSidFromBackChannel());
2651+
2652+
// Get sub
2653+
$this->assertEquals($sub, $client->getSubjectFromBackChannel());
2654+
2655+
// Get jti
2656+
$this->assertEquals($jti, $client->getJtiFromBackChannel());
2657+
}
2658+
public function testVerifyLogoutTokenSignedEncrypted()
2659+
{
2660+
// Create a new RSA key pair for signing the logout token
2661+
$private_key = JWKFactory::createRSAKey(
2662+
4096,
2663+
[
2664+
'alg' => 'RS256',
2665+
'use' => 'sig'
2666+
]
2667+
);
2668+
$public_key = $private_key->toPublic();
2669+
2670+
// Create a new RSA key pair for encrypting the user info response
2671+
$encryption_key = JWKFactory::createRSAKey(
2672+
4096,
2673+
[
2674+
'alg' => 'RSA-OAEP-256',
2675+
'use' => 'enc'
2676+
]
2677+
);
2678+
2679+
// Generate random values for the logout token
2680+
$kid = bin2hex(random_bytes(6));
2681+
$jti = bin2hex(random_bytes(6));
2682+
$sub = $this->faker->uuid();
2683+
$sid = $this->faker->uuid();
2684+
2685+
// Create claims for the logout token
2686+
$claims = [
2687+
'exp' => time() + 60,
2688+
'iat' => time(),
2689+
'iss' => 'https://example.org',
2690+
'aud' => 'fake-client-id',
2691+
'sub' => $sub,
2692+
'sid' => $sid,
2693+
'jti' => $jti,
2694+
'events' => json_decode('{"http://schemas.openid.net/event/backchannel-logout": {}}')
2695+
];
2696+
2697+
// Create logout token
2698+
$logoutToken = $this->signClaims($claims, $private_key, 'RS256', ['kid' => $kid]);
2699+
2700+
$keyEncryptionAlgorithmManager = new AlgorithmManager([
2701+
new RSAOAEP256(),
2702+
]);
2703+
$contentEncryptionAlgorithmManager = new AlgorithmManager([
2704+
new A128CBCHS256(),
2705+
]);
2706+
2707+
$jweBuilder = new JWEBuilder(
2708+
$keyEncryptionAlgorithmManager,
2709+
$contentEncryptionAlgorithmManager,
2710+
);
2711+
2712+
$jwe = $jweBuilder
2713+
->create()
2714+
->withPayload($logoutToken)
2715+
->withSharedProtectedHeader([
2716+
'alg' => 'RSA-OAEP-256',
2717+
'enc' => 'A128CBC-HS256',
2718+
'cty' => 'JWT',
2719+
])
2720+
->addRecipient($encryption_key->toPublic())
2721+
->build();
2722+
2723+
$serializer = new \Jose\Component\Encryption\Serializer\CompactSerializer();
2724+
2725+
$encryptedLogoutToken = $serializer->serialize($jwe, 0);
2726+
2727+
2728+
// List of JWKs to be returned by the JWKS endpoint
2729+
$jwks = [[
2730+
'kid' => $kid,
2731+
...$public_key->jsonSerialize()
2732+
]];
2733+
2734+
// Mock the OpenIDConnectClient, only mocking the fetchURL method
2735+
$client = $this->getMockBuilder(OpenIDConnectClient::class)
2736+
->setConstructorArgs([
2737+
'https://example.org',
2738+
'fake-client-id',
2739+
'fake-client-secret',
2740+
])
2741+
->onlyMethods(['fetchURL', 'handleJweResponse'])
2742+
->getMock();
2743+
2744+
$client->expects($this->any())
2745+
->method('fetchURL')
2746+
->with($this->anything())
2747+
->will($this->returnCallback(function (string$url, ?string $post_body = null, array $headers = []) use ($jwks) {
2748+
switch ($url) {
2749+
case 'https://example.org/.well-known/openid-configuration':
2750+
return new Response(200, 'application/json', json_encode([
2751+
'issuer' => 'https://example.org/',
2752+
'authorization_endpoint' => 'https://example.org/authorize',
2753+
'token_endpoint' => 'https://example.org/token',
2754+
'userinfo_endpoint' => 'https://example.org/userinfo',
2755+
'jwks_uri' => 'https://example.org/jwks',
2756+
'response_types_supported' => ['code', 'id_token'],
2757+
'subject_types_supported' => ['public'],
2758+
'id_token_signing_alg_values_supported' => ['RS256'],
2759+
]));
2760+
case 'https://example.org/jwks':
2761+
return new Response(200, 'application/json', json_encode([
2762+
'keys' => $jwks
2763+
]));
2764+
default:
2765+
throw new Exception("Unexpected request: $url");
2766+
}
2767+
}));
2768+
2769+
$client->expects($this->any())
2770+
->method('handleJweResponse')
2771+
->with($encryptedLogoutToken)
2772+
->willReturn($logoutToken);
2773+
2774+
2775+
// Call the verifyLogoutToken
2776+
$_REQUEST['logout_token'] = $encryptedLogoutToken;
2777+
$this->assertTrue($client->verifyLogoutToken());
2778+
}
2779+
24742780

24752781
}

0 commit comments

Comments
 (0)