6
6
* This file is part of the Sandstorm.NeosTwoFactorAuthentication package.
7
7
*/
8
8
9
+ use Neos \Error \Messages \Message ;
9
10
use Neos \Flow \Annotations as Flow ;
10
11
use Neos \Flow \Configuration \ConfigurationManager ;
12
+ use Neos \Flow \Configuration \Exception \InvalidConfigurationTypeException ;
11
13
use Neos \Flow \Mvc \Controller \ActionController ;
14
+ use Neos \Flow \Mvc \Exception \StopActionException ;
12
15
use Neos \Flow \Mvc \FlashMessage \FlashMessageService ;
13
- use Neos \Flow \Security \Authentication \AuthenticationManagerInterface ;
16
+ use Neos \Flow \Persistence \Exception \IllegalObjectTypeException ;
17
+ use Neos \Flow \Security \Account ;
14
18
use Neos \Flow \Security \Context as SecurityContext ;
19
+ use Neos \Flow \Session \Exception \SessionNotStartedException ;
15
20
use Neos \Fusion \View \FusionView ;
16
21
use Neos \Neos \Domain \Repository \DomainRepository ;
17
22
use Neos \Neos \Domain \Repository \SiteRepository ;
23
+ use Sandstorm \NeosTwoFactorAuthentication \Domain \AuthenticationStatus ;
24
+ use Sandstorm \NeosTwoFactorAuthentication \Domain \Model \SecondFactor ;
25
+ use Sandstorm \NeosTwoFactorAuthentication \Domain \Repository \SecondFactorRepository ;
26
+ use Sandstorm \NeosTwoFactorAuthentication \Service \SecondFactorSessionStorageService ;
27
+ use Sandstorm \NeosTwoFactorAuthentication \Service \TOTPService ;
18
28
19
29
class LoginController extends ActionController
20
30
{
@@ -29,12 +39,6 @@ class LoginController extends ActionController
29
39
*/
30
40
protected $ securityContext ;
31
41
32
- /**
33
- * @var AuthenticationManagerInterface
34
- * @Flow\Inject
35
- */
36
- protected $ authenticationManager ;
37
-
38
42
/**
39
43
* @var DomainRepository
40
44
* @Flow\Inject
@@ -53,27 +57,154 @@ class LoginController extends ActionController
53
57
*/
54
58
protected $ flashMessageService ;
55
59
60
+ /**
61
+ * @var SecondFactorRepository
62
+ * @Flow\Inject
63
+ */
64
+ protected $ secondFactorRepository ;
65
+
66
+ /**
67
+ * @Flow\Inject
68
+ * @var SecondFactorSessionStorageService
69
+ */
70
+ protected $ secondFactorSessionStorageService ;
71
+
72
+ /**
73
+ * @Flow\Inject
74
+ * @var TOTPService
75
+ */
76
+ protected $ tOTPService ;
77
+
78
+ /**
79
+ * This action decides which tokens are already authenticated
80
+ * and decides which is next to authenticate
81
+ *
82
+ * ATTENTION: this code is copied from the Neos.Neos:LoginController
83
+ */
84
+ public function askForSecondFactorAction (?string $ username = null ): void
85
+ {
86
+ $ currentDomain = $ this ->domainRepository ->findOneByActiveRequest ();
87
+ $ currentSite = $ currentDomain !== null ? $ currentDomain ->getSite () : $ this ->siteRepository ->findDefault ();
88
+
89
+ $ this ->view ->assignMultiple ([
90
+ 'styles ' => array_filter ($ this ->getNeosSettings ()['userInterface ' ]['backendLoginForm ' ]['stylesheets ' ]),
91
+ 'username ' => $ username ,
92
+ 'site ' => $ currentSite ,
93
+ 'flashMessages ' => $ this ->flashMessageService
94
+ ->getFlashMessageContainerForRequest ($ this ->request )
95
+ ->getMessagesAndFlush (),
96
+ ]);
97
+ }
98
+
99
+ /**
100
+ * @throws StopActionException
101
+ * @throws SessionNotStartedException
102
+ */
103
+ public function checkSecondFactorAction (string $ otp ): void
104
+ {
105
+ $ account = $ this ->securityContext ->getAccount ();
106
+
107
+ $ isValidOtp = $ this ->enteredTokenMatchesAnySecondFactor ($ otp , $ account );
108
+
109
+ if ($ isValidOtp ) {
110
+ $ this ->secondFactorSessionStorageService ->setAuthenticationStatus (AuthenticationStatus::AUTHENTICATED );
111
+ } else {
112
+ // FIXME: not visible in View!
113
+ $ this ->addFlashMessage ('Invalid OTP! ' , 'Error ' , Message::SEVERITY_ERROR );
114
+ }
115
+
116
+ $ originalRequest = $ this ->securityContext ->getInterceptedRequest ();
117
+ if ($ originalRequest !== null ) {
118
+ $ this ->redirectToRequest ($ originalRequest );
119
+ }
120
+
121
+ $ this ->redirect ('index ' , 'Backend\Backend ' , 'Neos.Neos ' );
122
+ }
123
+
56
124
/**
57
125
* This action decides which tokens are already authenticated
58
126
* and decides which is next to authenticate
59
127
*
60
128
* ATTENTION: this code is copied from the Neos.Neos:LoginController
61
129
*/
62
- public function askForSecondFactorAction (?string $ username = null , bool $ unauthorized = false )
130
+ public function setupSecondFactorAction (?string $ username = null ): void
63
131
{
132
+ $ otp = TOTPService::generateNewTotp ();
133
+ $ secret = $ otp ->getSecret ();
134
+ $ qrCode = $ this ->tOTPService ->generateQRCodeForTokenAndAccount ($ otp , $ this ->securityContext ->getAccount ());
135
+
64
136
$ currentDomain = $ this ->domainRepository ->findOneByActiveRequest ();
65
137
$ currentSite = $ currentDomain !== null ? $ currentDomain ->getSite () : $ this ->siteRepository ->findDefault ();
66
138
67
139
$ this ->view ->assignMultiple ([
68
140
'styles ' => array_filter ($ this ->getNeosSettings ()['userInterface ' ]['backendLoginForm ' ]['stylesheets ' ]),
69
141
'username ' => $ username ,
70
142
'site ' => $ currentSite ,
71
- 'flashMessages ' => $ this ->flashMessageService ->getFlashMessageContainerForRequest ($ this ->request )->getMessagesAndFlush (),
143
+ 'secret ' => $ secret ,
144
+ 'qrCode ' => $ qrCode ,
145
+ 'flashMessages ' => $ this ->flashMessageService
146
+ ->getFlashMessageContainerForRequest ($ this ->request )
147
+ ->getMessagesAndFlush (),
72
148
]);
73
149
}
74
150
151
+ /**
152
+ * @param string $secret
153
+ * @param string $secondFactorFromApp
154
+ * @return void
155
+ * @throws IllegalObjectTypeException
156
+ * @throws SessionNotStartedException
157
+ * @throws StopActionException
158
+ */
159
+ public function createSecondFactorAction (string $ secret , string $ secondFactorFromApp ): void
160
+ {
161
+ $ isValid = TOTPService::checkIfOtpIsValid ($ secret , $ secondFactorFromApp );
162
+
163
+ if (!$ isValid ) {
164
+ $ this ->addFlashMessage ('Submitted OTP was not correct. ' , '' , Message::SEVERITY_WARNING );
165
+ $ this ->redirect ('setupSecondFactor ' );
166
+ }
167
+
168
+ $ account = $ this ->securityContext ->getAccount ();
169
+
170
+ $ this ->secondFactorRepository ->createSecondFactorForAccount ($ secret , $ account );
171
+
172
+ $ this ->addFlashMessage ('Successfully created otp. ' );
173
+
174
+ $ this ->secondFactorSessionStorageService ->setAuthenticationStatus (AuthenticationStatus::AUTHENTICATED );
175
+
176
+ $ originalRequest = $ this ->securityContext ->getInterceptedRequest ();
177
+ if ($ originalRequest !== null ) {
178
+ $ this ->redirectToRequest ($ originalRequest );
179
+ }
180
+
181
+ $ this ->redirect ('index ' , 'Backend\Backend ' , 'Neos.Neos ' );
182
+ }
183
+
184
+ /**
185
+ * Check if the given token matches any registered second factor
186
+ *
187
+ * @param string $enteredSecondFactor
188
+ * @param Account $account
189
+ * @return bool
190
+ */
191
+ private function enteredTokenMatchesAnySecondFactor (string $ enteredSecondFactor , Account $ account ): bool
192
+ {
193
+ /** @var SecondFactor[] $secondFactors */
194
+ $ secondFactors = $ this ->secondFactorRepository ->findByAccount ($ account );
195
+ foreach ($ secondFactors as $ secondFactor ) {
196
+ $ isValid = TOTPService::checkIfOtpIsValid ($ secondFactor ->getSecret (), $ enteredSecondFactor );
197
+ if ($ isValid ) {
198
+ return true ;
199
+ }
200
+ }
201
+
202
+ return false ;
203
+ }
204
+
75
205
/**
76
206
* @return array
207
+ * @throws InvalidConfigurationTypeException
77
208
*/
78
209
protected function getNeosSettings (): array
79
210
{
0 commit comments