13
13
use Neos \Flow \Configuration \ConfigurationManager ;
14
14
use Neos \Flow \Mvc \Controller \ActionController ;
15
15
use Neos \Flow \Mvc \FlashMessage \FlashMessageService ;
16
- use Neos \Flow \Security \Authentication \ AuthenticationManagerInterface ;
16
+ use Neos \Flow \Security \Account ;
17
17
use Neos \Flow \Security \Context as SecurityContext ;
18
+ use Neos \Flow \Session \SessionManagerInterface ;
18
19
use Neos \Fusion \View \FusionView ;
19
20
use Neos \Neos \Domain \Repository \DomainRepository ;
20
21
use Neos \Neos \Domain \Repository \SiteRepository ;
21
22
use Sandstorm \NeosTwoFactorAuthentication \Domain \Model \SecondFactor ;
22
23
use Sandstorm \NeosTwoFactorAuthentication \Domain \Repository \SecondFactorRepository ;
23
- use Sandstorm \NeosTwoFactorAuthentication \Security \ Authentication \ Token \ UsernameAndPasswordWithSecondFactor ;
24
+ use Sandstorm \NeosTwoFactorAuthentication \Http \ Middleware \ SecondFactorMiddleware ;
24
25
use Sandstorm \NeosTwoFactorAuthentication \Service \TOTPService ;
25
26
26
27
class LoginController extends ActionController
@@ -36,12 +37,6 @@ class LoginController extends ActionController
36
37
*/
37
38
protected $ securityContext ;
38
39
39
- /**
40
- * @var AuthenticationManagerInterface
41
- * @Flow\Inject
42
- */
43
- protected $ authenticationManager ;
44
-
45
40
/**
46
41
* @var DomainRepository
47
42
* @Flow\Inject
@@ -66,13 +61,19 @@ class LoginController extends ActionController
66
61
*/
67
62
protected $ secondFactorRepository ;
68
63
64
+ /**
65
+ * @Flow\Inject(lazy=false)
66
+ * @var SessionManagerInterface
67
+ */
68
+ protected $ sessionManager ;
69
+
69
70
/**
70
71
* This action decides which tokens are already authenticated
71
72
* and decides which is next to authenticate
72
73
*
73
74
* ATTENTION: this code is copied from the Neos.Neos:LoginController
74
75
*/
75
- public function askForSecondFactorAction (?string $ username = null , bool $ unauthorized = false )
76
+ public function askForSecondFactorAction (?string $ username = null )
76
77
{
77
78
$ currentDomain = $ this ->domainRepository ->findOneByActiveRequest ();
78
79
$ currentSite = $ currentDomain !== null ? $ currentDomain ->getSite () : $ this ->siteRepository ->findDefault ();
@@ -83,6 +84,51 @@ public function askForSecondFactorAction(?string $username = null, bool $unautho
83
84
'site ' => $ currentSite ,
84
85
'flashMessages ' => $ this ->flashMessageService ->getFlashMessageContainerForRequest ($ this ->request )->getMessagesAndFlush (),
85
86
]);
87
+
88
+ // TODO: should we safe redirect to original request?
89
+ }
90
+
91
+ public function checkOtpAction (string $ otp )
92
+ {
93
+ $ account = $ this ->securityContext ->getAccount ();
94
+
95
+ $ isValidOtp = $ this ->enteredTokenMatchesAnySecondFactor ($ otp , $ account );
96
+
97
+ // WHY: We need to check the OTP here and set the authentication status on the Session Object of 2FA package
98
+ // see Sandstorm/NeosTwoFactor
99
+ if ($ isValidOtp ) {
100
+ $ this ->sessionManager ->getCurrentSession ()->putData (
101
+ SecondFactorMiddleware::SESSION_OBJECT_ID ,
102
+ [SecondFactorMiddleware::SESSION_OBJECT_AUTH_STATUS => SecondFactorMiddleware::SECOND_FACTOR_AUTHENTICATED ]
103
+ );
104
+ } else {
105
+ // FIXME: not visible in View!
106
+ $ this ->addFlashMessage ('Invalid otp! ' , 'Error ' , Message::SEVERITY_ERROR );
107
+ }
108
+
109
+ // TODO: should we safe redirect to original request?
110
+ $ this ->redirect ('index ' , 'Backend\Backend ' , 'Neos.Neos ' );
111
+ }
112
+
113
+ /**
114
+ * Check if the given token matches any registered second factor
115
+ *
116
+ * @param string $enteredSecondFactor
117
+ * @param Account $account
118
+ * @return bool
119
+ */
120
+ private function enteredTokenMatchesAnySecondFactor (string $ enteredSecondFactor , Account $ account ): bool
121
+ {
122
+ /** @var SecondFactor[] $secondFactors */
123
+ $ secondFactors = $ this ->secondFactorRepository ->findByAccount ($ account );
124
+ foreach ($ secondFactors as $ secondFactor ) {
125
+ $ isValid = TOTPService::checkIfOtpIsValid ($ secondFactor ->getSecret (), $ enteredSecondFactor );
126
+ if ($ isValid ) {
127
+ return true ;
128
+ }
129
+ }
130
+
131
+ return false ;
86
132
}
87
133
88
134
/**
@@ -91,7 +137,7 @@ public function askForSecondFactorAction(?string $username = null, bool $unautho
91
137
*
92
138
* ATTENTION: this code is copied from the Neos.Neos:LoginController
93
139
*/
94
- public function setupSecondFactorAction (?string $ username = null , bool $ unauthorized = false )
140
+ public function setupSecondFactorAction (?string $ username = null )
95
141
{
96
142
$ otp = TOTPService::generateNewTotp ();
97
143
$ secret = $ otp ->getSecret ();
@@ -101,11 +147,7 @@ public function setupSecondFactorAction(?string $username = null, bool $unauthor
101
147
$ currentSiteName = $ currentSite ->getName ();
102
148
$ urlEncodedSiteName = urlencode ($ currentSiteName );
103
149
104
- $ secondFactorAuthenticationTokens = $ this ->securityContext ->getAuthenticationTokensOfType (UsernameAndPasswordWithSecondFactor::class);
105
- // TODO: error if empty
106
- // TODO: check token status (is authentication successful)
107
-
108
- $ userIdentifier = $ secondFactorAuthenticationTokens [0 ]->getAccount ()->getAccountIdentifier ();
150
+ $ userIdentifier = $ this ->securityContext ->getAccount ()->getAccountIdentifier ();
109
151
110
152
$ oauthData = "otpauth://totp/ $ userIdentifier?secret= $ secret&period=30&issuer= $ urlEncodedSiteName " ;
111
153
$ qrCode = (new QRCode (new QROptions ([
@@ -131,19 +173,14 @@ public function setupSecondFactorAction(?string $username = null, bool $unauthor
131
173
*/
132
174
public function createSecondFactorAction (string $ secret , string $ secondFactorFromApp )
133
175
{
134
- // TODO: validate Token
135
176
$ isValid = TOTPService::checkIfOtpIsValid ($ secret , $ secondFactorFromApp );
136
177
137
178
if (!$ isValid ) {
138
179
$ this ->addFlashMessage ('Submitted OTP was not correct ' , '' , Message::SEVERITY_WARNING );
139
180
$ this ->redirect ('setupSecondFactor ' );
140
181
}
141
182
142
- $ secondFactorAuthenticationTokens = $ this ->securityContext ->getAuthenticationTokensOfType (UsernameAndPasswordWithSecondFactor::class);
143
- // TODO: error if empty
144
- // TODO: check token status (is authentication successful)
145
-
146
- $ account = $ secondFactorAuthenticationTokens [0 ]->getAccount ();
183
+ $ account = $ this ->securityContext ->getAccount ();
147
184
148
185
$ secondFactor = new SecondFactor ();
149
186
$ secondFactor ->setAccount ($ account );
@@ -153,7 +190,14 @@ public function createSecondFactorAction(string $secret, string $secondFactorFro
153
190
$ this ->persistenceManager ->persistAll ();
154
191
155
192
$ this ->addFlashMessage ('Successfully created otp ' );
156
- // TODO: login because 2fa is set up with valid otp or force re-login with new otp
193
+
194
+ // TODO: Discuss: we could skip this to force the user to enter a otp again directly after setup
195
+ $ this ->sessionManager ->getCurrentSession ()->putData (
196
+ SecondFactorMiddleware::SESSION_OBJECT_ID ,
197
+ [SecondFactorMiddleware::SESSION_OBJECT_AUTH_STATUS => SecondFactorMiddleware::SECOND_FACTOR_AUTHENTICATED ]
198
+ );
199
+
200
+ // TODO: should we safe redirect to original request?
157
201
$ this ->redirect ('index ' , 'Backend\Backend ' , 'Neos.Neos ' );
158
202
}
159
203
0 commit comments