Skip to content

Commit 9496714

Browse files
committed
API keys can be extracted from different sources
This commit allows API keys to be extracted both from request query string and request headers. The extraction technique can be configured and the parameter holding the API key can also be changed. There is no BC break with this commit as default settings match the previous behavior.
1 parent 9c4dd1b commit 9496714

7 files changed

Lines changed: 156 additions & 5 deletions

File tree

src/Uecode/Bundle/ApiKeyBundle/DependencyInjection/Configuration.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,21 @@ public function getConfigTreeBuilder()
1515
$treeBuilder = new TreeBuilder();
1616
$rootNode = $treeBuilder->root('uecode_api_key');
1717

18+
$rootNode
19+
->children()
20+
->scalarNode('delivery')
21+
->defaultValue('query')
22+
->validate()
23+
->ifNotInArray(array('query', 'header'))
24+
->thenInvalid('Unknown authentication delivery type "%s".')
25+
->end()
26+
->end()
27+
->scalarNode('parameter_name')
28+
->defaultValue('api_key')
29+
->end()
30+
->end()
31+
;
32+
1833
return $treeBuilder;
1934
}
2035
}

src/Uecode/Bundle/ApiKeyBundle/DependencyInjection/UecodeApiKeyExtension.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,14 @@ public function load(array $configs, ContainerBuilder $container)
2222
new FileLocator(__DIR__.'/../Resources/config')
2323
);
2424
$loader->load('services.yml');
25+
26+
$this->defineKeyExtractor($config, $container);
27+
}
28+
29+
private function defineKeyExtractor(array $config, ContainerBuilder $container)
30+
{
31+
$container->setParameter('uecode.api_key.parameter_name', $config['parameter_name']);
32+
$container->setAlias('uecode.api_key.extractor', 'uecode.api_key.extractor.'.$config['delivery']);
2533
}
2634
}
2735

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
namespace Uecode\Bundle\ApiKeyBundle\Extractor;
4+
5+
use Symfony\Component\HttpFoundation\Request;
6+
7+
/**
8+
* Extracts API keys from request headers.
9+
*
10+
* @author Kévin Gomez <contact@kevingomez.fr>
11+
*/
12+
class HeaderExtractor implements KeyExtractor
13+
{
14+
private $parameterName;
15+
16+
/**
17+
* @param string $parameterName The name of the URL parameter containing the API key.
18+
*/
19+
public function __construct($parameterName)
20+
{
21+
$this->parameterName = $parameterName;
22+
}
23+
24+
/**
25+
* {@inheritDoc}
26+
*/
27+
public function hasKey(Request $request)
28+
{
29+
return $request->headers->has($this->parameterName);
30+
}
31+
32+
/**
33+
* {@inheritDoc}
34+
*/
35+
public function extractKey(Request $request)
36+
{
37+
return $request->headers->get($this->parameterName);
38+
}
39+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
namespace Uecode\Bundle\ApiKeyBundle\Extractor;
4+
5+
use Symfony\Component\HttpFoundation\Request;
6+
7+
/**
8+
* @author Kévin Gomez <contact@kevingomez.fr>
9+
*/
10+
interface KeyExtractor
11+
{
12+
/**
13+
* Tells if the given requests carries an API key.
14+
*
15+
* @param Request $request
16+
*
17+
* @return bool
18+
*/
19+
function hasKey(Request $request);
20+
21+
/**
22+
* Extract the API key from thhe given request
23+
*
24+
* @param Request $request
25+
*
26+
* @return string
27+
*/
28+
function extractKey(Request $request);
29+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
namespace Uecode\Bundle\ApiKeyBundle\Extractor;
4+
5+
use Symfony\Component\HttpFoundation\Request;
6+
7+
/**
8+
* Extracts API keys from a request query string.
9+
*
10+
* @author Kévin Gomez <contact@kevingomez.fr>
11+
*/
12+
class QueryExtractor implements KeyExtractor
13+
{
14+
private $parameterName;
15+
16+
/**
17+
* @param string $parameterName The name of the URL parameter containing the API key.
18+
*/
19+
public function __construct($parameterName)
20+
{
21+
$this->parameterName = $parameterName;
22+
}
23+
24+
/**
25+
* {@inheritDoc}
26+
*/
27+
public function hasKey(Request $request)
28+
{
29+
return $request->query->has($this->parameterName);
30+
}
31+
32+
/**
33+
* {@inheritDoc}
34+
*/
35+
public function extractKey(Request $request)
36+
{
37+
return $request->query->get($this->parameterName);
38+
}
39+
}

src/Uecode/Bundle/ApiKeyBundle/Resources/config/services.yml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ parameters:
33
uecode.api_key.provider.api_key.class: Uecode\Bundle\ApiKeyBundle\Security\Authentication\Provider\ApiKeyProvider
44
uecode.api_key.listener.api_key.class: Uecode\Bundle\ApiKeyBundle\Security\Firewall\ApiKeyListener
55

6+
uecode.api_key.extractor.query.class: Uecode\Bundle\ApiKeyBundle\Extractor\QueryExtractor
7+
uecode.api_key.extractor.header.class: Uecode\Bundle\ApiKeyBundle\Extractor\HeaderExtractor
8+
69
services:
710
uecode.api_key.provider.user_provider:
811
class: %uecode.api_key.provider.user_provider.class%
@@ -12,5 +15,13 @@ services:
1215
arguments: [""]
1316
uecode.api_key.listener.api_key:
1417
class: %uecode.api_key.listener.api_key.class%
15-
arguments: [@security.context, @security.authentication.manager]
18+
arguments: [@security.context, @security.authentication.manager, @uecode.api_key.extractor]
1619

20+
uecode.api_key.extractor.query:
21+
class: %uecode.api_key.extractor.query.class%
22+
arguments: [%uecode.api_key.parameter_name%]
23+
public: false
24+
uecode.api_key.extractor.header:
25+
class: %uecode.api_key.extractor.header.class%
26+
arguments: [%uecode.api_key.parameter_name%]
27+
public: false

src/Uecode/Bundle/ApiKeyBundle/Security/Firewall/ApiKeyListener.php

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use Symfony\Component\Security\Core\SecurityContextInterface;
1010
use Symfony\Component\Security\Http\Firewall\ListenerInterface;
1111
use Uecode\Bundle\ApiKeyBundle\Security\Authentication\Token\ApiKeyUserToken;
12+
use Uecode\Bundle\ApiKeyBundle\Extractor\KeyExtractor;
1213

1314
/**
1415
* @author Aaron Scherer <aequasi@gmail.com>
@@ -25,10 +26,16 @@ class ApiKeyListener implements ListenerInterface
2526
*/
2627
protected $authenticationManager;
2728

28-
public function __construct(SecurityContextInterface $context, AuthenticationManagerInterface $manager)
29+
/**
30+
* @var KeyExtractor
31+
*/
32+
protected $keyExtractor;
33+
34+
public function __construct(SecurityContextInterface $context, AuthenticationManagerInterface $manager, KeyExtractor $keyExtractor)
2935
{
3036
$this->securityContext = $context;
3137
$this->authenticationManager = $manager;
38+
$this->keyExtractor = $keyExtractor;
3239
}
3340

3441
/**
@@ -39,15 +46,18 @@ public function __construct(SecurityContextInterface $context, AuthenticationMan
3946
public function handle(GetResponseEvent $event)
4047
{
4148
$request = $event->getRequest();
42-
if (!$request->query->has('api_key')) {
49+
50+
if (!$this->keyExtractor->hasKey($request)) {
4351
$response = new Response();
4452
$response->setStatusCode(401);
4553
$event->setResponse($response);
4654
return ;
4755
}
4856

57+
$apiKey = $this->keyExtractor->extractKey($request);
58+
4959
$token = new ApiKeyUserToken();
50-
$token->setApiKey($request->query->get('api_key'));
60+
$token->setApiKey($apiKey);
5161

5262
try {
5363
$authToken = $this->authenticationManager->authenticate($token);
@@ -56,7 +66,7 @@ public function handle(GetResponseEvent $event)
5666
return;
5767
} catch (AuthenticationException $failed) {
5868
$token = $this->securityContext->getToken();
59-
if ($token instanceof ApiKeyUserToken && $token->getCredentials() == $request->query->get('apiKey')) {
69+
if ($token instanceof ApiKeyUserToken && $token->getCredentials() == $apiKey) {
6070
$this->securityContext->setToken(null);
6171
}
6272

0 commit comments

Comments
 (0)