Skip to content

Commit 1285199

Browse files
authored
Merge pull request #2 from yeebase/feature/flow-53-compatibility
FEATURE: Flow 5.3 compatibility
2 parents 40a2e6d + 6d25d2c commit 1285199

4 files changed

Lines changed: 124 additions & 54 deletions

File tree

Classes/Http/RedirectComponent.php

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
<?php
2+
declare(strict_types=1);
3+
namespace Yeebase\TwoFactorAuthentication\Http;
4+
5+
use Neos\Flow\Annotations as Flow;
6+
use Neos\Flow\Http\Component\ComponentChain;
7+
use Neos\Flow\Http\Component\ComponentContext;
8+
use Neos\Flow\Http\Component\ComponentInterface;
9+
use Neos\Flow\Http\Request as HttpRequest;
10+
use Neos\Flow\Mvc\ActionRequest;
11+
use Neos\Flow\Mvc\Routing\UriBuilder;
12+
13+
/**
14+
* A HTTP component that redirects to the configured 2FA login/setup routes if requested
15+
*/
16+
final class RedirectComponent implements ComponentInterface
17+
{
18+
public const REDIRECT_LOGIN = 'login';
19+
public const REDIRECT_SETUP = 'setup';
20+
21+
/**
22+
* @Flow\InjectConfiguration(path="routes.login")
23+
* @var array
24+
*/
25+
protected $loginRouteValues;
26+
27+
/**
28+
* @Flow\InjectConfiguration(path="routes.setup")
29+
* @var array
30+
*/
31+
protected $setupRouteValue;
32+
33+
public function handle(ComponentContext $componentContext)
34+
{
35+
$redirectTarget = $componentContext->getParameter(static::class, 'redirect');
36+
if ($redirectTarget === null) {
37+
return;
38+
}
39+
if ($redirectTarget === self::REDIRECT_LOGIN) {
40+
$this->redirectToLogin($componentContext);
41+
} elseif ($redirectTarget === self::REDIRECT_SETUP) {
42+
$this->redirectToSetup($componentContext);
43+
} else {
44+
throw new \RuntimeException(sprintf('Invalid redirect target "%s"', $redirectTarget), 1568189192);
45+
}
46+
}
47+
48+
/**
49+
* Triggers a redirect to the 2FA login route configured at routes.login or throws an exception if the configuration is missing/incorrect
50+
*/
51+
private function redirectToLogin(ComponentContext $componentContext): void
52+
{
53+
try {
54+
$this->validateRouteValues($this->loginRouteValues);
55+
} catch (\InvalidArgumentException $exception) {
56+
throw new \RuntimeException('Missing/invalid routes.login configuration: ' . $exception->getMessage(), 1550660144, $exception);
57+
}
58+
$this->redirect($componentContext, $this->loginRouteValues);
59+
}
60+
61+
/**
62+
* Triggers a redirect to the 2FA setup route configured at routes.setup or throws an exception if the configuration is missing/incorrect
63+
*/
64+
private function redirectToSetup(ComponentContext $componentContext): void
65+
{
66+
try {
67+
$this->validateRouteValues($this->setupRouteValue);
68+
} catch (\InvalidArgumentException $exception) {
69+
throw new \RuntimeException('Missing/invalid routes.setup configuration: ' . $exception->getMessage(), 1550660178, $exception);
70+
}
71+
$this->redirect($componentContext, $this->setupRouteValue);
72+
}
73+
74+
private function validateRouteValues(array $routeValues): void
75+
{
76+
$requiredRouteValues = ['@package', '@controller', '@action'];
77+
foreach ($requiredRouteValues as $routeValue) {
78+
if (!array_key_exists($routeValue, $routeValues)) {
79+
throw new \InvalidArgumentException(sprintf('Missing "%s" route value', $routeValue), 1550660039);
80+
}
81+
}
82+
}
83+
84+
private function redirect(ComponentContext $componentContext, array $routeValues): void
85+
{
86+
/** @var HttpRequest $httpRequest */
87+
$httpRequest = $componentContext->getHttpRequest();
88+
$actionRequest = new ActionRequest($httpRequest);
89+
$uriBuilder = new UriBuilder();
90+
$uriBuilder->setRequest($actionRequest);
91+
$redirectUrl = $uriBuilder->setCreateAbsoluteUri(true)->setFormat('html')->build($routeValues);
92+
93+
$componentContext->replaceHttpResponse($componentContext->getHttpResponse()->withStatus(303)->withHeader('Location', $redirectUrl));
94+
$componentContext->setParameter(ComponentChain::class, 'cancel', true);
95+
}
96+
}

Classes/Security/Authentication/Provider/TwoFactorAuthenticationProvider.php

Lines changed: 20 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,17 @@
44

55
use Neos\Flow\Annotations as Flow;
66
use Neos\Flow\Core\Bootstrap;
7+
use Neos\Flow\Http\Component\ComponentContext;
78
use Neos\Flow\Http\HttpRequestHandlerInterface;
8-
use Neos\Flow\Security\Authentication\EntryPoint\WebRedirect;
99
use Neos\Flow\Security\Authentication\Provider\AbstractProvider;
1010
use Neos\Flow\Security\Authentication\TokenInterface;
1111
use Neos\Flow\Security\Context as SecurityContext;
1212
use Neos\Flow\Security\Exception\AuthenticationRequiredException;
1313
use Neos\Flow\Security\Exception\UnsupportedAuthenticationTokenException;
14+
use Neos\Flow\Session\SessionManagerInterface;
15+
use Neos\Utility\Exception\PropertyNotAccessibleException;
16+
use Neos\Utility\ObjectAccess;
17+
use Yeebase\TwoFactorAuthentication\Http\RedirectComponent;
1418
use Yeebase\TwoFactorAuthentication\Security\Authentication\Token\OtpToken;
1519
use Yeebase\TwoFactorAuthentication\Service\TwoFactorAuthenticationService;
1620

