'application/json' ], $test_jwks ) ] ); $jwks_formatted = $jwks->call()->getKeys( uniqid() ); $this->assertCount( 2, $jwks_formatted ); $this->assertArrayHasKey( '__test_kid_1__', $jwks_formatted ); $pem_parts_1 = explode( PHP_EOL, $jwks_formatted['__test_kid_1__'] ); $this->assertCount( 4, $pem_parts_1 ); $this->assertEquals( '-----BEGIN CERTIFICATE-----', $pem_parts_1[0] ); $this->assertEquals( '__test_x5c_1__', $pem_parts_1[1] ); $this->assertEquals( '-----END CERTIFICATE-----', $pem_parts_1[2] ); $pem_parts_2 = explode( PHP_EOL, $jwks_formatted['__test_kid_2__'] ); $this->assertCount( 4, $pem_parts_2 ); $this->assertEquals( '-----BEGIN CERTIFICATE-----', $pem_parts_2[0] ); $this->assertEquals( '__test_x5c_2__', $pem_parts_2[1] ); $this->assertEquals( '-----END CERTIFICATE-----', $pem_parts_2[2] ); } public function testThatGetKeysEmptyJwksReturnsEmptyArray() { $jwks = new MockJwks( [ new Response( 200, [ 'Content-Type' => 'application/json' ], '{}' ), new Response( 200, [ 'Content-Type' => 'application/json' ], '{"keys":[]}' ), ] ); $jwks_formatted = $jwks->call()->getKeys( uniqid() ); $this->assertEquals( [], $jwks_formatted ); $jwks_formatted = $jwks->call()->getKeys( uniqid() ); $this->assertEquals( [], $jwks_formatted ); } public function testThatGetKeysUsesCache() { $jwks_body_1 = '{"keys":[{"kid":"abc","x5c":["123"]}]}'; $jwks_body_2 = '{"keys":[{"kid":"def","x5c":["456"]}]}'; $jwks = new MockJwks( [ new Response( 200, [ 'Content-Type' => 'application/json' ], $jwks_body_1 ), new Response( 200, [ 'Content-Type' => 'application/json' ], $jwks_body_2 ), ], [ 'cache' => new ArrayCachePool() ] ); $jwks_formatted_1 = $jwks->call()->getKeys( '__test_url__' ); $this->assertArrayHasKey( 'abc', $jwks_formatted_1 ); $this->assertContains( '123', $jwks_formatted_1['abc'] ); $jwks_formatted_2 = $jwks->call()->getKeys( '__test_url__' ); $this->assertEquals( $jwks_formatted_1, $jwks_formatted_2 ); $jwks_formatted_2 = $jwks->call()->getKeys( '__test_url_2__' ); $this->assertArrayHasKey( 'def', $jwks_formatted_2 ); $this->assertContains( '456', $jwks_formatted_2['def'] ); } public function testThatGetKeyBreaksCache() { $jwks_body_1 = '{"keys":[{"kid":"__kid_1__","x5c":["__x5c_1__"]}]}'; $jwks_body_2 = '{"keys":[{"kid":"__kid_1__","x5c":["__x5c_1__"]},{"kid":"__kid_2__","x5c":["__x5c_2__"]}]}'; $jwks = new MockJwks( [ new Response( 200, [ 'Content-Type' => 'application/json' ], $jwks_body_1 ), new Response( 200, [ 'Content-Type' => 'application/json' ], $jwks_body_2 ), ], [ 'cache' => new ArrayCachePool() ] ); $jwks_formatted_1 = $jwks->call()->getKeys( '__test_url__' ); $this->assertArrayHasKey( '__kid_1__', $jwks_formatted_1 ); $this->assertArrayNotHasKey( '__kid_2__', $jwks_formatted_1 ); $jwks_formatted_2 = $jwks->call()->getKeys( '__test_url__' ); $this->assertEquals( $jwks_formatted_1, $jwks_formatted_2 ); $jwks_formatted_3 = $jwks->call()->getKeys( '__test_url__', false ); $this->assertArrayHasKey( '__kid_1__', $jwks_formatted_3 ); $this->assertArrayHasKey( '__kid_2__', $jwks_formatted_3 ); } public function testThatGetKeysUsesOptionsUrl() { $jwks_body = '{"keys":[{"kid":"__kid_1__","x5c":["__x5c_1__"]}]}'; $jwks = new MockJwks( [ new Response( 200, [ 'Content-Type' => 'application/json' ], $jwks_body ) ], [ 'cache' => new ArrayCachePool() ], [ 'base_uri' => '__test_jwks_url__' ] ); $jwks->call()->getKeys(); $this->assertEquals( '__test_jwks_url__', $jwks->getHistoryUrl() ); } public function testThatGetKeyGetsSpecificKid() { $cache = new ArrayCachePool(); $jwks = new JWKFetcher( $cache, [ 'base_uri' => '__test_jwks_url__' ] ); $cache->set(md5('__test_jwks_url__'), ['__test_kid_1__' => '__test_x5c_1__']); $this->assertEquals('__test_x5c_1__', $jwks->getKey('__test_kid_1__')); } public function testThatGetKeyBreaksCacheIsKidMissing() { $cache = new ArrayCachePool(); $jwks_body = '{"keys":[{"kid":"__test_kid_2__","x5c":["__test_x5c_2__"]}]}'; $jwks = new MockJwks( [ new Response( 200, [ 'Content-Type' => 'application/json' ], $jwks_body ) ], [ 'cache' => $cache ], [ 'base_uri' => '__test_jwks_url__' ] ); $cache->set(md5('__test_jwks_url__'), ['__test_kid_1__' => '__test_x5c_1__']); $this->assertContains('__test_x5c_2__', $jwks->call()->getKey('__test_kid_2__')); } public function testThatEmptyUrlReturnsEmptyKeys() { $jwks_formatted_1 = (new JWKFetcher())->getKeys(); $this->assertEquals( [], $jwks_formatted_1 ); } public function testThatTtlChanges() { $jwks_body = '{"keys":[{"kid":"__test_kid_2__","x5c":["__test_x5c_2__"]}]}'; $jwks = new MockJwks( // [ new Response( 200, [ 'Content-Type' => 'application/json' ], $jwks_body ) ], // [ 'cache' => new ArrayCachePool() ], // [ 'base_uri' => '__test_jwks_url__' ] ); // Ensure TTL is assigned a recommended default value of 10 minutes. $this->assertEquals(JWKFetcher::CACHE_TTL, $jwks->call()->getTtl()); // Ensure TTL is assigned correctly; 60 seconds is the minimum. $jwks->call()->setTtl(60); $this->assertEquals(60, $jwks->call()->getTtl()); // Ensure assigning a TTL of less than 60 seconds throws an exception. $this->expectException(CoreException::class); $jwks->call()->setTtl(30); } public function testThatCacheMutates() { $jwks_body = '{"keys":[{"kid":"__kid_1__","x5c":["__x5c_1__"]},{"kid":"__kid_2__","x5c":["__x5c_2__"]}]}'; $jwks_body_modified = ['__kid_3__' => '__x5c_3__', '__kid_4__' => '__x5c_4__']; $jwks = new MockJwks( [ new Response( 200, [ 'Content-Type' => 'application/json' ], $jwks_body ), new Response( 200, [ 'Content-Type' => 'application/json' ], $jwks_body ), ], [ 'cache' => new ArrayCachePool() ], [ 'base_uri' => '__test_jwks_url__' ] ); $jwks->call()->getKeys('__test_jwks_url__', false); // getCacheKey MUST return an MD5 hash of provided JWKS URL. $this->assertEquals(md5('__test_jwks_url__'), $jwks->call()->getCacheKey('__test_jwks_url__')); // Requests for invalid/missing kids MUST return NULL. $this->assertEquals(null, $jwks->call()->getKey('__test_missing_kid__')); // Requesting a valid kid MUST return an ARRAY containing the x5c $this->assertContains('__x5c_1__', $jwks->call()->getKey('__kid_1__')); // Pulling directly from cache will return same results as getKeys() $this->assertArrayHasKey('__kid_1__', $jwks->call()->getCacheEntry('__test_jwks_url__')); // Overwrite an existing JWK in the cache. $jwks->call()->setCacheEntry('__test_jwks_url__', $jwks_body_modified); // Inject a new JWK into the cache. $jwks->call()->setCacheEntry('__test_jwks_url_2__', $jwks_body_modified); // Ensure the cache was updated successfully $this->assertArrayHasKey('__kid_3__', $jwks->call()->getCacheEntry('__test_jwks_url__')); // Purge the cache of keys relating to our test url $jwks->call()->removeCacheEntry('__test_jwks_url__'); // Ensure cache for our test url is empty $this->assertEmpty($jwks->call()->getCacheEntry('__test_jwks_url__')); // Ensure the cache still contains content for __test_jwks_url_2__ $this->assertArrayHasKey('__kid_3__', $jwks->call()->getCacheEntry('__test_jwks_url_2__')); // Purge the cache of all keys $jwks->call()->clearCache(); // Ensure all JWKs are cleared from the cache. $this->assertEmpty($jwks->call()->getCacheEntry('__test_jwks_url__')); $this->assertEmpty($jwks->call()->getCacheEntry('__test_jwks_url_2__')); } }