Skip to content

Commit 2b64d85

Browse files
rasmustnilssonRasmus Nilsson
andauthored
Extend AbstractListener (#23)
* Add test coverage for AutoLoginListener * Bump symfony requirement * Use symfony/phpunit-bridge instead of phpunit/phpunit * Enable autoloading for Tests folder * Use psr-4 and collapse directory Co-authored-by: Rasmus Nilsson <rasmus.nilsson@lokalguiden.se>
1 parent 42686a1 commit 2b64d85

File tree

11 files changed

+230
-47
lines changed

11 files changed

+230
-47
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
composer.lock
22
vendor
3+
.phpunit.result.cache
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace Jmikola\AutoLogin\Tests\Http\Firewall;
4+
5+
use PHPUnit\Framework\TestCase;
6+
use PHPUnit\Framework\MockObject\MockObject;
7+
use Symfony\Component\HttpFoundation\Request;
8+
use Symfony\Component\HttpKernel\Event\RequestEvent;
9+
use Symfony\Component\HttpKernel\HttpKernelInterface;
10+
use Jmikola\AutoLogin\Http\Firewall\AutoLoginListener;
11+
use Jmikola\AutoLogin\Authentication\Token\AutoLoginToken;
12+
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
13+
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
14+
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
15+
16+
class AutoLoginListenerTest extends TestCase
17+
{
18+
private const PROVIDER_KEY = 'test-provider-key';
19+
private const TOKEN_KEY = 'test-token-param';
20+
private const TOKEN = 'test-token';
21+
22+
/**
23+
* @var MockObject|TokenStorageInterface
24+
*/
25+
private $tokenStorageMock;
26+
27+
/**
28+
* @var MockObject|AuthenticationManagerInterface
29+
*/
30+
private $authenticationManagerMock;
31+
32+
protected function setUp(): void
33+
{
34+
$this->tokenStorageMock = $this->createMock(TokenStorageInterface::class);
35+
$this->authenticationManagerMock = $this->createMock(AuthenticationManagerInterface::class);
36+
}
37+
38+
public function testShouldDoNothingIfTokenIsNotInRequest(): void
39+
{
40+
$listener = $this->createListener([]);
41+
42+
$event = new RequestEvent(
43+
$this->createMock(HttpKernelInterface::class),
44+
new Request(),
45+
HttpKernelInterface::MASTER_REQUEST
46+
);
47+
48+
$this->expectTokenStorageGetTokenNotCalled();
49+
$this->expectAuthenticationManagerAuthenticateNotCalled();
50+
51+
$listener->__invoke($event);
52+
}
53+
54+
public function testShouldNotOverrideAlreadyAuthenticatedUser(): void
55+
{
56+
$listener = $this->createListener([]);
57+
58+
$event = new RequestEvent(
59+
$this->createMock(HttpKernelInterface::class),
60+
new Request([self::TOKEN_KEY => self::TOKEN]),
61+
HttpKernelInterface::MASTER_REQUEST
62+
);
63+
64+
$this->expectTokenStorageGetToken();
65+
$this->expectAuthenticationManagerAuthenticateNotCalled();
66+
67+
$listener->__invoke($event);
68+
}
69+
70+
public function testShouldOverrideAlreadyAuthenticatedUser(): void
71+
{
72+
$listener = $this->createListener([
73+
'override_already_authenticated' => true,
74+
]);
75+
76+
$event = new RequestEvent(
77+
$this->createMock(HttpKernelInterface::class),
78+
new Request([self::TOKEN_KEY => self::TOKEN]),
79+
HttpKernelInterface::MASTER_REQUEST
80+
);
81+
82+
$this->expectTokenStorageGetToken();
83+
$this->expectShouldAuthenticateUser();
84+
85+
$listener->__invoke($event);
86+
}
87+
88+
private function createListener(array $options): AutoLoginListener
89+
{
90+
return new AutoLoginListener(
91+
$this->tokenStorageMock,
92+
$this->authenticationManagerMock,
93+
self::PROVIDER_KEY,
94+
self::TOKEN_KEY,
95+
null,
96+
null,
97+
$options
98+
);
99+
}
100+
101+
private function expectTokenStorageGetTokenNotCalled(): void
102+
{
103+
$this->tokenStorageMock
104+
->expects(self::never())
105+
->method('getToken')
106+
;
107+
}
108+
109+
private function expectTokenStorageGetToken(): void
110+
{
111+
$this->tokenStorageMock
112+
->expects(self::once())
113+
->method('getToken')
114+
->willReturn($this->createMock(TokenInterface::class))
115+
;
116+
}
117+
118+
private function expectAuthenticationManagerAuthenticateNotCalled(): void
119+
{
120+
$this->authenticationManagerMock
121+
->expects(self::never())
122+
->method('authenticate')
123+
;
124+
}
125+
126+
private function expectShouldAuthenticateUser(): void
127+
{
128+
$token = $this->createMock(TokenInterface::class);
129+
130+
$this->authenticationManagerMock
131+
->expects(self::once())
132+
->method('authenticate')
133+
->with(self::isInstanceOf(AutoLoginToken::class))
134+
->willReturn($token)
135+
;
136+
137+
$this->tokenStorageMock
138+
->expects(self::once())
139+
->method('setToken')
140+
->with($token)
141+
;
142+
}
143+
}

composer.json

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "jmikola/auto-login",
33
"type": "library",
4-
"description": "Faciliates automatic login via a single token for Symfony's Security component.",
4+
"description": "Facilitates automatic login via a single token for Symfony's Security component.",
55
"keywords": ["authentication", "auto-login", "login", "security"],
66
"homepage": "https://github.com/jmikola/AutoLogin",
77
"license": "MIT",
@@ -12,16 +12,25 @@
1212
"require": {
1313
"php": ">=7.1",
1414
"psr/log": "^1.0",
15-
"symfony/event-dispatcher": "^4.3 || ^5.0",
16-
"symfony/http-kernel": "^4.3 || ^5.0",
17-
"symfony/security": "^4.3 || ^5.0"
15+
"symfony/event-dispatcher": "^4.4 || ^5.0",
16+
"symfony/http-kernel": "^4.4 || ^5.0",
17+
"symfony/security": "^4.4 || ^5.0"
1818
},
1919
"autoload": {
20-
"psr-0": { "Jmikola\\AutoLogin": "src" }
20+
"psr-4": { "Jmikola\\AutoLogin\\": "src/" }
21+
},
22+
"autoload-dev": {
23+
"psr-4": { "Jmikola\\AutoLogin\\Tests\\": "Tests/" }
2124
},
2225
"extra": {
2326
"branch-alias": {
2427
"dev-master": "2.0.x-dev"
2528
}
29+
},
30+
"require-dev": {
31+
"symfony/phpunit-bridge": "^5.2"
32+
},
33+
"scripts": {
34+
"test": "./vendor/bin/simple-phpunit Tests/*"
2635
}
2736
}

