@@ -343,6 +343,9 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
343343
344344 private List <UserTwoFactorAuthenticator > userTwoFactorAuthenticationProviders ;
345345
346+ private long validUserLastAuthTimeDurationInMs = 0L ;
347+ private static final long DEFAULT_USER_AUTH_TIME_DURATION_MS = 350L ;
348+
346349 public static ConfigKey <Boolean > enableUserTwoFactorAuthentication = new ConfigKey <>("Advanced" ,
347350 Boolean .class ,
348351 "enable.user.2fa" ,
@@ -1557,7 +1560,7 @@ protected void validateCurrentPassword(UserVO user, String currentPassword) {
15571560 for (UserAuthenticator userAuthenticator : _userPasswordEncoders ) {
15581561 Pair <Boolean , ActionOnFailedAuthentication > authenticationResult = userAuthenticator .authenticate (user .getUsername (), currentPassword , userAccount .getDomainId (), null );
15591562 if (authenticationResult == null ) {
1560- s_logger .trace (String .format ("Authenticator [%s] is returning null for the authenticate mehtod ." , userAuthenticator .getClass ()));
1563+ s_logger .trace (String .format ("Authenticator [%s] is returning null for the authenticate method ." , userAuthenticator .getClass ()));
15611564 continue ;
15621565 }
15631566 if (BooleanUtils .toBoolean (authenticationResult .first ())) {
@@ -2561,107 +2564,17 @@ public void logoutUser(long userId) {
25612564
25622565 @ Override
25632566 public UserAccount authenticateUser (final String username , final String password , final Long domainId , final InetAddress loginIpAddress , final Map <String , Object []> requestParameters ) {
2567+ long authStartTimeInMs = System .currentTimeMillis ();
25642568 UserAccount user = null ;
25652569 final String [] oAuthProviderArray = (String [])requestParameters .get (ApiConstants .PROVIDER );
25662570 final String [] secretCodeArray = (String [])requestParameters .get (ApiConstants .SECRET_CODE );
25672571 String oauthProvider = ((oAuthProviderArray == null ) ? null : oAuthProviderArray [0 ]);
25682572 String secretCode = ((secretCodeArray == null ) ? null : secretCodeArray [0 ]);
25692573
2570-
25712574 if ((password != null && !password .isEmpty ()) || (oauthProvider != null && secretCode != null )) {
25722575 user = getUserAccount (username , password , domainId , requestParameters );
25732576 } else {
2574- String key = _configDao .getValue ("security.singlesignon.key" );
2575- if (key == null ) {
2576- // the SSO key is gone, don't authenticate
2577- return null ;
2578- }
2579-
2580- String singleSignOnTolerance = _configDao .getValue ("security.singlesignon.tolerance.millis" );
2581- if (singleSignOnTolerance == null ) {
2582- // the SSO tolerance is gone (how much time before/after system time we'll allow the login request to be
2583- // valid),
2584- // don't authenticate
2585- return null ;
2586- }
2587-
2588- long tolerance = Long .parseLong (singleSignOnTolerance );
2589- String signature = null ;
2590- long timestamp = 0L ;
2591- String unsignedRequest = null ;
2592- StringBuffer unsignedRequestBuffer = new StringBuffer ();
2593-
2594- // - build a request string with sorted params, make sure it's all lowercase
2595- // - sign the request, verify the signature is the same
2596- List <String > parameterNames = new ArrayList <String >();
2597-
2598- for (Object paramNameObj : requestParameters .keySet ()) {
2599- parameterNames .add ((String )paramNameObj ); // put the name in a list that we'll sort later
2600- }
2601-
2602- Collections .sort (parameterNames );
2603-
2604- try {
2605- for (String paramName : parameterNames ) {
2606- // parameters come as name/value pairs in the form String/String[]
2607- String paramValue = ((String [])requestParameters .get (paramName ))[0 ];
2608-
2609- if ("signature" .equalsIgnoreCase (paramName )) {
2610- signature = paramValue ;
2611- } else {
2612- if ("timestamp" .equalsIgnoreCase (paramName )) {
2613- String timestampStr = paramValue ;
2614- try {
2615- // If the timestamp is in a valid range according to our tolerance, verify the request
2616- // signature, otherwise return null to indicate authentication failure
2617- timestamp = Long .parseLong (timestampStr );
2618- long currentTime = System .currentTimeMillis ();
2619- if (Math .abs (currentTime - timestamp ) > tolerance ) {
2620- if (s_logger .isDebugEnabled ()) {
2621- s_logger .debug ("Expired timestamp passed in to login, current time = " + currentTime + ", timestamp = " + timestamp );
2622- }
2623- return null ;
2624- }
2625- } catch (NumberFormatException nfe ) {
2626- if (s_logger .isDebugEnabled ()) {
2627- s_logger .debug ("Invalid timestamp passed in to login: " + timestampStr );
2628- }
2629- return null ;
2630- }
2631- }
2632-
2633- if (unsignedRequestBuffer .length () != 0 ) {
2634- unsignedRequestBuffer .append ("&" );
2635- }
2636- unsignedRequestBuffer .append (paramName ).append ("=" ).append (URLEncoder .encode (paramValue , "UTF-8" ));
2637- }
2638- }
2639-
2640- if ((signature == null ) || (timestamp == 0L )) {
2641- if (s_logger .isDebugEnabled ()) {
2642- s_logger .debug ("Missing parameters in login request, signature = " + signature + ", timestamp = " + timestamp );
2643- }
2644- return null ;
2645- }
2646-
2647- unsignedRequest = unsignedRequestBuffer .toString ().toLowerCase ().replaceAll ("\\ +" , "%20" );
2648-
2649- Mac mac = Mac .getInstance ("HmacSHA1" );
2650- SecretKeySpec keySpec = new SecretKeySpec (key .getBytes (), "HmacSHA1" );
2651- mac .init (keySpec );
2652- mac .update (unsignedRequest .getBytes ());
2653- byte [] encryptedBytes = mac .doFinal ();
2654- String computedSignature = new String (Base64 .encodeBase64 (encryptedBytes ));
2655- boolean equalSig = ConstantTimeComparator .compareStrings (signature , computedSignature );
2656- if (!equalSig ) {
2657- s_logger .info ("User signature: " + signature + " is not equaled to computed signature: " + computedSignature );
2658- } else {
2659- user = _userAccountDao .getUserAccount (username , domainId );
2660- }
2661- } catch (Exception ex ) {
2662- s_logger .error ("Exception authenticating user" , ex );
2663- return null ;
2664- }
2577+ user = getUserAccountForSSO (username , domainId , requestParameters );
26652578 }
26662579
26672580 if (user != null ) {
@@ -2695,18 +2608,35 @@ public UserAccount authenticateUser(final String username, final String password
26952608 }
26962609 }
26972610
2611+ 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 ());
2612+
2613+ validUserLastAuthTimeDurationInMs = System .currentTimeMillis () - authStartTimeInMs ;
26982614 // Here all is fine!
26992615 if (s_logger .isDebugEnabled ()) {
2700- s_logger .debug ("User: " + username + " in domain " + domainId + " has successfully logged in" );
2616+ s_logger .debug (String . format ( "User: %s in domain %d has successfully logged in, auth time duration - %d ms" , username , domainId , validUserLastAuthTimeDurationInMs ) );
27012617 }
27022618
2703- 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 ());
2704-
27052619 return user ;
27062620 } else {
27072621 if (s_logger .isDebugEnabled ()) {
27082622 s_logger .debug ("User: " + username + " in domain " + domainId + " has failed to log in" );
27092623 }
2624+
2625+ long waitTimeDurationInMs ;
2626+ long invalidUserAuthTimeDurationInMs = System .currentTimeMillis () - authStartTimeInMs ;
2627+ if (validUserLastAuthTimeDurationInMs > 0 ) {
2628+ waitTimeDurationInMs = validUserLastAuthTimeDurationInMs - invalidUserAuthTimeDurationInMs ;
2629+ } else {
2630+ waitTimeDurationInMs = DEFAULT_USER_AUTH_TIME_DURATION_MS - invalidUserAuthTimeDurationInMs ;
2631+ }
2632+
2633+ if (waitTimeDurationInMs > 0 ) {
2634+ try {
2635+ Thread .sleep (waitTimeDurationInMs );
2636+ } catch (final InterruptedException e ) {
2637+ }
2638+ }
2639+
27102640 return null ;
27112641 }
27122642 }
@@ -2718,7 +2648,7 @@ private UserAccount getUserAccount(String username, String password, Long domain
27182648 UserAccount userAccount = _userAccountDao .getUserAccount (username , domainId );
27192649
27202650 boolean authenticated = false ;
2721- HashSet <ActionOnFailedAuthentication > actionsOnFailedAuthenticaion = new HashSet <ActionOnFailedAuthentication >();
2651+ HashSet <ActionOnFailedAuthentication > actionsOnFailedAuthenticaion = new HashSet <>();
27222652 User .Source userSource = userAccount != null ? userAccount .getSource () : User .Source .UNKNOWN ;
27232653 for (UserAuthenticator authenticator : _userAuthenticators ) {
27242654 final String [] secretCodeArray = (String [])requestParameters .get (ApiConstants .SECRET_CODE );
@@ -2744,7 +2674,6 @@ private UserAccount getUserAccount(String username, String password, Long domain
27442674 boolean updateIncorrectLoginCount = actionsOnFailedAuthenticaion .contains (ActionOnFailedAuthentication .INCREMENT_INCORRECT_LOGIN_ATTEMPT_COUNT );
27452675
27462676 if (authenticated ) {
2747-
27482677 Domain domain = _domainMgr .getDomain (domainId );
27492678 String domainName = null ;
27502679 if (domain != null ) {
@@ -2786,6 +2715,103 @@ private UserAccount getUserAccount(String username, String password, Long domain
27862715 }
27872716 }
27882717
2718+ private UserAccount getUserAccountForSSO (String username , Long domainId , Map <String , Object []> requestParameters ) {
2719+ String key = _configDao .getValue ("security.singlesignon.key" );
2720+ if (key == null ) {
2721+ // the SSO key is gone, don't authenticate
2722+ return null ;
2723+ }
2724+
2725+ String singleSignOnTolerance = _configDao .getValue ("security.singlesignon.tolerance.millis" );
2726+ if (singleSignOnTolerance == null ) {
2727+ // the SSO tolerance is gone (how much time before/after system time we'll allow the login request to be
2728+ // valid),
2729+ // don't authenticate
2730+ return null ;
2731+ }
2732+
2733+ UserAccount user = null ;
2734+ long tolerance = Long .parseLong (singleSignOnTolerance );
2735+ String signature = null ;
2736+ long timestamp = 0L ;
2737+ String unsignedRequest ;
2738+ StringBuffer unsignedRequestBuffer = new StringBuffer ();
2739+
2740+ // - build a request string with sorted params, make sure it's all lowercase
2741+ // - sign the request, verify the signature is the same
2742+ List <String > parameterNames = new ArrayList <>();
2743+
2744+ for (Object paramNameObj : requestParameters .keySet ()) {
2745+ parameterNames .add ((String )paramNameObj ); // put the name in a list that we'll sort later
2746+ }
2747+
2748+ Collections .sort (parameterNames );
2749+
2750+ try {
2751+ for (String paramName : parameterNames ) {
2752+ // parameters come as name/value pairs in the form String/String[]
2753+ String paramValue = ((String [])requestParameters .get (paramName ))[0 ];
2754+
2755+ if ("signature" .equalsIgnoreCase (paramName )) {
2756+ signature = paramValue ;
2757+ } else {
2758+ if ("timestamp" .equalsIgnoreCase (paramName )) {
2759+ String timestampStr = paramValue ;
2760+ try {
2761+ // If the timestamp is in a valid range according to our tolerance, verify the request
2762+ // signature, otherwise return null to indicate authentication failure
2763+ timestamp = Long .parseLong (timestampStr );
2764+ long currentTime = System .currentTimeMillis ();
2765+ if (Math .abs (currentTime - timestamp ) > tolerance ) {
2766+ if (s_logger .isDebugEnabled ()) {
2767+ s_logger .debug ("Expired timestamp passed in to login, current time = " + currentTime + ", timestamp = " + timestamp );
2768+ }
2769+ return null ;
2770+ }
2771+ } catch (NumberFormatException nfe ) {
2772+ if (s_logger .isDebugEnabled ()) {
2773+ s_logger .debug ("Invalid timestamp passed in to login: " + timestampStr );
2774+ }
2775+ return null ;
2776+ }
2777+ }
2778+
2779+ if (unsignedRequestBuffer .length () != 0 ) {
2780+ unsignedRequestBuffer .append ("&" );
2781+ }
2782+ unsignedRequestBuffer .append (paramName ).append ("=" ).append (URLEncoder .encode (paramValue , "UTF-8" ));
2783+ }
2784+ }
2785+
2786+ if ((signature == null ) || (timestamp == 0L )) {
2787+ if (s_logger .isDebugEnabled ()) {
2788+ s_logger .debug ("Missing parameters in login request, signature = " + signature + ", timestamp = " + timestamp );
2789+ }
2790+ return null ;
2791+ }
2792+
2793+ unsignedRequest = unsignedRequestBuffer .toString ().toLowerCase ().replaceAll ("\\ +" , "%20" );
2794+
2795+ Mac mac = Mac .getInstance ("HmacSHA1" );
2796+ SecretKeySpec keySpec = new SecretKeySpec (key .getBytes (), "HmacSHA1" );
2797+ mac .init (keySpec );
2798+ mac .update (unsignedRequest .getBytes ());
2799+ byte [] encryptedBytes = mac .doFinal ();
2800+ String computedSignature = new String (Base64 .encodeBase64 (encryptedBytes ));
2801+ boolean equalSig = ConstantTimeComparator .compareStrings (signature , computedSignature );
2802+ if (!equalSig ) {
2803+ s_logger .info ("User signature: " + signature + " is not equaled to computed signature: " + computedSignature );
2804+ } else {
2805+ user = _userAccountDao .getUserAccount (username , domainId );
2806+ }
2807+ } catch (Exception ex ) {
2808+ s_logger .error ("Exception authenticating user" , ex );
2809+ return null ;
2810+ }
2811+
2812+ return user ;
2813+ }
2814+
27892815 protected void updateLoginAttemptsWhenIncorrectLoginAttemptsEnabled (UserAccount account , boolean updateIncorrectLoginCount ,
27902816 int allowedLoginAttempts ) {
27912817 int attemptsMade = account .getLoginAttempts () + 1 ;
0 commit comments