Skip to content

Commit ad9d9cd

Browse files
sureshanapartiDaan Hoogland
authored andcommitted
Keep same/consistent auth time for valid & invalid users
1 parent 38f3107 commit ad9d9cd

File tree

1 file changed

+124
-98
lines changed

1 file changed

+124
-98
lines changed

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

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

Comments
 (0)