@@ -318,71 +318,90 @@ public int triggerAndCheckRateLimit(Main main, TOTPDevice device) throws Excepti
318318 }
319319
320320 @ Test
321- public void rateLimitCooldownTest () throws Exception {
322- String [] args = {"../" };
323-
324- // set rate limiting cooldown time to 1s
325- Utils .setValueInConfig ("totp_rate_limit_cooldown_sec" , "1" );
326- // set max attempts to 3
327- Utils .setValueInConfig ("totp_max_attempts" , "3" );
328-
329- TestingProcessManager .TestingProcess process = TestingProcessManager .start (args );
330- assertNotNull (process .checkOrWaitForEvent (ProcessState .PROCESS_STATE .STARTED ));
331-
332- if (StorageLayer .getStorage (process .getProcess ()).getType () != STORAGE_TYPE .SQL ) {
333- return ;
321+ public void rateLimitCooldownTest () throws Throwable {
322+ // Flaky test.
323+ Throwable lastException = null ;
324+
325+ for (int i = 0 ; i < 5 ; i ++) {
326+ String [] args = {"../" };
327+
328+ // set rate limiting cooldown time to 1s
329+ Utils .setValueInConfig ("totp_rate_limit_cooldown_sec" , "1" );
330+ // set max attempts to 3
331+ Utils .setValueInConfig ("totp_max_attempts" , "3" );
332+
333+ TestingProcessManager .TestingProcess process = TestingProcessManager .start (args );
334+ assertNotNull (process .checkOrWaitForEvent (ProcessState .PROCESS_STATE .STARTED ));
335+
336+ if (StorageLayer .getStorage (process .getProcess ()).getType () != STORAGE_TYPE .SQL ) {
337+ return ;
338+ }
339+
340+ try {
341+ FeatureFlagTestContent .getInstance (process .main )
342+ .setKeyValue (FeatureFlagTestContent .ENABLED_FEATURES , new EE_FEATURES []{EE_FEATURES .MFA });
343+
344+ Main main = process .getProcess ();
345+
346+ // Create device
347+ TOTPDevice device = Totp .registerDevice (main , "user" , "deviceName" , 1 , 1 );
348+ Totp .verifyDevice (main , "user" , device .deviceName , generateTotpCode (main , device , -1 ));
349+
350+ // Trigger rate limiting and fix it with a correct code after some time:
351+ int attemptsRequired = triggerAndCheckRateLimit (main , device );
352+ assert attemptsRequired == 3 ;
353+ // Wait for 1 second (Should cool down rate limiting):
354+ Thread .sleep (1000 );
355+ // But again try with invalid code:
356+ InvalidTotpException invalidTotpException ;
357+ for (int tries = 0 ; tries < 10 ; tries ++) {
358+ invalidTotpException = assertThrows (InvalidTotpException .class ,
359+ () -> Totp .verifyCode (main , "user" , "invalid0" ));
360+ if (invalidTotpException .currentAttempts == 3 ) {
361+ break ;
362+ }
363+ }
364+
365+ // This triggered rate limiting again. So even valid codes will fail for
366+ // another cooldown period:
367+ LimitReachedException limitReachedException = assertThrows (LimitReachedException .class ,
368+ () -> Totp .verifyCode (main , "user" , generateTotpCode (main , device )));
369+ assertEquals (3 , limitReachedException .currentAttempts );
370+ // Wait for 1 second (Should cool down rate limiting):
371+ Thread .sleep (1000 );
372+
373+ // test that after cool down, we can retry invalid codes N times again
374+ for (int tries = 0 ; tries < 10 ; tries ++) {
375+ invalidTotpException = assertThrows (InvalidTotpException .class ,
376+ () -> Totp .verifyCode (main , "user" , "invalid0" ));
377+ if (invalidTotpException .currentAttempts == 3 ) {
378+ break ;
379+ }
380+ }
381+
382+ Thread .sleep (1100 );
383+
384+ // Now try with valid code:
385+ Totp .verifyCode (main , "user" , generateTotpCode (main , device ));
386+ // Now invalid code shouldn't trigger rate limiting. Unless you do it N times:
387+ assertThrows (InvalidTotpException .class , () -> Totp .verifyCode (main , "user" , "invaldd" ));
388+
389+ process .kill ();
390+ assertNotNull (process .checkOrWaitForEvent (ProcessState .PROCESS_STATE .STOPPED ));
391+
392+ return ; // successful
393+ } catch (Exception | AssertionError e ) {
394+ process .kill ();
395+ assertNotNull (process .checkOrWaitForEvent (ProcessState .PROCESS_STATE .STOPPED ));
396+
397+ Thread .sleep (250 );
398+
399+ lastException = e ;
400+ }
401+ }
402+ if (lastException != null ) {
403+ throw lastException ;
334404 }
335-
336- FeatureFlagTestContent .getInstance (process .main )
337- .setKeyValue (FeatureFlagTestContent .ENABLED_FEATURES , new EE_FEATURES []{EE_FEATURES .MFA });
338-
339- Main main = process .getProcess ();
340-
341- // Create device
342- TOTPDevice device = Totp .registerDevice (main , "user" , "deviceName" , 1 , 1 );
343- Totp .verifyDevice (main , "user" , device .deviceName , generateTotpCode (main , device , -1 ));
344-
345- // Trigger rate limiting and fix it with a correct code after some time:
346- int attemptsRequired = triggerAndCheckRateLimit (main , device );
347- assert attemptsRequired == 3 ;
348- // Wait for 1 second (Should cool down rate limiting):
349- Thread .sleep (1000 );
350- // But again try with invalid code:
351- InvalidTotpException invalidTotpException = assertThrows (InvalidTotpException .class ,
352- () -> Totp .verifyCode (main , "user" , "invalid0" ));
353- assertEquals (1 , invalidTotpException .currentAttempts );
354- invalidTotpException = assertThrows (InvalidTotpException .class ,
355- () -> Totp .verifyCode (main , "user" , "invalid0" ));
356- assertEquals (2 , invalidTotpException .currentAttempts );
357- invalidTotpException = assertThrows (InvalidTotpException .class ,
358- () -> Totp .verifyCode (main , "user" , "invalid0" ));
359- assertEquals (3 , invalidTotpException .currentAttempts );
360-
361- // This triggered rate limiting again. So even valid codes will fail for
362- // another cooldown period:
363- LimitReachedException limitReachedException = assertThrows (LimitReachedException .class ,
364- () -> Totp .verifyCode (main , "user" , generateTotpCode (main , device )));
365- assertEquals (3 , limitReachedException .currentAttempts );
366- // Wait for 1 second (Should cool down rate limiting):
367- Thread .sleep (1000 );
368-
369- // test that after cool down, we can retry invalid codes N times again
370- invalidTotpException = assertThrows (InvalidTotpException .class ,
371- () -> Totp .verifyCode (main , "user" , "invalid0" ));
372- assertEquals (1 , invalidTotpException .currentAttempts );
373- invalidTotpException = assertThrows (InvalidTotpException .class ,
374- () -> Totp .verifyCode (main , "user" , "invalid0" ));
375- assertEquals (2 , invalidTotpException .currentAttempts );
376- invalidTotpException = assertThrows (InvalidTotpException .class ,
377- () -> Totp .verifyCode (main , "user" , "invalid0" ));
378- assertEquals (3 , invalidTotpException .currentAttempts );
379-
380- Thread .sleep (1100 );
381-
382- // Now try with valid code:
383- Totp .verifyCode (main , "user" , generateTotpCode (main , device ));
384- // Now invalid code shouldn't trigger rate limiting. Unless you do it N times:
385- assertThrows (InvalidTotpException .class , () -> Totp .verifyCode (main , "user" , "invaldd" ));
386405 }
387406
388407 @ Test
@@ -460,82 +479,102 @@ public void createAndVerifyDeviceTest() throws Exception {
460479 }
461480
462481 @ Test
463- public void removeDeviceTest () throws Exception {
482+ public void removeDeviceTest () throws Throwable {
464483 // Flaky test.
465- TestSetupResult result = defaultInit ();
466- if (result == null ) {
467- return ;
484+ Throwable lastException = null ;
485+
486+ for (int i = 0 ; i < 5 ; i ++) {
487+ TestSetupResult result = defaultInit ();
488+ if (result == null ) {
489+ return ;
490+ }
491+
492+ try {
493+ Main main = result .process .getProcess ();
494+ TOTPStorage storage = result .storage ;
495+
496+ // Create devices
497+ TOTPDevice device1 = Totp .registerDevice (main , "user" , "device1" , 1 , 30 );
498+ TOTPDevice device2 = Totp .registerDevice (main , "user" , "device2" , 1 , 30 );
499+
500+ Thread .sleep (1 );
501+ Totp .verifyDevice (main , "user" , "device1" , generateTotpCode (main , device1 , -1 ));
502+ Thread .sleep (1 );
503+ Totp .verifyDevice (main , "user" , "device2" , generateTotpCode (main , device2 , -1 ));
504+
505+ TOTPDevice [] devices = Totp .getDevices (main , "user" );
506+ assert (devices .length == 2 );
507+
508+ // Try to delete device for non-existent user:
509+ assertThrows (UnknownDeviceException .class , () -> Totp .removeDevice (main , "non-existent-user" , "device1" ));
510+
511+ // Try to delete non-existent device:
512+ assertThrows (UnknownDeviceException .class , () -> Totp .removeDevice (main , "user" , "non-existent-device" ));
513+
514+ // Delete one of the devices
515+ {
516+ assertThrows (InvalidTotpException .class , () -> Totp .verifyCode (main , "user" , "ic0" ));
517+
518+ Thread .sleep (1000 - System .currentTimeMillis () % 1000 + 10 );
519+
520+ Thread .sleep (1 );
521+ Totp .verifyCode (main , "user" , generateTotpCode (main , device1 ));
522+ Thread .sleep (1 );
523+ Totp .verifyCode (main , "user" , generateTotpCode (main , device2 ));
524+
525+ // Delete device1
526+ Totp .removeDevice (main , "user" , "device1" );
527+
528+ // 1 device still remain so all codes should still be there:
529+ TOTPUsedCode [] usedCodes = getAllUsedCodesUtil (storage , "user" );
530+ assert (usedCodes .length == 5 ); // 2 for device verification and 3 for code verification
531+
532+ devices = Totp .getDevices (main , "user" );
533+ assert (devices .length == 1 );
534+ }
535+
536+ // Deleting the last device of a user should delete all related codes:
537+ // Delete the 2nd (and the last) device
538+ {
539+
540+ // Create another user to test that other users aren't affected:
541+ TOTPDevice otherUserDevice = Totp .registerDevice (main , "other-user" , "device" , 1 , 30 );
542+ Totp .verifyDevice (main , "other-user" , "device" , generateTotpCode (main , otherUserDevice , -1 ));
543+ Thread .sleep (1 );
544+ Totp .verifyCode (main , "other-user" , generateTotpCode (main , otherUserDevice ));
545+ assertThrows (InvalidTotpException .class , () -> Totp .verifyCode (main , "other-user" , "ic1" ));
546+
547+ // Delete device2
548+ Totp .removeDevice (main , "user" , "device2" );
549+
550+ // No more devices are left for the user:
551+ assert (Totp .getDevices (main , "user" ).length == 0 );
552+
553+ // No device left so all codes of the user should be deleted:
554+ TOTPUsedCode [] usedCodes = getAllUsedCodesUtil (storage , "user" );
555+ assert (usedCodes .length == 0 );
556+
557+ usedCodes = getAllUsedCodesUtil (storage , "other-user" );
558+ System .out .println ("Point2 " + usedCodes .length );
559+ assert (usedCodes .length == 3 ); // 1 for device verification and 2 for code verification
560+
561+ // But for other users things should still be there:
562+ TOTPDevice [] otherUserDevices = Totp .getDevices (main , "other-user" );
563+ assert (otherUserDevices .length == 1 );
564+ }
565+
566+ result .process .kill ();
567+ assertNotNull (result .process .checkOrWaitForEvent (ProcessState .PROCESS_STATE .STOPPED ));
568+
569+ return ; // successful
570+ } catch (Exception | AssertionError e ) {
571+ lastException = e ;
572+ result .process .kill ();
573+ assertNotNull (result .process .checkOrWaitForEvent (ProcessState .PROCESS_STATE .STOPPED ));
574+ }
468575 }
469- Main main = result .process .getProcess ();
470- TOTPStorage storage = result .storage ;
471-
472- // Create devices
473- TOTPDevice device1 = Totp .registerDevice (main , "user" , "device1" , 1 , 30 );
474- TOTPDevice device2 = Totp .registerDevice (main , "user" , "device2" , 1 , 30 );
475-
476- Thread .sleep (1 );
477- Totp .verifyDevice (main , "user" , "device1" , generateTotpCode (main , device1 , -1 ));
478- Thread .sleep (1 );
479- Totp .verifyDevice (main , "user" , "device2" , generateTotpCode (main , device2 , -1 ));
480-
481- TOTPDevice [] devices = Totp .getDevices (main , "user" );
482- assert (devices .length == 2 );
483-
484- // Try to delete device for non-existent user:
485- assertThrows (UnknownDeviceException .class , () -> Totp .removeDevice (main , "non-existent-user" , "device1" ));
486-
487- // Try to delete non-existent device:
488- assertThrows (UnknownDeviceException .class , () -> Totp .removeDevice (main , "user" , "non-existent-device" ));
489-
490- // Delete one of the devices
491- {
492- assertThrows (InvalidTotpException .class , () -> Totp .verifyCode (main , "user" , "ic0" ));
493-
494- Thread .sleep (1000 - System .currentTimeMillis () % 1000 + 10 );
495-
496- Thread .sleep (1 );
497- Totp .verifyCode (main , "user" , generateTotpCode (main , device1 ));
498- Thread .sleep (1 );
499- Totp .verifyCode (main , "user" , generateTotpCode (main , device2 ));
500-
501- // Delete device1
502- Totp .removeDevice (main , "user" , "device1" );
503-
504- devices = Totp .getDevices (main , "user" );
505- assert (devices .length == 1 );
506-
507- // 1 device still remain so all codes should still be still there:
508- TOTPUsedCode [] usedCodes = getAllUsedCodesUtil (storage , "user" );
509- assert (usedCodes .length == 5 ); // 2 for device verification and 3 for code verification
510- }
511-
512- // Deleting the last device of a user should delete all related codes:
513- // Delete the 2nd (and the last) device
514- {
515-
516- // Create another user to test that other users aren't affected:
517- TOTPDevice otherUserDevice = Totp .registerDevice (main , "other-user" , "device" , 1 , 30 );
518- Totp .verifyDevice (main , "other-user" , "device" , generateTotpCode (main , otherUserDevice , -1 ));
519- Thread .sleep (1 );
520- Totp .verifyCode (main , "other-user" , generateTotpCode (main , otherUserDevice ));
521- assertThrows (InvalidTotpException .class , () -> Totp .verifyCode (main , "other-user" , "ic1" ));
522-
523- // Delete device2
524- Totp .removeDevice (main , "user" , "device2" );
525-
526- // No more devices are left for the user:
527- assert (Totp .getDevices (main , "user" ).length == 0 );
528-
529- // No device left so all codes of the user should be deleted:
530- TOTPUsedCode [] usedCodes = getAllUsedCodesUtil (storage , "user" );
531- assert (usedCodes .length == 0 );
532-
533- // But for other users things should still be there:
534- TOTPDevice [] otherUserDevices = Totp .getDevices (main , "other-user" );
535- assert (otherUserDevices .length == 1 );
536-
537- usedCodes = getAllUsedCodesUtil (storage , "other-user" );
538- assert (usedCodes .length == 3 ); // 1 for device verification and 2 for code verification
576+ if (lastException != null ) {
577+ throw lastException ;
539578 }
540579 }
541580
0 commit comments