Skip to content

Commit 9cf9966

Browse files
sureshanapartiPearl1594
authored andcommitted
Keep same/consistent auth time for valid & invalid users
1 parent c3c6d34 commit 9cf9966

File tree

1 file changed

+119
-97
lines changed

1 file changed

+119
-97
lines changed

server/src/main/java/com/cloud/user/AccountManagerImpl.java

Lines changed: 119 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)