phpunit.xml.dist

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<phpunit
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
backupGlobals="false"
5+
backupStaticAttributes="false"
6+
colors="true"
7+
convertErrorsToExceptions="true"
8+
convertNoticesToExceptions="true"
9+
convertWarningsToExceptions="true"
10+
processIsolation="false"
11+
stopOnFailure="false"
12+
bootstrap="vendor/autoload.php"
13+
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"
14+
/>

src/Jmikola/AutoLogin/Authentication/Provider/AutoLoginProvider.php renamed to src/Authentication/Provider/AutoLoginProvider.php

File renamed without changes.
File renamed without changes.
File renamed without changes.

src/Jmikola/AutoLogin/Exception/AutoLoginTokenNotFoundException.php renamed to src/Exception/AutoLoginTokenNotFoundException.php

File renamed without changes.

src/Jmikola/AutoLogin/Http/Firewall/AutoLoginListener.php renamed to src/Http/Firewall/AutoLoginListener.php

Lines changed: 58 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -14,60 +14,89 @@
1414
use Symfony\Component\Security\Core\Exception\AuthenticationException;
1515
use Symfony\Component\Security\Http\SecurityEvents;
1616
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
17+
use Symfony\Component\Security\Http\Firewall\AbstractListener;
1718

18-
class AutoLoginListener
19+
class AutoLoginListener extends AbstractListener
1920
{
21+
/**
22+
* @var TokenStorageInterface
23+
*/
24+
private $tokenStorage;
25+
26+
/**
27+
* @var AuthenticationManagerInterface
28+
*/
2029
private $authenticationManager;
21-
private $dispatcher;
22-
private $logger;
30+
31+
/**
32+
* @var string
33+
*/
2334
private $providerKey;
24-
private $securityContext;
35+
36+
/**
37+
* @var string
38+
*/
2539
private $tokenParam;
26-
private $options;
2740

2841
/**
29-
* Constructor.
30-
*
31-
* @param TokenStorageInterface $securityContext
32-
* @param AuthenticationManagerInterface $authenticationManager
33-
* @param string $providerKey
34-
* @param string $tokenParam
35-
* @param LoggerInterface $logger
36-
* @param EventDispatcherInterface $dispatcher
37-
* @param array $options
42+
* @var LoggerInterface|null
3843
*/
39-
public function __construct(TokenStorageInterface $securityContext, AuthenticationManagerInterface $authenticationManager, string $providerKey, string $tokenParam, LoggerInterface $logger = null, EventDispatcherInterface $dispatcher = null, array $options = array())
40-
{
41-
$this->securityContext = $securityContext;
44+
private $logger = null;
45+
46+
/**
47+
* @var EventDispatcherInterface|null
48+
*/
49+
private $dispatcher = null;
50+
51+
/**
52+
* @var array
53+
*/
54+
private $options;
55+
56+
public function __construct(
57+
TokenStorageInterface $tokenStorage,
58+
AuthenticationManagerInterface $authenticationManager,
59+
string $providerKey,
60+
string $tokenParam,
61+
LoggerInterface $logger = null,
62+
EventDispatcherInterface $dispatcher = null,
63+
array $options = []
64+
) {
65+
$this->tokenStorage = $tokenStorage;
4266
$this->authenticationManager = $authenticationManager;
4367
$this->providerKey = $providerKey;
4468
$this->tokenParam = $tokenParam;
4569
$this->logger = $logger;
4670
$this->dispatcher = $dispatcher;
4771

48-
$this->options = $options = array_merge(array(
72+
$this->options = $options = array_merge([
4973
'override_already_authenticated' => false,
50-
), $options);
74+
], $options);
5175
}
5276

5377
/**
54-
* @param RequestEvent $event
78+
* {@inheritDoc}
5579
*/
56-
public function __invoke(RequestEvent $event)
80+
public function supports(Request $request): ?bool
5781
{
58-
$request = $event->getRequest();
59-
60-
if ( ! ($this->isTokenInRequest($request))) {
61-
return;
62-
}
82+
return $request->query->has($this->tokenParam) ||
83+
$request->attributes->has($this->tokenParam) ||
84+
$request->request->has($this->tokenParam);
85+
}
6386

87+
/**
88+
* {@inheritDoc}
89+
*/
90+
public function authenticate(RequestEvent $event)
91+
{
92+
$request = $event->getRequest();
6493
$tokenParam = $request->get($this->tokenParam);
6594

6695
/* If the security context has a token, a user is already authenticated.
6796
* We will dispatch an event with the token parameter so that a listener
6897
* may track its usage.
6998
*/
70-
if (null !== $this->securityContext->getToken()) {
99+
if (null !== $this->tokenStorage->getToken()) {
71100
if (null !== $this->dispatcher) {
72101
$this->dispatcher->dispatch(
73102
new AlreadyAuthenticatedEvent($tokenParam),
@@ -95,7 +124,7 @@ public function __invoke(RequestEvent $event)
95124
* implement custom logic to respect our own token class.
96125
*/
97126
if ($authenticatedToken = $this->authenticationManager->authenticate($token)) {
98-
$this->securityContext->setToken($authenticatedToken);
127+
$this->tokenStorage->setToken($authenticatedToken);
99128

100129
if (null !== $this->dispatcher) {
101130
$this->dispatcher->dispatch(
@@ -107,24 +136,11 @@ public function __invoke(RequestEvent $event)
107136
} catch (AuthenticationException $e) {
108137
if (null !== $this->logger) {
109138
$this->logger->warning(
110-
'SecurityContext not populated with auto-login token as the '.
139+
'TokenStorage not populated with auto-login token as the '.
111140
'AuthenticationManager rejected the auto-login token created '.
112141
'by AutoLoginListener: '.$e->getMessage()
113142
);
114143
}
115144
}
116145
}
117-
118-
/**
119-
* Check the ParameterBags consulted by Request::get() for the token.
120-
*
121-
* @param Request $request
122-
* @return boolean
123-
*/
124-
private function isTokenInRequest(Request $request) : bool
125-
{
126-
return $request->query->has($this->tokenParam) ||
127-
$request->attributes->has($this->tokenParam) ||
128-
$request->request->has($this->tokenParam);
129-
}
130146
}

0 commit comments

Comments
 (0)