@@ -350,6 +350,9 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
350350
351351 private List <UserTwoFactorAuthenticator > userTwoFactorAuthenticationProviders ;
352352
353+ private long validUserLastAuthTimeDurationInMs = 0L ;
354+ private static final long DEFAULT_USER_AUTH_TIME_DURATION_MS = 350L ;
355+
353356 public static ConfigKey <Boolean > enableUserTwoFactorAuthentication = new ConfigKey <>("Advanced" ,
354357 Boolean .class ,
355358 "enable.user.2fa" ,
@@ -1599,7 +1602,7 @@ protected void validateCurrentPassword(UserVO user, String currentPassword) {
15991602 for (UserAuthenticator userAuthenticator : _userPasswordEncoders ) {
16001603 Pair <Boolean , ActionOnFailedAuthentication > authenticationResult = userAuthenticator .authenticate (user .getUsername (), currentPassword , userAccount .getDomainId (), null );
16011604 if (authenticationResult == null ) {
1602- logger .trace (String .format ("Authenticator [%s] is returning null for the authenticate mehtod ." , userAuthenticator .getClass ()));
1605+ logger .trace (String .format ("Authenticator [%s] is returning null for the authenticate method ." , userAuthenticator .getClass ()));
16031606 continue ;
16041607 }
16051608 if (BooleanUtils .toBoolean (authenticationResult .first ())) {
@@ -2644,107 +2647,17 @@ public void logoutUser(long userId) {
26442647
26452648 @ Override
26462649 public UserAccount authenticateUser (final String username , final String password , final Long domainId , final InetAddress loginIpAddress , final Map <String , Object []> requestParameters ) {
2650+ long authStartTimeInMs = System .currentTimeMillis ();
26472651 UserAccount user = null ;
26482652 final String [] oAuthProviderArray = (String [])requestParameters .get (ApiConstants .PROVIDER );
26492653 final String [] secretCodeArray = (String [])requestParameters .get (ApiConstants .SECRET_CODE );
26502654 String oauthProvider = ((oAuthProviderArray == null ) ? null : oAuthProviderArray [0 ]);
26512655 String secretCode = ((secretCodeArray == null ) ? null : secretCodeArray [0 ]);
26522656
2653-
26542657 if ((password != null && !password .isEmpty ()) || (oauthProvider != null && secretCode != null )) {
26552658 user = getUserAccount (username , password , domainId , requestParameters );
26562659 } else {
2657- String key = _configDao .getValue ("security.singlesignon.key" );
2658- if (key == null ) {
2659- // the SSO key is gone, don't authenticate
2660- return null ;
2661- }
2662-
2663- String singleSignOnTolerance = _configDao .getValue ("security.singlesignon.tolerance.millis" );
2664- if (singleSignOnTolerance == null ) {
2665- // the SSO tolerance is gone (how much time before/after system time we'll allow the login request to be
2666- // valid),
2667- // don't authenticate
2668- return null ;
2669- }
2670-
2671- long tolerance = Long .parseLong (singleSignOnTolerance );
2672- String signature = null ;
2673- long timestamp = 0L ;
2674- String unsignedRequest ;
2675- StringBuffer unsignedRequestBuffer = new StringBuffer ();
2676-
2677- // - build a request string with sorted params, make sure it's all lowercase
2678- // - sign the request, verify the signature is the same
2679- List <String > parameterNames = new ArrayList <>();
2680-
2681- for (Object paramNameObj : requestParameters .keySet ()) {
2682- parameterNames .add ((String )paramNameObj ); // put the name in a list that we'll sort later
2683- }
2684-
2685- Collections .sort (parameterNames );
2686-
2687- try {
2688- for (String paramName : parameterNames ) {
2689- // parameters come as name/value pairs in the form String/String[]
2690- String paramValue = ((String [])requestParameters .get (paramName ))[0 ];
2691-
2692- if ("signature" .equalsIgnoreCase (paramName )) {
2693- signature = paramValue ;
2694- } else {
2695- if ("timestamp" .equalsIgnoreCase (paramName )) {
2696- String timestampStr = paramValue ;
2697- try {
2698- // If the timestamp is in a valid range according to our tolerance, verify the request
2699- // signature, otherwise return null to indicate authentication failure
2700- timestamp = Long .parseLong (timestampStr );
2701- long currentTime = System .currentTimeMillis ();
2702- if (Math .abs (currentTime - timestamp ) > tolerance ) {
2703- if (logger .isDebugEnabled ()) {
2704- logger .debug ("Expired timestamp passed in to login, current time = " + currentTime + ", timestamp = " + timestamp );
2705- }
2706- return null ;
2707- }
2708- } catch (NumberFormatException nfe ) {
2709- if (logger .isDebugEnabled ()) {
2710- logger .debug ("Invalid timestamp passed in to login: " + timestampStr );
2711- }
2712- return null ;
2713- }
2714- }
2715-
2716- if (unsignedRequestBuffer .length () != 0 ) {
2717- unsignedRequestBuffer .append ("&" );
2718- }
2719- unsignedRequestBuffer .append (paramName ).append ("=" ).append (URLEncoder .encode (paramValue , "UTF-8" ));
2720- }
2721- }
2722-
2723- if ((signature == null ) || (timestamp == 0L )) {
2724- if (logger .isDebugEnabled ()) {
2725- logger .debug ("Missing parameters in login request, signature = " + signature + ", timestamp = " + timestamp );
2726- }
2727- return null ;
2728- }
2729-
2730- unsignedRequest = unsignedRequestBuffer .toString ().toLowerCase ().replaceAll ("\\ +" , "%20" );
2731-
2732- Mac mac = Mac .getInstance ("HmacSHA1" );
2733- SecretKeySpec keySpec = new SecretKeySpec (key .getBytes (), "HmacSHA1" );
2734- mac .init (keySpec );
2735- mac .update (unsignedRequest .getBytes ());
2736- byte [] encryptedBytes = mac .doFinal ();
2737- String computedSignature = new String (Base64 .encodeBase64 (encryptedBytes ));
2738- boolean equalSig = ConstantTimeComparator .compareStrings (signature , computedSignature );
2739- if (!equalSig ) {
2740- logger .info ("User signature: " + signature + " is not equaled to computed signature: " + computedSignature );
2741- } else {
2742- user = _userAccountDao .getUserAccount (username , domainId );
2743- }
2744- } catch (Exception ex ) {
2745- logger .error ("Exception authenticating user" , ex );
2746- return null ;
2747- }
2660+ user = getUserAccountForSSO (username , domainId , requestParameters );
27482661 }
27492662
27502663 if (user != null ) {
@@ -2778,18 +2691,35 @@ public UserAccount authenticateUser(final String username, final String password
27782691 }
27792692 }
27802693
2694+ ActionEventUtils .onActionEvent (user .getId (), user .getAccountId (), user .getDomainId (), EventTypes .EVENT_USER_LOGIN , "user has logged in from IP Address " + loginIpAddress , user .getId (), ApiCommandResourceType .User .toString ());
2695+
2696+ validUserLastAuthTimeDurationInMs = System .currentTimeMillis () - authStartTimeInMs ;
27812697 // Here all is fine!
27822698 if (logger .isDebugEnabled ()) {
2783- logger .debug ("User: " + username + " in domain " + domainId + " has successfully logged in" );
2699+ logger .debug (String . format ( "User: %s in domain %d has successfully logged in, auth time duration - %d ms" , username , domainId , validUserLastAuthTimeDurationInMs ) );
27842700 }
27852701
2786- ActionEventUtils .onActionEvent (user .getId (), user .getAccountId (), user .getDomainId (), EventTypes .EVENT_USER_LOGIN , "user has logged in from IP Address " + loginIpAddress , user .getId (), ApiCommandResourceType .User .toString ());
2787-
27882702 return user ;
27892703 } else {
27902704 if (logger .isDebugEnabled ()) {
27912705 logger .debug ("User: " + username + " in domain " + domainId + " has failed to log in" );
27922706 }
2707+
2708+ long waitTimeDurationInMs ;
2709+ long invalidUserAuthTimeDurationInMs = System .currentTimeMillis () - authStartTimeInMs ;
2710+ if (validUserLastAuthTimeDurationInMs > 0 ) {
2711+ waitTimeDurationInMs = validUserLastAuthTimeDurationInMs - invalidUserAuthTimeDurationInMs ;
2712+ } else {
2713+ waitTimeDurationInMs = DEFAULT_USER_AUTH_TIME_DURATION_MS - invalidUserAuthTimeDurationInMs ;
2714+ }
2715+
2716+ if (waitTimeDurationInMs > 0 ) {
2717+ try {
2718+ Thread .sleep (waitTimeDurationInMs );
2719+ } catch (final InterruptedException e ) {
2720+ }
2721+ }
2722+
27932723 return null ;
27942724 }
27952725 }
@@ -2827,7 +2757,6 @@ private UserAccount getUserAccount(String username, String password, Long domain
28272757 boolean updateIncorrectLoginCount = actionsOnFailedAuthenticaion .contains (ActionOnFailedAuthentication .INCREMENT_INCORRECT_LOGIN_ATTEMPT_COUNT );
28282758
28292759 if (authenticated ) {
2830-
28312760 Domain domain = _domainMgr .getDomain (domainId );
28322761 String domainName = null ;
28332762 if (domain != null ) {
@@ -2869,6 +2798,99 @@ private UserAccount getUserAccount(String username, String password, Long domain
28692798 }
28702799 }
28712800
2801+ private UserAccount getUserAccountForSSO (String username , Long domainId , Map <String , Object []> requestParameters ) {
2802+ String key = _configDao .getValue ("security.singlesignon.key" );
2803+ if (key == null ) {
2804+ // the SSO key is gone, don't authenticate
2805+ return null ;
2806+ }
2807+
2808+ String singleSignOnTolerance = _configDao .getValue ("security.singlesignon.tolerance.millis" );
2809+ if (singleSignOnTolerance == null ) {
2810+ // the SSO tolerance is gone (how much time before/after system time we'll allow the login request to be
2811+ // valid),
2812+ // don't authenticate
2813+ return null ;
2814+ }
2815+
2816+ UserAccount user = null ;
2817+ long tolerance = Long .parseLong (singleSignOnTolerance );
2818+ String signature = null ;
2819+ long timestamp = 0L ;
2820+ String unsignedRequest ;
2821+ StringBuffer unsignedRequestBuffer = new StringBuffer ();
2822+
2823+ // - build a request string with sorted params, make sure it's all lowercase
2824+ // - sign the request, verify the signature is the same
2825+ List <String > parameterNames = new ArrayList <>();
2826+
2827+ for (Object paramNameObj : requestParameters .keySet ()) {
2828+ parameterNames .add ((String )paramNameObj ); // put the name in a list that we'll sort later
2829+ }
2830+
2831+ Collections .sort (parameterNames );
2832+
2833+ try {
2834+ for (String paramName : parameterNames ) {
2835+ // parameters come as name/value pairs in the form String/String[]
2836+ String paramValue = ((String [])requestParameters .get (paramName ))[0 ];
2837+
2838+ if ("signature" .equalsIgnoreCase (paramName )) {
2839+ signature = paramValue ;
2840+ } else {
2841+ if ("timestamp" .equalsIgnoreCase (paramName )) {
2842+ String timestampStr = paramValue ;
2843+ try {
2844+ // If the timestamp is in a valid range according to our tolerance, verify the request
2845+ // signature, otherwise return null to indicate authentication failure
2846+ timestamp = Long .parseLong (timestampStr );
2847+ long currentTime = System .currentTimeMillis ();
2848+ if (Math .abs (currentTime - timestamp ) > tolerance ) {
2849+ logger .debug ("Expired timestamp passed in to login, current time = {}, timestamp = {}" , currentTime , timestamp );
2850+ return null ;
2851+ }
2852+ } catch (NumberFormatException nfe ) {
2853+ logger .debug ("Invalid timestamp passed in to login: {}" , timestampStr );
2854+ return null ;
2855+ }
2856+ }
2857+
2858+ if (unsignedRequestBuffer .length () != 0 ) {
2859+ unsignedRequestBuffer .append ("&" );
2860+ }
2861+ unsignedRequestBuffer .append (paramName ).append ("=" ).append (URLEncoder .encode (paramValue , "UTF-8" ));
2862+ }
2863+ }
2864+
2865+ if ((signature == null ) || (timestamp == 0L )) {
2866+ if (logger .isDebugEnabled ()) {
2867+ logger .debug ("Missing parameters in login request, signature = " + signature + ", timestamp = " + timestamp );
2868+ }
2869+ return null ;
2870+ }
2871+
2872+ unsignedRequest = unsignedRequestBuffer .toString ().toLowerCase ().replaceAll ("\\ +" , "%20" );
2873+
2874+ Mac mac = Mac .getInstance ("HmacSHA1" );
2875+ SecretKeySpec keySpec = new SecretKeySpec (key .getBytes (), "HmacSHA1" );
2876+ mac .init (keySpec );
2877+ mac .update (unsignedRequest .getBytes ());
2878+ byte [] encryptedBytes = mac .doFinal ();
2879+ String computedSignature = new String (Base64 .encodeBase64 (encryptedBytes ));
2880+ boolean equalSig = ConstantTimeComparator .compareStrings (signature , computedSignature );
2881+ if (!equalSig ) {
2882+ logger .info ("User signature: " + signature + " is not equaled to computed signature: " + computedSignature );
2883+ } else {
2884+ user = _userAccountDao .getUserAccount (username , domainId );
2885+ }
2886+ } catch (Exception ex ) {
2887+ logger .error ("Exception authenticating user" , ex );
2888+ return null ;
2889+ }
2890+
2891+ return user ;
2892+ }
2893+
28722894 protected void updateLoginAttemptsWhenIncorrectLoginAttemptsEnabled (UserAccount account , boolean updateIncorrectLoginCount ,
28732895 int allowedLoginAttempts ) {
28742896 int attemptsMade = account .getLoginAttempts () + 1 ;
0 commit comments