This is an automated email from the ASF dual-hosted git repository. sureshanaparti pushed a commit to branch 4.20 in repository https://gitbox.apache.org/repos/asf/cloudstack.git
The following commit(s) were added to refs/heads/4.20 by this push: new ed6ee6b704f Mark LDAP user query timeout as incorrect login instead of disabling user immediately (#11220) ed6ee6b704f is described below commit ed6ee6b704ff84eb0efcec92fbcec518a90d30d0 Author: Suresh Kumar Anaparti <sureshkumar.anapa...@gmail.com> AuthorDate: Fri Jul 25 19:31:43 2025 +0530 Mark LDAP user query timeout as incorrect login instead of disabling user immediately (#11220) * Mark LDAP user query timeout as incorrect login instead of disabling user immediately * code improvements --- .../cloudstack/api/command/LdapListUsersCmd.java | 5 +- .../apache/cloudstack/ldap/LdapAuthenticator.java | 58 ++++++++++++++-------- .../apache/cloudstack/ldap/LdapManagerImpl.java | 22 ++++---- 3 files changed, 50 insertions(+), 35 deletions(-) diff --git a/plugins/user-authenticators/ldap/src/main/java/org/apache/cloudstack/api/command/LdapListUsersCmd.java b/plugins/user-authenticators/ldap/src/main/java/org/apache/cloudstack/api/command/LdapListUsersCmd.java index e5d434d3810..6bfc70ea139 100644 --- a/plugins/user-authenticators/ldap/src/main/java/org/apache/cloudstack/api/command/LdapListUsersCmd.java +++ b/plugins/user-authenticators/ldap/src/main/java/org/apache/cloudstack/api/command/LdapListUsersCmd.java @@ -140,10 +140,11 @@ public class LdapListUsersCmd extends BaseListCmd { try { final List<LdapUser> users = _ldapManager.getUsers(domainId); ldapResponses = createLdapUserResponse(users); -// now filter and annotate + // now filter and annotate ldapResponses = applyUserFilter(ldapResponses); } catch (final NoLdapUserMatchingQueryException ex) { - // ok, we'll make do with the empty list ldapResponses = new ArrayList<LdapUserResponse>(); + logger.debug(ex.getMessage()); + // ok, we'll make do with the empty list } finally { response.setResponses(ldapResponses); response.setResponseName(getCommandName()); diff --git a/plugins/user-authenticators/ldap/src/main/java/org/apache/cloudstack/ldap/LdapAuthenticator.java b/plugins/user-authenticators/ldap/src/main/java/org/apache/cloudstack/ldap/LdapAuthenticator.java index b8509881594..36c663566cb 100644 --- a/plugins/user-authenticators/ldap/src/main/java/org/apache/cloudstack/ldap/LdapAuthenticator.java +++ b/plugins/user-authenticators/ldap/src/main/java/org/apache/cloudstack/ldap/LdapAuthenticator.java @@ -45,6 +45,8 @@ public class LdapAuthenticator extends AdapterBase implements UserAuthenticator @Inject private AccountManager _accountManager; + private static final String LDAP_READ_TIMED_OUT_MESSAGE = "LDAP response read timed out"; + public LdapAuthenticator() { super(); } @@ -74,8 +76,8 @@ public class LdapAuthenticator extends AdapterBase implements UserAuthenticator return rc; } List<LdapTrustMapVO> ldapTrustMapVOs = getLdapTrustMapVOS(domainId); - if(ldapTrustMapVOs != null && ldapTrustMapVOs.size() > 0) { - if(ldapTrustMapVOs.size() == 1 && ldapTrustMapVOs.get(0).getAccountId() == 0) { + if (ldapTrustMapVOs != null && ldapTrustMapVOs.size() > 0) { + if (ldapTrustMapVOs.size() == 1 && ldapTrustMapVOs.get(0).getAccountId() == 0) { if (logger.isTraceEnabled()) { logger.trace("We have a single mapping of a domain to an ldap group or ou"); } @@ -125,11 +127,11 @@ public class LdapAuthenticator extends AdapterBase implements UserAuthenticator mappedGroups.retainAll(memberships); tracelist("actual groups for " + username, mappedGroups); // check membership, there must be only one match in this domain - if(ldapUser.isDisabled()) { + if (ldapUser.isDisabled()) { logAndDisable(userAccount, "attempt to log on using disabled ldap user " + userAccount.getUsername(), false); - } else if(mappedGroups.size() > 1) { + } else if (mappedGroups.size() > 1) { logAndDisable(userAccount, "user '" + username + "' is mapped to more then one account in domain and will be disabled.", false); - } else if(mappedGroups.size() < 1) { + } else if (mappedGroups.size() < 1) { logAndDisable(userAccount, "user '" + username + "' is not mapped to an account in domain and will be removed.", true); } else { // a valid ldap configured user exists @@ -137,12 +139,12 @@ public class LdapAuthenticator extends AdapterBase implements UserAuthenticator // we could now assert that ldapTrustMapVOs.contains(mapping); // createUser in Account can only be done by account name not by account id; Account account = _accountManager.getAccount(mapping.getAccountId()); - if(null == account) { + if (null == account) { throw new CloudRuntimeException(String.format("account for user (%s) not found by id %d", username, mapping.getAccountId())); } String accountName = account.getAccountName(); rc.first(_ldapManager.canAuthenticate(ldapUser.getPrincipal(), password, domainId)); - if (! rc.first()) { + if (!rc.first()) { rc.second(ActionOnFailedAuthentication.INCREMENT_INCORRECT_LOGIN_ATTEMPT_COUNT); } // for security reasons we keep processing on faulty login attempt to not give a way information on userid existence @@ -162,7 +164,7 @@ public class LdapAuthenticator extends AdapterBase implements UserAuthenticator userAccount = _accountManager.getUserAccountById(user.getId()); } else { // not a new user, check if mapped group has changed - if(userAccount.getAccountId() != mapping.getAccountId()) { + if (userAccount.getAccountId() != mapping.getAccountId()) { final Account mappedAccount = _accountManager.getAccount(mapping.getAccountId()); if (mappedAccount == null || mappedAccount.getRemoved() != null) { throw new CloudRuntimeException("Mapped account for users does not exist. Please contact your administrator."); @@ -174,12 +176,21 @@ public class LdapAuthenticator extends AdapterBase implements UserAuthenticator } } catch (NoLdapUserMatchingQueryException e) { logger.debug(e.getMessage()); - disableUserInCloudStack(userAccount); + processLdapUserErrorMessage(userAccount, e.getMessage(), rc); } return rc; } + private void processLdapUserErrorMessage(UserAccount user, String errorMessage, Pair<Boolean, ActionOnFailedAuthentication> rc) { + if (StringUtils.isNotEmpty(errorMessage) && errorMessage.contains(LDAP_READ_TIMED_OUT_MESSAGE) && !rc.first()) { + rc.second(ActionOnFailedAuthentication.INCREMENT_INCORRECT_LOGIN_ATTEMPT_COUNT); + } else { + // no user in ldap ==>> disable user in cloudstack + disableUserInCloudStack(user); + } + } + private void tracelist(String msg, List<String> listToTrace) { if (logger.isTraceEnabled()) { StringBuilder logMsg = new StringBuilder(); @@ -197,7 +208,7 @@ public class LdapAuthenticator extends AdapterBase implements UserAuthenticator if (logger.isInfoEnabled()) { logger.info(msg); } - if(remove) { + if (remove) { removeUserInCloudStack(userAccount); } else { disableUserInCloudStack(userAccount); @@ -229,23 +240,22 @@ public class LdapAuthenticator extends AdapterBase implements UserAuthenticator processLdapUser(password, domainId, user, rc, ldapUser, accountType); } catch (NoLdapUserMatchingQueryException e) { logger.debug(e.getMessage()); - // no user in ldap ==>> disable user in cloudstack - disableUserInCloudStack(user); + processLdapUserErrorMessage(user, e.getMessage(), rc); } return rc; } private void processLdapUser(String password, Long domainId, UserAccount user, Pair<Boolean, ActionOnFailedAuthentication> rc, LdapUser ldapUser, Account.Type accountType) { - if(!ldapUser.isDisabled()) { + if (!ldapUser.isDisabled()) { rc.first(_ldapManager.canAuthenticate(ldapUser.getPrincipal(), password, domainId)); - if(rc.first()) { - if(user == null) { + if (rc.first()) { + if (user == null) { // import user to cloudstack createCloudStackUserAccount(ldapUser, domainId, accountType); } else { enableUserInCloudStack(user); } - } else if(user != null) { + } else if (user != null) { rc.second(ActionOnFailedAuthentication.INCREMENT_INCORRECT_LOGIN_ATTEMPT_COUNT); } } else { @@ -264,30 +274,34 @@ public class LdapAuthenticator extends AdapterBase implements UserAuthenticator */ Pair<Boolean, ActionOnFailedAuthentication> authenticate(String username, String password, Long domainId, UserAccount user) { boolean result = false; + boolean timedOut = false; - if(user != null ) { + if (user != null ) { try { LdapUser ldapUser = _ldapManager.getUser(username, domainId); - if(!ldapUser.isDisabled()) { + if (!ldapUser.isDisabled()) { result = _ldapManager.canAuthenticate(ldapUser.getPrincipal(), password, domainId); } else { logger.debug("user with principal "+ ldapUser.getPrincipal() + " is disabled in ldap"); } } catch (NoLdapUserMatchingQueryException e) { logger.debug(e.getMessage()); + if (e.getMessage().contains(LDAP_READ_TIMED_OUT_MESSAGE)) { + timedOut = true; + } } } - return processResultAndAction(user, result); + return processResultAndAction(user, result, timedOut); } - private Pair<Boolean, ActionOnFailedAuthentication> processResultAndAction(UserAccount user, boolean result) { - return (!result && user != null) ? + private Pair<Boolean, ActionOnFailedAuthentication> processResultAndAction(UserAccount user, boolean result, boolean timedOut) { + return (!result && (user != null || timedOut)) ? new Pair<Boolean, ActionOnFailedAuthentication>(result, ActionOnFailedAuthentication.INCREMENT_INCORRECT_LOGIN_ATTEMPT_COUNT): new Pair<Boolean, ActionOnFailedAuthentication>(result, null); } private void enableUserInCloudStack(UserAccount user) { - if(user != null && (user.getState().equalsIgnoreCase(Account.State.DISABLED.toString()))) { + if (user != null && (user.getState().equalsIgnoreCase(Account.State.DISABLED.toString()))) { _accountManager.enableUser(user.getId()); } } diff --git a/plugins/user-authenticators/ldap/src/main/java/org/apache/cloudstack/ldap/LdapManagerImpl.java b/plugins/user-authenticators/ldap/src/main/java/org/apache/cloudstack/ldap/LdapManagerImpl.java index 2394c0b3d1e..05b8578bb42 100644 --- a/plugins/user-authenticators/ldap/src/main/java/org/apache/cloudstack/ldap/LdapManagerImpl.java +++ b/plugins/user-authenticators/ldap/src/main/java/org/apache/cloudstack/ldap/LdapManagerImpl.java @@ -166,7 +166,7 @@ public class LdapManagerImpl extends ComponentLifecycleBase implements LdapManag private LdapConfigurationResponse addConfigurationInternal(final String hostname, int port, final Long domainId) throws InvalidParameterValueException { // TODO evaluate what the right default should be - if(port <= 0) { + if (port <= 0) { port = 389; } @@ -210,7 +210,7 @@ public class LdapManagerImpl extends ComponentLifecycleBase implements LdapManag // TODO return the right account for this user final LdapContext context = _ldapContextFactory.createUserContext(principal, password, domainId); closeContext(context); - if(logger.isTraceEnabled()) { + if (logger.isTraceEnabled()) { logger.trace(String.format("User(%s) authenticated for domain(%s)", principal, domainId)); } return true; @@ -234,7 +234,7 @@ public class LdapManagerImpl extends ComponentLifecycleBase implements LdapManag @Override public LdapConfigurationResponse createLdapConfigurationResponse(final LdapConfigurationVO configuration) { String domainUuid = null; - if(configuration.getDomainId() != null) { + if (configuration.getDomainId() != null) { DomainVO domain = domainDao.findById(configuration.getDomainId()); if (domain != null) { domainUuid = domain.getUuid(); @@ -303,8 +303,8 @@ public class LdapManagerImpl extends ComponentLifecycleBase implements LdapManag return _ldapUserManagerFactory.getInstance(_ldapConfiguration.getLdapProvider(null)).getUser(escapedUsername, context, domainId); } catch (NamingException | IOException e) { - logger.debug("ldap Exception: ",e); - throw new NoLdapUserMatchingQueryException("No Ldap User found for username: "+username); + logger.debug("LDAP Exception: ", e); + throw new NoLdapUserMatchingQueryException("Unable to find LDAP User for username: " + username + ", due to " + e.getMessage()); } finally { closeContext(context); } @@ -324,8 +324,8 @@ public class LdapManagerImpl extends ComponentLifecycleBase implements LdapManag LdapUserManager userManagerFactory = _ldapUserManagerFactory.getInstance(ldapProvider); return userManagerFactory.getUser(escapedUsername, type, name, context, domainId); } catch (NamingException | IOException e) { - logger.debug("ldap Exception: ",e); - throw new NoLdapUserMatchingQueryException("No Ldap User found for username: "+username + " in group: " + name + " of type: " + type); + logger.debug("LDAP Exception: ", e); + throw new NoLdapUserMatchingQueryException("Unable to find LDAP User for username: " + username + " in group: " + name + " of type: " + type + ", due to " + e.getMessage()); } finally { closeContext(context); } @@ -338,7 +338,7 @@ public class LdapManagerImpl extends ComponentLifecycleBase implements LdapManag context = _ldapContextFactory.createBindContext(domainId); return _ldapUserManagerFactory.getInstance(_ldapConfiguration.getLdapProvider(domainId)).getUsers(context, domainId); } catch (NamingException | IOException e) { - logger.debug("ldap Exception: ",e); + logger.debug("LDAP Exception: ", e); throw new NoLdapUserMatchingQueryException("*"); } finally { closeContext(context); @@ -352,7 +352,7 @@ public class LdapManagerImpl extends ComponentLifecycleBase implements LdapManag context = _ldapContextFactory.createBindContext(domainId); return _ldapUserManagerFactory.getInstance(_ldapConfiguration.getLdapProvider(domainId)).getUsersInGroup(groupName, context, domainId); } catch (NamingException | IOException e) { - logger.debug("ldap NamingException: ",e); + logger.debug("LDAP Exception: ", e); throw new NoLdapUserMatchingQueryException("groupName=" + groupName); } finally { closeContext(context); @@ -390,7 +390,7 @@ public class LdapManagerImpl extends ComponentLifecycleBase implements LdapManag final String escapedUsername = LdapUtils.escapeLDAPSearchFilter(username); return _ldapUserManagerFactory.getInstance(_ldapConfiguration.getLdapProvider(null)).getUsers("*" + escapedUsername + "*", context, null); } catch (NamingException | IOException e) { - logger.debug("ldap Exception: ",e); + logger.debug("LDAP Exception: ",e); throw new NoLdapUserMatchingQueryException(username); } finally { closeContext(context); @@ -481,7 +481,7 @@ public class LdapManagerImpl extends ComponentLifecycleBase implements LdapManag private void clearOldAccountMapping(LinkAccountToLdapCmd cmd) { // first find if exists log warning and update LdapTrustMapVO oldVo = _ldapTrustMapDao.findGroupInDomain(cmd.getDomainId(), cmd.getLdapDomain()); - if(oldVo != null) { + if (oldVo != null) { // deal with edge cases, i.e. check if the old account is indeed deleted etc. if (oldVo.getAccountId() != 0l) { AccountVO oldAcount = accountDao.findByIdIncludingRemoved(oldVo.getAccountId());