@@ -38,18 +42,6 @@ final class TwoFactorAuthenticationProvider extends AbstractProvider
3842
*/
3943
protected $twoFactorAuthenticationService;
4044

41-
/**
42-
* @Flow\InjectConfiguration(path="routes.login")
43-
* @var array
44-
*/
45-
protected $loginRouteValues;
46-
47-
/**
48-
* @Flow\InjectConfiguration(path="routes.setup")
49-
* @var array
50-
*/
51-
protected $setupRouteValue;
52-
5345
/**
5446
* @Flow\InjectConfiguration(path="requireTwoFactorAuthentication")
5547
* @var bool
@@ -76,7 +68,7 @@ public function authenticate(TokenInterface $authenticationToken): void
7668
}
7769
if ($this->twoFactorAuthenticationService->isTwoFactorAuthenticationEnabledFor($account)) {
7870
if (!$authenticationToken->hasOtp()) {
79-
$this->redirectToLogin();
71+
$this->requestRedirect(RedirectComponent::REDIRECT_LOGIN);
8072
return;
8173
}
8274
if ($this->twoFactorAuthenticationService->validateOtp($account, $authenticationToken->getOtp())) {
@@ -90,7 +82,7 @@ public function authenticate(TokenInterface $authenticationToken): void
9082
return;
9183
}
9284
if ($this->requireTwoFactorAuthentication) {
93-
$this->redirectToSetup();
85+
$this->requestRedirect(RedirectComponent::REDIRECT_SETUP);
9486
} else {
9587
/** @noinspection PhpUnhandledExceptionInspection */
9688
$authenticationToken->setAuthenticationStatus(TokenInterface::AUTHENTICATION_SUCCESSFUL);
@@ -99,49 +91,24 @@ public function authenticate(TokenInterface $authenticationToken): void
9991
}
10092

10193
/**
102-
* Triggers a redirect to the 2FA login route configured at routes.login or throws an exception if the configuration is missing/incorrect
103-
*/
104-
private function redirectToLogin(): void
105-
{
106-
try {
107-
$this->validateRouteValues($this->loginRouteValues);
108-
} catch (\InvalidArgumentException $exception) {
109-
throw new \RuntimeException('Missing/invalid routes.login configuration: ' . $exception->getMessage(), 1550660144, $exception);
110-
}
111-
$this->redirect($this->loginRouteValues);
112-
}
113-
114-
/**
115-
* Triggers a redirect to the 2FA setup route configured at routes.setup or throws an exception if the configuration is missing/incorrect
94+
* Triggers a redirect by setting the corresponding HTTP component parameter for the @see RedirectComponent to pick up
95+
*
96+
* @param string $target one of the RedirectComponent::REDIRECT_* constants
11697
*/
117-
private function redirectToSetup(): void
98+
private function requestRedirect(string $target): void
11899
{
119-
try {
120-
$this->validateRouteValues($this->setupRouteValue);
121-
} catch (\InvalidArgumentException $exception) {
122-
throw new \RuntimeException('Missing/invalid routes.setup configuration: ' . $exception->getMessage(), 1550660178, $exception);
123-
}
124-
$this->redirect($this->setupRouteValue);
125-
}
126-
127-
private function validateRouteValues(array $routeValues): void
128-
{
129-
$requiredRouteValues = ['@package', '@controller', '@action'];
130-
foreach ($requiredRouteValues as $routeValue) {
131-
if (!array_key_exists($routeValue, $routeValues)) {
132-
throw new \InvalidArgumentException(sprintf('Missing "%s" route value', $routeValue), 1550660039);
133-
}
134-
}
135-
}
136-
137-
private function redirect(array $routeValues): void {
138100
$requestHandler = $this->bootstrap->getActiveRequestHandler();
139101
if (!$requestHandler instanceof HttpRequestHandlerInterface) {
140102
throw new \RuntimeException('This provider only supports HTTP requests', 1549985779);
141103
}
142-
$webRedirect = new WebRedirect();
143-
$webRedirect->setOptions(['routeValues' => $routeValues]);
144-
/** @noinspection PhpUnhandledExceptionInspection */
145-
$webRedirect->startAuthentication($requestHandler->getHttpRequest(), $requestHandler->getHttpResponse());
104+
try {
105+
$componentContext = ObjectAccess::getProperty($requestHandler, 'componentContext', true);
106+
} catch (PropertyNotAccessibleException $e) {
107+
throw new \RuntimeException('Faild to extract ComponentContext from RequestHandler', 1568188386, $e);
108+
}
109+
if (!$componentContext instanceof ComponentContext) {
110+
throw new \RuntimeException('Faild to extract ComponentContext from RequestHandler', 1568188387);
111+
}
112+
$componentContext->setParameter(RedirectComponent::class, 'redirect', $target);
146113
}
147114
}

Configuration/Settings.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,13 @@ Yeebase:
2828

2929
Neos:
3030
Flow:
31+
http:
32+
chain:
33+
'process':
34+
chain:
35+
'Yeebase.TwoFactorAuthentication:Redirect':
36+
position: 'after dispatching'
37+
component: 'Yeebase\TwoFactorAuthentication\Http\RedirectComponent'
3138
persistence:
3239
doctrine:
3340
migrations:

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"description": "Two-Factor-Authentication (2FA) for Neos Flow",
55
"license": "MIT",
66
"require": {
7-
"neos/flow": "^5.2",
7+
"neos/flow": "^5.3",
88
"pragmarx/google2fa": "^4.0",
99
"bacon/bacon-qr-code": "^2.0"
1010
},

0 commit comments

Comments
 (0)