diff --git a/DataCollector/SecurityDataCollector.php b/DataCollector/SecurityDataCollector.php index aa6e8d9a..cd0e41ac 100644 --- a/DataCollector/SecurityDataCollector.php +++ b/DataCollector/SecurityDataCollector.php @@ -101,10 +101,12 @@ public function collect(Request $request, Response $response, ?\Throwable $excep } $logoutUrl = null; - try { - $logoutUrl = $this->logoutUrlGenerator?->getLogoutPath(); - } catch (\Exception) { - // fail silently when the logout URL cannot be generated + if ($this->logoutUrlGenerator && method_exists($token, 'getFirewallName')) { + try { + $logoutUrl = $this->logoutUrlGenerator->getLogoutPath($token->getFirewallName()); + } catch (\Exception) { + // fail silently when the logout URL cannot be generated + } } $this->data = [ diff --git a/DependencyInjection/Security/AccessToken/CasTokenHandlerFactory.php b/DependencyInjection/Security/AccessToken/CasTokenHandlerFactory.php index a0c2ca04..63ff5db1 100644 --- a/DependencyInjection/Security/AccessToken/CasTokenHandlerFactory.php +++ b/DependencyInjection/Security/AccessToken/CasTokenHandlerFactory.php @@ -42,7 +42,6 @@ public function addConfiguration(NodeBuilder $node): void { $node ->arrayNode($this->getKey()) - ->fixXmlConfig($this->getKey()) ->children() ->scalarNode('validation_url') ->info('CAS server validation URL') diff --git a/DependencyInjection/Security/AccessToken/OidcTokenHandlerFactory.php b/DependencyInjection/Security/AccessToken/OidcTokenHandlerFactory.php index de53d5e8..0bfd4793 100644 --- a/DependencyInjection/Security/AccessToken/OidcTokenHandlerFactory.php +++ b/DependencyInjection/Security/AccessToken/OidcTokenHandlerFactory.php @@ -90,7 +90,8 @@ public function addConfiguration(NodeBuilder $node): void { $node ->arrayNode($this->getKey()) - ->fixXmlConfig($this->getKey()) + ->fixXmlConfig('issuer') + ->fixXmlConfig('algorithm') ->validate() ->ifTrue(static fn ($v) => !isset($v['algorithm']) && !isset($v['algorithms'])) ->thenInvalid('You must set either "algorithm" or "algorithms".') @@ -172,6 +173,7 @@ public function addConfiguration(NodeBuilder $node): void ->info('JSON-encoded JWKSet used to sign the token (must contain a list of valid public keys).') ->end() ->arrayNode('encryption') + ->fixXmlConfig('algorithm') ->canBeEnabled() ->children() ->booleanNode('enforce') diff --git a/DependencyInjection/Security/AccessToken/OidcUserInfoTokenHandlerFactory.php b/DependencyInjection/Security/AccessToken/OidcUserInfoTokenHandlerFactory.php index c6308ff3..0b69d4e7 100644 --- a/DependencyInjection/Security/AccessToken/OidcUserInfoTokenHandlerFactory.php +++ b/DependencyInjection/Security/AccessToken/OidcUserInfoTokenHandlerFactory.php @@ -63,7 +63,6 @@ public function addConfiguration(NodeBuilder $node): void { $node ->arrayNode($this->getKey()) - ->fixXmlConfig($this->getKey()) ->beforeNormalization() ->ifString() ->then(fn ($v) => ['claim' => 'sub', 'base_uri' => $v]) diff --git a/DependencyInjection/Security/Factory/AccessTokenFactory.php b/DependencyInjection/Security/Factory/AccessTokenFactory.php index 371049c8..f5aa4711 100644 --- a/DependencyInjection/Security/Factory/AccessTokenFactory.php +++ b/DependencyInjection/Security/Factory/AccessTokenFactory.php @@ -43,11 +43,10 @@ public function addConfiguration(NodeDefinition $node): void { parent::addConfiguration($node); - $builder = $node->children(); + $builder = $node->fixXmlConfig('token_extractor')->children(); $builder ->scalarNode('realm')->defaultNull()->end() ->arrayNode('token_extractors') - ->fixXmlConfig('token_extractors') ->beforeNormalization() ->ifString() ->then(fn ($v) => [$v]) diff --git a/DependencyInjection/Security/Factory/RememberMeFactory.php b/DependencyInjection/Security/Factory/RememberMeFactory.php index c62c01d4..57308068 100644 --- a/DependencyInjection/Security/Factory/RememberMeFactory.php +++ b/DependencyInjection/Security/Factory/RememberMeFactory.php @@ -126,6 +126,7 @@ public function getKey(): string public function addConfiguration(NodeDefinition $node): void { $builder = $node + ->fixXmlConfig('signature_property', 'signature_properties') ->fixXmlConfig('user_provider') ->children() ; diff --git a/Resources/config/schema/security-1.0.xsd b/Resources/config/schema/security-1.0.xsd index 692321a4..537119d8 100644 --- a/Resources/config/schema/security-1.0.xsd +++ b/Resources/config/schema/security-1.0.xsd @@ -9,11 +9,8 @@ - - - + - @@ -21,28 +18,10 @@ - + - - - - - - - - - - - - - - - - - - @@ -55,7 +34,6 @@ - @@ -196,12 +174,16 @@ + + + + @@ -304,6 +286,7 @@ + @@ -321,59 +304,66 @@ - - + + - + - + + + + + + + + + + + + + + - - + + - - + + + - + - - - - - - - - - - - - + + + + + @@ -442,7 +432,7 @@ - + @@ -452,6 +442,7 @@ + diff --git a/Tests/DataCollector/SecurityDataCollectorTest.php b/Tests/DataCollector/SecurityDataCollectorTest.php index 5528c9b7..df6bcd0c 100644 --- a/Tests/DataCollector/SecurityDataCollectorTest.php +++ b/Tests/DataCollector/SecurityDataCollectorTest.php @@ -41,7 +41,7 @@ class SecurityDataCollectorTest extends TestCase { public function testCollectWhenSecurityIsDisabled() { - $collector = new SecurityDataCollector(null, null, null, null, null, null, true); + $collector = new SecurityDataCollector(null, null, null, null, null, null); $collector->collect(new Request(), new Response()); $this->assertSame('security', $collector->getName()); @@ -61,7 +61,7 @@ public function testCollectWhenSecurityIsDisabled() public function testCollectWhenAuthenticationTokenIsNull() { $tokenStorage = new TokenStorage(); - $collector = new SecurityDataCollector($tokenStorage, $this->getRoleHierarchy(), null, null, null, null, true); + $collector = new SecurityDataCollector($tokenStorage, $this->getRoleHierarchy(), null, null, null, null); $collector->collect(new Request(), new Response()); $this->assertTrue($collector->isEnabled()); @@ -83,7 +83,7 @@ public function testCollectAuthenticationTokenAndRoles(array $roles, array $norm $tokenStorage = new TokenStorage(); $tokenStorage->setToken(new UsernamePasswordToken(new InMemoryUser('hhamon', 'P4$$w0rD', $roles), 'provider', $roles)); - $collector = new SecurityDataCollector($tokenStorage, $this->getRoleHierarchy(), null, null, null, null, true); + $collector = new SecurityDataCollector($tokenStorage, $this->getRoleHierarchy(), null, null, null, null); $collector->collect(new Request(), new Response()); $collector->lateCollect(); @@ -106,7 +106,7 @@ public function testCollectSwitchUserToken() $tokenStorage = new TokenStorage(); $tokenStorage->setToken(new SwitchUserToken(new InMemoryUser('hhamon', 'P4$$w0rD', ['ROLE_USER']), 'provider', ['ROLE_USER'], $adminToken)); - $collector = new SecurityDataCollector($tokenStorage, $this->getRoleHierarchy(), null, null, null, null, true); + $collector = new SecurityDataCollector($tokenStorage, $this->getRoleHierarchy(), null, null, null, null); $collector->collect(new Request(), new Response()); $collector->lateCollect(); @@ -136,7 +136,7 @@ public function testGetFirewall() ->with($request) ->willReturn($firewallConfig); - $collector = new SecurityDataCollector(null, null, null, null, $firewallMap, new TraceableFirewallListener($firewallMap, new EventDispatcher(), new LogoutUrlGenerator()), true); + $collector = new SecurityDataCollector(null, null, null, null, $firewallMap, new TraceableFirewallListener($firewallMap, new EventDispatcher(), new LogoutUrlGenerator())); $collector->collect($request, new Response()); $collector->lateCollect(); $collected = $collector->getFirewall(); @@ -160,7 +160,7 @@ public function testGetFirewallReturnsNull() $response = new Response(); // Don't inject any firewall map - $collector = new SecurityDataCollector(null, null, null, null, null, null, true); + $collector = new SecurityDataCollector(null, null, null, null, null, null); $collector->collect($request, $response); $this->assertNull($collector->getFirewall()); @@ -170,7 +170,7 @@ public function testGetFirewallReturnsNull() ->disableOriginalConstructor() ->getMock(); - $collector = new SecurityDataCollector(null, null, null, null, $firewallMap, new TraceableFirewallListener($firewallMap, new EventDispatcher(), new LogoutUrlGenerator()), true); + $collector = new SecurityDataCollector(null, null, null, null, $firewallMap, new TraceableFirewallListener($firewallMap, new EventDispatcher(), new LogoutUrlGenerator())); $collector->collect($request, $response); $this->assertNull($collector->getFirewall()); @@ -180,7 +180,7 @@ public function testGetFirewallReturnsNull() ->disableOriginalConstructor() ->getMock(); - $collector = new SecurityDataCollector(null, null, null, null, $firewallMap, new TraceableFirewallListener($firewallMap, new EventDispatcher(), new LogoutUrlGenerator()), true); + $collector = new SecurityDataCollector(null, null, null, null, $firewallMap, new TraceableFirewallListener($firewallMap, new EventDispatcher(), new LogoutUrlGenerator())); $collector->collect($request, $response); $this->assertNull($collector->getFirewall()); } @@ -214,7 +214,7 @@ public function testGetListeners() $firewall = new TraceableFirewallListener($firewallMap, new EventDispatcher(), new LogoutUrlGenerator()); $firewall->onKernelRequest($event); - $collector = new SecurityDataCollector(null, null, null, null, $firewallMap, $firewall, true); + $collector = new SecurityDataCollector(null, null, null, null, $firewallMap, $firewall); $collector->collect($request, $response); $this->assertNotEmpty($collected = $collector->getListeners()[0]); @@ -261,7 +261,7 @@ public function dispatch(object $event, ?string $eventName = null): object ], ]]); - $dataCollector = new SecurityDataCollector(null, null, null, $accessDecisionManager, null, null, true); + $dataCollector = new SecurityDataCollector(null, null, null, $accessDecisionManager, null, null); $dataCollector->collect(new Request(), new Response()); @@ -349,7 +349,7 @@ public function dispatch(object $event, ?string $eventName = null): object ], ]); - $dataCollector = new SecurityDataCollector(null, null, null, $accessDecisionManager, null, null, true); + $dataCollector = new SecurityDataCollector(null, null, null, $accessDecisionManager, null, null); $dataCollector->collect(new Request(), new Response()); @@ -421,7 +421,7 @@ public function testGetVotersIfAccessDecisionManagerHasNoVoters() 'voterDetails' => [], ]]); - $dataCollector = new SecurityDataCollector(null, null, null, $accessDecisionManager, null, null, true); + $dataCollector = new SecurityDataCollector(null, null, null, $accessDecisionManager, null, null); $dataCollector->collect(new Request(), new Response()); diff --git a/Tests/DependencyInjection/CompleteConfigurationTestCase.php b/Tests/DependencyInjection/CompleteConfigurationTestCase.php index 04fba9fe..dcb67011 100644 --- a/Tests/DependencyInjection/CompleteConfigurationTestCase.php +++ b/Tests/DependencyInjection/CompleteConfigurationTestCase.php @@ -726,6 +726,41 @@ public function testFirewallPatterns() $this->assertSame('(?:^/register$|^/documentation$)', $container->getDefinition($requestMatcherId)->getArgument(0)); } + public function testAccessTokenOidc() + { + $container = $this->getContainer('access_token_oidc'); + + $this->assertTrue($container->hasDefinition('security.authenticator.access_token.firewall1')); + $this->assertTrue($container->hasDefinition('security.access_token_handler.firewall1')); + + $def = $container->getDefinition('security.access_token_handler.firewall1'); + $this->assertSame('audience', $def->getArgument(2)); + $this->assertSame(['https://www.example.com'], $def->getArgument(3)); + $this->assertSame('sub', $def->getArgument(4)); + } + + public function testAccessTokenOidcWithEncryption() + { + $container = $this->getContainer('access_token_oidc_encryption'); + + $this->assertTrue($container->hasDefinition('security.authenticator.access_token.firewall1')); + $this->assertTrue($container->hasDefinition('security.access_token_handler.firewall1')); + + $def = $container->getDefinition('security.access_token_handler.firewall1'); + $this->assertSame(['RS256'], $def->getArgument(0)->getArgument(0)); + } + + public function testAccessTokenOidcUserInfoWithDiscovery() + { + if ('xml' === $this->getFileExtension()) { + $this->markTestSkipped('OIDC user info discovery is not supported by the XML schema.'); + } + $container = $this->getContainer('access_token_oidc_user_info_discovery'); + + $this->assertTrue($container->hasDefinition('security.authenticator.access_token.firewall1')); + $this->assertTrue($container->hasDefinition('security.access_token_handler.firewall1')); + } + protected function getContainer($file) { $file .= '.'.$this->getFileExtension(); diff --git a/Tests/DependencyInjection/Fixtures/php/access_token_oidc.php b/Tests/DependencyInjection/Fixtures/php/access_token_oidc.php new file mode 100644 index 00000000..b4631c4b --- /dev/null +++ b/Tests/DependencyInjection/Fixtures/php/access_token_oidc.php @@ -0,0 +1,25 @@ +loadFromExtension('security', [ + 'providers' => [ + 'default' => [ + 'memory' => null, + ], + ], + 'firewalls' => [ + 'firewall1' => [ + 'provider' => 'default', + 'access_token' => [ + 'token_handler' => [ + 'oidc' => [ + 'algorithms' => ['RS256'], + 'issuers' => ['https://www.example.com'], + 'audience' => 'audience', + 'keyset' => '{"keys":[{"kty":"RSA","n":"abc","e":"AQAB"}]}', + ], + ], + ], + ], + ], +]); + diff --git a/Tests/DependencyInjection/Fixtures/php/access_token_oidc_encryption.php b/Tests/DependencyInjection/Fixtures/php/access_token_oidc_encryption.php new file mode 100644 index 00000000..65bb9479 --- /dev/null +++ b/Tests/DependencyInjection/Fixtures/php/access_token_oidc_encryption.php @@ -0,0 +1,30 @@ +loadFromExtension('security', [ + 'providers' => [ + 'default' => [ + 'memory' => null, + ], + ], + 'firewalls' => [ + 'firewall1' => [ + 'provider' => 'default', + 'access_token' => [ + 'token_handler' => [ + 'oidc' => [ + 'algorithms' => ['RS256'], + 'issuers' => ['https://www.example.com'], + 'audience' => 'audience', + 'keyset' => '{"keys":[{"kty":"RSA","n":"abc","e":"AQAB"}]}', + 'encryption' => [ + 'enabled' => true, + 'keyset' => '{"keys":[{"kty":"RSA","n":"abc","e":"AQAB","d":"def"}]}', + 'algorithms' => ['RSA-OAEP'], + ], + ], + ], + ], + ], + ], +]); + diff --git a/Tests/DependencyInjection/Fixtures/php/access_token_oidc_user_info_discovery.php b/Tests/DependencyInjection/Fixtures/php/access_token_oidc_user_info_discovery.php new file mode 100644 index 00000000..f01b7263 --- /dev/null +++ b/Tests/DependencyInjection/Fixtures/php/access_token_oidc_user_info_discovery.php @@ -0,0 +1,27 @@ +loadFromExtension('security', [ + 'providers' => [ + 'default' => [ + 'memory' => null, + ], + ], + 'firewalls' => [ + 'firewall1' => [ + 'provider' => 'default', + 'access_token' => [ + 'token_handler' => [ + 'oidc_user_info' => [ + 'base_uri' => 'https://www.example.com/realms/demo/protocol/openid-connect/userinfo', + 'discovery' => [ + 'cache' => [ + 'id' => 'oidc_cache', + ], + ], + ], + ], + ], + ], + ], +]); + diff --git a/Tests/DependencyInjection/Fixtures/xml/access_token_oidc.xml b/Tests/DependencyInjection/Fixtures/xml/access_token_oidc.xml new file mode 100644 index 00000000..2b197ae4 --- /dev/null +++ b/Tests/DependencyInjection/Fixtures/xml/access_token_oidc.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + https://www.example.com + RS256 + + + + + + + diff --git a/Tests/DependencyInjection/Fixtures/xml/access_token_oidc_encryption.xml b/Tests/DependencyInjection/Fixtures/xml/access_token_oidc_encryption.xml new file mode 100644 index 00000000..d21da9ca --- /dev/null +++ b/Tests/DependencyInjection/Fixtures/xml/access_token_oidc_encryption.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + https://www.example.com + RS256 + + RSA-OAEP + + + + + + + + diff --git a/Tests/DependencyInjection/Fixtures/xml/access_token_oidc_user_info_discovery.xml b/Tests/DependencyInjection/Fixtures/xml/access_token_oidc_user_info_discovery.xml new file mode 100644 index 00000000..91874379 --- /dev/null +++ b/Tests/DependencyInjection/Fixtures/xml/access_token_oidc_user_info_discovery.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/DependencyInjection/Fixtures/xml/argon2i_hasher.xml b/Tests/DependencyInjection/Fixtures/xml/argon2i_hasher.xml index 3dc2c685..133de4f8 100644 --- a/Tests/DependencyInjection/Fixtures/xml/argon2i_hasher.xml +++ b/Tests/DependencyInjection/Fixtures/xml/argon2i_hasher.xml @@ -13,7 +13,7 @@ - + diff --git a/Tests/DependencyInjection/Fixtures/xml/bcrypt_hasher.xml b/Tests/DependencyInjection/Fixtures/xml/bcrypt_hasher.xml index d4c5d3de..0e790e25 100644 --- a/Tests/DependencyInjection/Fixtures/xml/bcrypt_hasher.xml +++ b/Tests/DependencyInjection/Fixtures/xml/bcrypt_hasher.xml @@ -13,7 +13,7 @@ - + diff --git a/Tests/DependencyInjection/Fixtures/xml/container1.xml b/Tests/DependencyInjection/Fixtures/xml/container1.xml index f54c5064..fb5080de 100644 --- a/Tests/DependencyInjection/Fixtures/xml/container1.xml +++ b/Tests/DependencyInjection/Fixtures/xml/container1.xml @@ -9,19 +9,19 @@ https://symfony.com/schema/dic/security/security-1.0.xsd"> - + - + - + - + - + - + - + diff --git a/Tests/DependencyInjection/Fixtures/xml/firewall_provider.xml b/Tests/DependencyInjection/Fixtures/xml/firewall_provider.xml index e2f0e986..37e0b8af 100644 --- a/Tests/DependencyInjection/Fixtures/xml/firewall_provider.xml +++ b/Tests/DependencyInjection/Fixtures/xml/firewall_provider.xml @@ -9,15 +9,11 @@ https://symfony.com/schema/dic/security/security-1.0.xsd"> - - - + - - - - - + + + diff --git a/Tests/DependencyInjection/Fixtures/xml/firewall_undefined_provider.xml b/Tests/DependencyInjection/Fixtures/xml/firewall_undefined_provider.xml index e7f3e687..c1b51373 100644 --- a/Tests/DependencyInjection/Fixtures/xml/firewall_undefined_provider.xml +++ b/Tests/DependencyInjection/Fixtures/xml/firewall_undefined_provider.xml @@ -9,15 +9,11 @@ https://symfony.com/schema/dic/security/security-1.0.xsd"> - - - + - - - - - + + + diff --git a/Tests/DependencyInjection/Fixtures/xml/listener_provider.xml b/Tests/DependencyInjection/Fixtures/xml/listener_provider.xml index 462136c6..6d5e7149 100644 --- a/Tests/DependencyInjection/Fixtures/xml/listener_provider.xml +++ b/Tests/DependencyInjection/Fixtures/xml/listener_provider.xml @@ -9,15 +9,11 @@ https://symfony.com/schema/dic/security/security-1.0.xsd"> - - - + - - - - - + + + diff --git a/Tests/DependencyInjection/Fixtures/xml/listener_undefined_provider.xml b/Tests/DependencyInjection/Fixtures/xml/listener_undefined_provider.xml index cb82f2cc..0cd1ab6d 100644 --- a/Tests/DependencyInjection/Fixtures/xml/listener_undefined_provider.xml +++ b/Tests/DependencyInjection/Fixtures/xml/listener_undefined_provider.xml @@ -9,15 +9,11 @@ https://symfony.com/schema/dic/security/security-1.0.xsd"> - - - + - - - - - + + + diff --git a/Tests/DependencyInjection/Fixtures/xml/migrating_hasher.xml b/Tests/DependencyInjection/Fixtures/xml/migrating_hasher.xml index a4a9d201..110868de 100644 --- a/Tests/DependencyInjection/Fixtures/xml/migrating_hasher.xml +++ b/Tests/DependencyInjection/Fixtures/xml/migrating_hasher.xml @@ -13,9 +13,9 @@ - + bcrypt - + diff --git a/Tests/DependencyInjection/Fixtures/xml/remember_me_options.xml b/Tests/DependencyInjection/Fixtures/xml/remember_me_options.xml index 767397ad..e051ce22 100644 --- a/Tests/DependencyInjection/Fixtures/xml/remember_me_options.xml +++ b/Tests/DependencyInjection/Fixtures/xml/remember_me_options.xml @@ -9,9 +9,7 @@ https://symfony.com/schema/dic/security/security-1.0.xsd"> - - - + diff --git a/Tests/DependencyInjection/Fixtures/xml/sodium_hasher.xml b/Tests/DependencyInjection/Fixtures/xml/sodium_hasher.xml index fd5cacef..eb26969a 100644 --- a/Tests/DependencyInjection/Fixtures/xml/sodium_hasher.xml +++ b/Tests/DependencyInjection/Fixtures/xml/sodium_hasher.xml @@ -13,7 +13,7 @@ - + diff --git a/Tests/DependencyInjection/Fixtures/yml/access_token_oidc.yml b/Tests/DependencyInjection/Fixtures/yml/access_token_oidc.yml new file mode 100644 index 00000000..7da369de --- /dev/null +++ b/Tests/DependencyInjection/Fixtures/yml/access_token_oidc.yml @@ -0,0 +1,16 @@ +security: + providers: + default: + memory: ~ + + firewalls: + firewall1: + provider: default + access_token: + token_handler: + oidc: + algorithms: ['RS256'] + issuers: ['https://www.example.com'] + audience: 'audience' + keyset: '{"keys":[{"kty":"RSA","n":"abc","e":"AQAB"}]}' + diff --git a/Tests/DependencyInjection/Fixtures/yml/access_token_oidc_encryption.yml b/Tests/DependencyInjection/Fixtures/yml/access_token_oidc_encryption.yml new file mode 100644 index 00000000..956b33f4 --- /dev/null +++ b/Tests/DependencyInjection/Fixtures/yml/access_token_oidc_encryption.yml @@ -0,0 +1,20 @@ +security: + providers: + default: + memory: ~ + + firewalls: + firewall1: + provider: default + access_token: + token_handler: + oidc: + algorithms: ['RS256'] + issuers: ['https://www.example.com'] + audience: 'audience' + keyset: '{"keys":[{"kty":"RSA","n":"abc","e":"AQAB"}]}' + encryption: + enabled: true + keyset: '{"keys":[{"kty":"RSA","n":"abc","e":"AQAB","d":"def"}]}' + algorithms: ['RSA-OAEP'] + diff --git a/Tests/DependencyInjection/Fixtures/yml/access_token_oidc_user_info_discovery.yml b/Tests/DependencyInjection/Fixtures/yml/access_token_oidc_user_info_discovery.yml new file mode 100644 index 00000000..62e80d8d --- /dev/null +++ b/Tests/DependencyInjection/Fixtures/yml/access_token_oidc_user_info_discovery.yml @@ -0,0 +1,16 @@ +security: + providers: + default: + memory: ~ + + firewalls: + firewall1: + provider: default + access_token: + token_handler: + oidc_user_info: + base_uri: 'https://www.example.com/realms/demo/protocol/openid-connect/userinfo' + discovery: + cache: + id: 'oidc_cache' +