1616use OxidEsales \Eshop \Core \Request ;
1717use OxidEsales \Eshop \Core \Utils ;
1818use OxidEsales \EshopCommunity \Core \Di \ContainerFacade ;
19- use OxidEsales \EshopCommunity \Internal \Framework \Module \Facade \ModuleSettingServiceInterface ;
2019use DateTimeImmutable ;
2120use OxidEsales \SecurityModule \Authentication \TwoFactorAuth \OTP \Infrastructure \Repository \OtpChallengeStateRepositoryInterface ;
2221use OxidEsales \SecurityModule \Authentication \TwoFactorAuth \Service \AuthorizeService ;
23- use OxidEsales \SecurityModule \Authentication \TwoFactorAuth \Settings \TwoFASettings ;
22+ use OxidEsales \SecurityModule \Authentication \TwoFactorAuth \Settings \TwoFASettingsInterface ;
2423use OxidEsales \SecurityModule \Captcha \Service \ModuleSettingsServiceInterface as CaptchaSettingsServiceInterface ;
25- use OxidEsales \SecurityModule \Core \ Module ;
24+ use OxidEsales \SecurityModule \Shared \ Model \ User as SecurityModuleUser ;
2625use OxidEsales \SecurityModule \Tests \Integration \IntegrationTestCase ;
2726
28- // todo-critical: rework the 2FA part of the test. it changes the real configs on the fly and causing side effects
2927class UserTest extends IntegrationTestCase
3028{
3129 private const OTP_USER_NAME = 'user@oxid-esales.com ' ;
@@ -47,12 +45,6 @@ public function setUp(): void
4745 Registry::getSession ()->setVariable ('captcha_expiration ' , time () + 60 );
4846 }
4947
50- public function tearDown (): void
51- {
52- $ this ->disableTwoFactorAuth ();
53- parent ::tearDown ();
54- }
55-
5648 public function testCheckValuesWithInvalidCaptcha ()
5749 {
5850 $ this ->requestMock
@@ -68,7 +60,7 @@ public function testCheckValuesWithInvalidCaptcha()
6860 $ message = Registry::getLang ()->translateString ("ERROR_INVALID_CAPTCHA " );
6961 $ this ->expectExceptionMessage ($ message );
7062
71- $ subject = oxNew (User::class );
63+ $ subject = $ this -> createUserMock (captchaEnabled: true , twoFaEnabled: false );
7264 $ subject ->checkValues ('' , '' , '' , [], []);
7365 }
7466
@@ -87,42 +79,48 @@ public function testCheckValuesWithEmptyCaptcha()
8779 $ message = Registry::getLang ()->translateString ("ERROR_EMPTY_CAPTCHA " );
8880 $ this ->expectExceptionMessage ($ message );
8981
90- $ subject = oxNew (User::class );
82+ $ subject = $ this -> createUserMock (captchaEnabled: true , twoFaEnabled: false );
9183 $ subject ->checkValues ('' , '' , '' , [], []);
9284 }
9385
9486 public function testLoginWithInvalidCaptcha ()
9587 {
9688 $ this ->requestMock
9789 ->method ('getRequestParameter ' )
98- ->with ('captcha ' )
99- ->willReturn ('invalid_captcha ' );
90+ ->willReturnCallback (function ($ param ) {
91+ if ($ param === 'captcha ' ) {
92+ return 'invalid_captcha ' ;
93+ }
94+ return null ;
95+ });
10096
10197 $ this ->expectException (UserException::class);
10298 $ this ->expectExceptionMessage ("ERROR_INVALID_CAPTCHA " );
10399
104- $ subject = oxNew (User::class );
100+ $ subject = $ this -> createUserMock (captchaEnabled: true , twoFaEnabled: false );
105101 $ subject ->login ('' , '' );
106102 }
107103
108104 public function testLoginWithEmptyCaptcha ()
109105 {
110106 $ this ->requestMock
111107 ->method ('getRequestParameter ' )
112- ->with ('captcha ' )
113- ->willReturn ('' );
108+ ->willReturnCallback (function ($ param ) {
109+ if ($ param === 'captcha ' ) {
110+ return '' ;
111+ }
112+ return null ;
113+ });
114114
115115 $ this ->expectException (UserException::class);
116116 $ this ->expectExceptionMessage ("ERROR_EMPTY_CAPTCHA " );
117117
118- $ subject = oxNew (User::class );
118+ $ subject = $ this -> createUserMock (captchaEnabled: true , twoFaEnabled: false );
119119 $ subject ->login ('' , '' );
120120 }
121121
122122 public function testLoginWithValidCaptchaAndValidCredentials (): void
123123 {
124- $ this ->disableTwoFactorAuth ();
125-
126124 $ this ->requestMock
127125 ->method ('getRequestParameter ' )
128126 ->willReturnCallback (function ($ param ) {
@@ -132,22 +130,19 @@ public function testLoginWithValidCaptchaAndValidCredentials(): void
132130 return null ;
133131 });
134132
135- $ subject = oxNew (User::class );
133+ $ subject = $ this -> createUserMock (captchaEnabled: true , twoFaEnabled: false );
136134 $ result = $ subject ->login (self ::OTP_USER_NAME , self ::OTP_USER_PASSWORD );
137135
138136 $ this ->assertTrue ($ result );
139137 }
140138
141139 public function testLoginWithOTPEnabledAndValidCredentialsRedirectsToOTP (): void
142140 {
143- $ this ->disableCaptcha ();
144- $ this ->enableTwoFactorAuth ();
145-
146141 $ utilsMock = $ this ->createMock (Utils::class);
147142 $ utilsMock ->expects ($ this ->once ())->method ('redirect ' );
148143 Registry::set (Utils::class, $ utilsMock );
149144
150- $ subject = oxNew (User::class );
145+ $ subject = $ this -> createUserMock (captchaEnabled: false , twoFaEnabled: true );
151146 $ subject ->login (self ::OTP_USER_NAME , self ::OTP_USER_PASSWORD );
152147
153148 $ this ->assertNotNull (
@@ -157,34 +152,25 @@ public function testLoginWithOTPEnabledAndValidCredentialsRedirectsToOTP(): void
157152
158153 public function testLoginWithOTPEnabledAndInvalidCredentialsThrowsException (): void
159154 {
160- $ this ->disableCaptcha ();
161- $ this ->enableTwoFactorAuth ();
162-
163155 $ this ->expectException (UserException::class);
164156 $ this ->expectExceptionMessage ('ERROR_MESSAGE_USER_NOVALIDLOGIN ' );
165157
166- $ subject = oxNew (User::class );
158+ $ subject = $ this -> createUserMock (captchaEnabled: false , twoFaEnabled: true );
167159 $ subject ->login (self ::OTP_USER_NAME , uniqid ());
168160 }
169161
170162 public function testLoginWithOTPEnabledAndNonExistentUserThrowsException (): void
171163 {
172- $ this ->disableCaptcha ();
173- $ this ->enableTwoFactorAuth ();
174-
175164 $ this ->expectException (UserException::class);
176165 $ this ->expectExceptionMessage ('ERROR_MESSAGE_USER_NOVALIDLOGIN ' );
177166
178- $ subject = oxNew (User::class );
167+ $ subject = $ this -> createUserMock (captchaEnabled: false , twoFaEnabled: true );
179168 $ subject ->login ('nonexistent@test.com ' , 'anypassword ' );
180169 }
181170
182171 public function testLoginWithOTPDisabledCallsParentLogin (): void
183172 {
184- $ this ->disableCaptcha ();
185- $ this ->disableTwoFactorAuth ();
186-
187- $ subject = oxNew (User::class);
173+ $ subject = $ this ->createUserMock (captchaEnabled: false , twoFaEnabled: false );
188174 $ result = $ subject ->login (self ::OTP_USER_NAME , self ::OTP_USER_PASSWORD );
189175
190176 $ this ->assertTrue ($ result );
@@ -195,14 +181,11 @@ public function testLoginWithOTPDisabledCallsParentLogin(): void
195181
196182 public function testLoginWithOTPEnabledStoresUserIdInSession (): void
197183 {
198- $ this ->disableCaptcha ();
199- $ this ->enableTwoFactorAuth ();
200-
201184 $ utilsMock = $ this ->createMock (Utils::class);
202185 $ utilsMock ->method ('redirect ' );
203186 Registry::set (Utils::class, $ utilsMock );
204187
205- $ subject = oxNew (User::class );
188+ $ subject = $ this -> createUserMock (captchaEnabled: false , twoFaEnabled: true );
206189 $ subject ->login (self ::OTP_USER_NAME , self ::OTP_USER_PASSWORD );
207190
208191 $ sessionUserId = Registry::getSession ()->getVariable (AuthorizeService::USER_SESSION_KEY );
@@ -222,24 +205,22 @@ public function testLoginWithVerifiedChallengeStateSkipsOTPRedirect(): void
222205 $ utilsMock ->expects ($ this ->never ())->method ('redirect ' );
223206 Registry::set (Utils::class, $ utilsMock );
224207
225- $ result = oxNew (User::class)->login (self ::OTP_USER_NAME , self ::OTP_USER_PASSWORD );
208+ $ subject = $ this ->createUserMock (captchaEnabled: false , twoFaEnabled: true );
209+ $ result = $ subject ->login (self ::OTP_USER_NAME , self ::OTP_USER_PASSWORD );
226210
227211 $ this ->assertTrue ($ result );
228212 }
229213
230214 public function testLoginWithOTPPassSessionVariableMismatchTriggersOTPFlow (): void
231215 {
232- $ this ->disableCaptcha ();
233- $ this ->enableTwoFactorAuth ();
234-
235216 $ mismatchedUserId = 'different-user-id ' ;
236217 Registry::getSession ()->setVariable ('OTP_PASS ' , $ mismatchedUserId );
237218
238219 $ utilsMock = $ this ->createMock (Utils::class);
239220 $ utilsMock ->method ('redirect ' );
240221 Registry::set (Utils::class, $ utilsMock );
241222
242- $ subject = oxNew (User::class );
223+ $ subject = $ this -> createUserMock (captchaEnabled: false , twoFaEnabled: true );
243224 $ subject ->login (self ::OTP_USER_NAME , self ::OTP_USER_PASSWORD );
244225
245226 $ this ->assertNotNull (
@@ -253,35 +234,29 @@ public function testLoginWithOTPPassSessionVariableMismatchTriggersOTPFlow(): vo
253234 );
254235 }
255236
256- private function enableTwoFactorAuth ( ): void
237+ private function createUserMock ( bool $ captchaEnabled , bool $ twoFaEnabled ): SecurityModuleUser
257238 {
258- $ moduleSettingService = ContainerFacade::get (ModuleSettingServiceInterface::class);
259- $ moduleSettingService ->saveBoolean (
260- TwoFASettings::ACTIVE ,
261- true ,
262- Module::MODULE_ID
263- );
264- $ moduleSettingService ->saveString (
265- TwoFASettings::TWO_FACTOR_TYPE ,
266- 'otp ' ,
267- Module::MODULE_ID
268- );
269- }
239+ $ captchaSettings = $ this ->createMock (CaptchaSettingsServiceInterface::class);
240+ $ captchaSettings ->method ('isCaptchaEnabled ' )->willReturn ($ captchaEnabled );
241+ $ captchaSettings ->method ('isHoneyPotCaptchaEnabled ' )->willReturn (false );
270242
271- private function disableTwoFactorAuth (): void
272- {
273- $ moduleSettingService = ContainerFacade::get (ModuleSettingServiceInterface::class);
274- $ moduleSettingService ->saveBoolean (
275- TwoFASettings::ACTIVE ,
276- false ,
277- Module::MODULE_ID
243+ $ twoFaSettings = $ this ->createMock (TwoFASettingsInterface::class);
244+ $ twoFaSettings ->method ('isTwoFactorAuthEnabled ' )->willReturn ($ twoFaEnabled );
245+
246+ $ serviceMocks = [
247+ CaptchaSettingsServiceInterface::class => $ captchaSettings ,
248+ TwoFASettingsInterface::class => $ twoFaSettings ,
249+ ];
250+
251+ /** @var SecurityModuleUser $userMock */
252+ $ userMock = $ this ->getMockBuilder (SecurityModuleUser::class)
253+ ->onlyMethods (['getService ' ])
254+ ->getMock ();
255+ $ userMock ->method ('getService ' )->willReturnCallback (
256+ fn (string $ id ) => $ serviceMocks [$ id ] ?? ContainerFacade::get ($ id )
278257 );
279- }
280258
281- private function disableCaptcha (): void
282- {
283- $ captchaSettings = ContainerFacade::get (CaptchaSettingsServiceInterface::class);
284- $ captchaSettings ->saveIsCaptchaEnabled (false );
259+ return $ userMock ;
285260 }
286261
287262 private function getOTPUserId (): string
0 commit comments