Skip to content

Commit ed6ee6b

Browse files
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
1 parent 890386e commit ed6ee6b

File tree

3 files changed

+50
-35
lines changed

3 files changed

+50
-35
lines changed

plugins/user-authenticators/ldap/src/main/java/org/apache/cloudstack/api/command/LdapListUsersCmd.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,10 +140,11 @@ public void execute() throws ServerApiException {
140140
try {
141141
final List<LdapUser> users = _ldapManager.getUsers(domainId);
142142
ldapResponses = createLdapUserResponse(users);
143-
// now filter and annotate
143+
// now filter and annotate
144144
ldapResponses = applyUserFilter(ldapResponses);
145145
} catch (final NoLdapUserMatchingQueryException ex) {
146-
// ok, we'll make do with the empty list ldapResponses = new ArrayList<LdapUserResponse>();
146+
logger.debug(ex.getMessage());
147+
// ok, we'll make do with the empty list
147148
} finally {
148149
response.setResponses(ldapResponses);
149150
response.setResponseName(getCommandName());

plugins/user-authenticators/ldap/src/main/java/org/apache/cloudstack/ldap/LdapAuthenticator.java

Lines changed: 36 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ public class LdapAuthenticator extends AdapterBase implements UserAuthenticator
4545
@Inject
4646
private AccountManager _accountManager;
4747

48+
private static final String LDAP_READ_TIMED_OUT_MESSAGE = "LDAP response read timed out";
49+
4850
public LdapAuthenticator() {
4951
super();
5052
}
@@ -74,8 +76,8 @@ public Pair<Boolean, ActionOnFailedAuthentication> authenticate(final String use
7476
return rc;
7577
}
7678
List<LdapTrustMapVO> ldapTrustMapVOs = getLdapTrustMapVOS(domainId);
77-
if(ldapTrustMapVOs != null && ldapTrustMapVOs.size() > 0) {
78-
if(ldapTrustMapVOs.size() == 1 && ldapTrustMapVOs.get(0).getAccountId() == 0) {
79+
if (ldapTrustMapVOs != null && ldapTrustMapVOs.size() > 0) {
80+
if (ldapTrustMapVOs.size() == 1 && ldapTrustMapVOs.get(0).getAccountId() == 0) {
7981
if (logger.isTraceEnabled()) {
8082
logger.trace("We have a single mapping of a domain to an ldap group or ou");
8183
}
@@ -125,24 +127,24 @@ Pair<Boolean, ActionOnFailedAuthentication> authenticate(String username, String
125127
mappedGroups.retainAll(memberships);
126128
tracelist("actual groups for " + username, mappedGroups);
127129
// check membership, there must be only one match in this domain
128-
if(ldapUser.isDisabled()) {
130+
if (ldapUser.isDisabled()) {
129131
logAndDisable(userAccount, "attempt to log on using disabled ldap user " + userAccount.getUsername(), false);
130-
} else if(mappedGroups.size() > 1) {
132+
} else if (mappedGroups.size() > 1) {
131133
logAndDisable(userAccount, "user '" + username + "' is mapped to more then one account in domain and will be disabled.", false);
132-
} else if(mappedGroups.size() < 1) {
134+
} else if (mappedGroups.size() < 1) {
133135
logAndDisable(userAccount, "user '" + username + "' is not mapped to an account in domain and will be removed.", true);
134136
} else {
135137
// a valid ldap configured user exists
136138
LdapTrustMapVO mapping = _ldapManager.getLinkedLdapGroup(domainId,mappedGroups.get(0));
137139
// we could now assert that ldapTrustMapVOs.contains(mapping);
138140
// createUser in Account can only be done by account name not by account id;
139141
Account account = _accountManager.getAccount(mapping.getAccountId());
140-
if(null == account) {
142+
if (null == account) {
141143
throw new CloudRuntimeException(String.format("account for user (%s) not found by id %d", username, mapping.getAccountId()));
142144
}
143145
String accountName = account.getAccountName();
144146
rc.first(_ldapManager.canAuthenticate(ldapUser.getPrincipal(), password, domainId));
145-
if (! rc.first()) {
147+
if (!rc.first()) {
146148
rc.second(ActionOnFailedAuthentication.INCREMENT_INCORRECT_LOGIN_ATTEMPT_COUNT);
147149
}
148150
// for security reasons we keep processing on faulty login attempt to not give a way information on userid existence
@@ -162,7 +164,7 @@ Pair<Boolean, ActionOnFailedAuthentication> authenticate(String username, String
162164
userAccount = _accountManager.getUserAccountById(user.getId());
163165
} else {
164166
// not a new user, check if mapped group has changed
165-
if(userAccount.getAccountId() != mapping.getAccountId()) {
167+
if (userAccount.getAccountId() != mapping.getAccountId()) {
166168
final Account mappedAccount = _accountManager.getAccount(mapping.getAccountId());
167169
if (mappedAccount == null || mappedAccount.getRemoved() != null) {
168170
throw new CloudRuntimeException("Mapped account for users does not exist. Please contact your administrator.");
@@ -174,12 +176,21 @@ Pair<Boolean, ActionOnFailedAuthentication> authenticate(String username, String
174176
}
175177
} catch (NoLdapUserMatchingQueryException e) {
176178
logger.debug(e.getMessage());
177-
disableUserInCloudStack(userAccount);
179+
processLdapUserErrorMessage(userAccount, e.getMessage(), rc);
178180
}
179181

180182
return rc;
181183
}
182184

185+
private void processLdapUserErrorMessage(UserAccount user, String errorMessage, Pair<Boolean, ActionOnFailedAuthentication> rc) {
186+
if (StringUtils.isNotEmpty(errorMessage) && errorMessage.contains(LDAP_READ_TIMED_OUT_MESSAGE) && !rc.first()) {
187+
rc.second(ActionOnFailedAuthentication.INCREMENT_INCORRECT_LOGIN_ATTEMPT_COUNT);
188+
} else {
189+
// no user in ldap ==>> disable user in cloudstack
190+
disableUserInCloudStack(user);
191+
}
192+
}
193+
183194
private void tracelist(String msg, List<String> listToTrace) {
184195
if (logger.isTraceEnabled()) {
185196
StringBuilder logMsg = new StringBuilder();
@@ -197,7 +208,7 @@ private void logAndDisable(UserAccount userAccount, String msg, boolean remove)
197208
if (logger.isInfoEnabled()) {
198209
logger.info(msg);
199210
}
200-
if(remove) {
211+
if (remove) {
201212
removeUserInCloudStack(userAccount);
202213
} else {
203214
disableUserInCloudStack(userAccount);
@@ -229,23 +240,22 @@ private Pair<Boolean, ActionOnFailedAuthentication> authenticate(String username
229240
processLdapUser(password, domainId, user, rc, ldapUser, accountType);
230241
} catch (NoLdapUserMatchingQueryException e) {
231242
logger.debug(e.getMessage());
232-
// no user in ldap ==>> disable user in cloudstack
233-
disableUserInCloudStack(user);
243+
processLdapUserErrorMessage(user, e.getMessage(), rc);
234244
}
235245
return rc;
236246
}
237247

238248
private void processLdapUser(String password, Long domainId, UserAccount user, Pair<Boolean, ActionOnFailedAuthentication> rc, LdapUser ldapUser, Account.Type accountType) {
239-
if(!ldapUser.isDisabled()) {
249+
if (!ldapUser.isDisabled()) {
240250
rc.first(_ldapManager.canAuthenticate(ldapUser.getPrincipal(), password, domainId));
241-
if(rc.first()) {
242-
if(user == null) {
251+
if (rc.first()) {
252+
if (user == null) {
243253
// import user to cloudstack
244254
createCloudStackUserAccount(ldapUser, domainId, accountType);
245255
} else {
246256
enableUserInCloudStack(user);
247257
}
248-
} else if(user != null) {
258+
} else if (user != null) {
249259
rc.second(ActionOnFailedAuthentication.INCREMENT_INCORRECT_LOGIN_ATTEMPT_COUNT);
250260
}
251261
} else {
@@ -264,30 +274,34 @@ private void processLdapUser(String password, Long domainId, UserAccount user, P
264274
*/
265275
Pair<Boolean, ActionOnFailedAuthentication> authenticate(String username, String password, Long domainId, UserAccount user) {
266276
boolean result = false;
277+
boolean timedOut = false;
267278

268-
if(user != null ) {
279+
if (user != null ) {
269280
try {
270281
LdapUser ldapUser = _ldapManager.getUser(username, domainId);
271-
if(!ldapUser.isDisabled()) {
282+
if (!ldapUser.isDisabled()) {
272283
result = _ldapManager.canAuthenticate(ldapUser.getPrincipal(), password, domainId);
273284
} else {
274285
logger.debug("user with principal "+ ldapUser.getPrincipal() + " is disabled in ldap");
275286
}
276287
} catch (NoLdapUserMatchingQueryException e) {
277288
logger.debug(e.getMessage());
289+
if (e.getMessage().contains(LDAP_READ_TIMED_OUT_MESSAGE)) {
290+
timedOut = true;
291+
}
278292
}
279293
}
280-
return processResultAndAction(user, result);
294+
return processResultAndAction(user, result, timedOut);
281295
}
282296

283-
private Pair<Boolean, ActionOnFailedAuthentication> processResultAndAction(UserAccount user, boolean result) {
284-
return (!result && user != null) ?
297+
private Pair<Boolean, ActionOnFailedAuthentication> processResultAndAction(UserAccount user, boolean result, boolean timedOut) {
298+
return (!result && (user != null || timedOut)) ?
285299
new Pair<Boolean, ActionOnFailedAuthentication>(result, ActionOnFailedAuthentication.INCREMENT_INCORRECT_LOGIN_ATTEMPT_COUNT):
286300
new Pair<Boolean, ActionOnFailedAuthentication>(result, null);
287301
}
288302

289303
private void enableUserInCloudStack(UserAccount user) {
290-
if(user != null && (user.getState().equalsIgnoreCase(Account.State.DISABLED.toString()))) {
304+
if (user != null && (user.getState().equalsIgnoreCase(Account.State.DISABLED.toString()))) {
291305
_accountManager.enableUser(user.getId());
292306
}
293307
}

plugins/user-authenticators/ldap/src/main/java/org/apache/cloudstack/ldap/LdapManagerImpl.java

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ public LdapConfigurationResponse addConfiguration(final String hostname, int por
166166

167167
private LdapConfigurationResponse addConfigurationInternal(final String hostname, int port, final Long domainId) throws InvalidParameterValueException {
168168
// TODO evaluate what the right default should be
169-
if(port <= 0) {
169+
if (port <= 0) {
170170
port = 389;
171171
}
172172

@@ -210,7 +210,7 @@ public boolean canAuthenticate(final String principal, final String password, fi
210210
// TODO return the right account for this user
211211
final LdapContext context = _ldapContextFactory.createUserContext(principal, password, domainId);
212212
closeContext(context);
213-
if(logger.isTraceEnabled()) {
213+
if (logger.isTraceEnabled()) {
214214
logger.trace(String.format("User(%s) authenticated for domain(%s)", principal, domainId));
215215
}
216216
return true;
@@ -234,7 +234,7 @@ private void closeContext(final LdapContext context) {
234234
@Override
235235
public LdapConfigurationResponse createLdapConfigurationResponse(final LdapConfigurationVO configuration) {
236236
String domainUuid = null;
237-
if(configuration.getDomainId() != null) {
237+
if (configuration.getDomainId() != null) {
238238
DomainVO domain = domainDao.findById(configuration.getDomainId());
239239
if (domain != null) {
240240
domainUuid = domain.getUuid();
@@ -303,8 +303,8 @@ public LdapUser getUser(final String username, Long domainId) throws NoLdapUserM
303303
return _ldapUserManagerFactory.getInstance(_ldapConfiguration.getLdapProvider(null)).getUser(escapedUsername, context, domainId);
304304

305305
} catch (NamingException | IOException e) {
306-
logger.debug("ldap Exception: ",e);
307-
throw new NoLdapUserMatchingQueryException("No Ldap User found for username: "+username);
306+
logger.debug("LDAP Exception: ", e);
307+
throw new NoLdapUserMatchingQueryException("Unable to find LDAP User for username: " + username + ", due to " + e.getMessage());
308308
} finally {
309309
closeContext(context);
310310
}
@@ -324,8 +324,8 @@ public LdapUser getUser(final String username, final String type, final String n
324324
LdapUserManager userManagerFactory = _ldapUserManagerFactory.getInstance(ldapProvider);
325325
return userManagerFactory.getUser(escapedUsername, type, name, context, domainId);
326326
} catch (NamingException | IOException e) {
327-
logger.debug("ldap Exception: ",e);
328-
throw new NoLdapUserMatchingQueryException("No Ldap User found for username: "+username + " in group: " + name + " of type: " + type);
327+
logger.debug("LDAP Exception: ", e);
328+
throw new NoLdapUserMatchingQueryException("Unable to find LDAP User for username: " + username + " in group: " + name + " of type: " + type + ", due to " + e.getMessage());
329329
} finally {
330330
closeContext(context);
331331
}
@@ -338,7 +338,7 @@ public List<LdapUser> getUsers(Long domainId) throws NoLdapUserMatchingQueryExce
338338
context = _ldapContextFactory.createBindContext(domainId);
339339
return _ldapUserManagerFactory.getInstance(_ldapConfiguration.getLdapProvider(domainId)).getUsers(context, domainId);
340340
} catch (NamingException | IOException e) {
341-
logger.debug("ldap Exception: ",e);
341+
logger.debug("LDAP Exception: ", e);
342342
throw new NoLdapUserMatchingQueryException("*");
343343
} finally {
344344
closeContext(context);
@@ -352,7 +352,7 @@ public List<LdapUser> getUsersInGroup(String groupName, Long domainId) throws No
352352
context = _ldapContextFactory.createBindContext(domainId);
353353
return _ldapUserManagerFactory.getInstance(_ldapConfiguration.getLdapProvider(domainId)).getUsersInGroup(groupName, context, domainId);
354354
} catch (NamingException | IOException e) {
355-
logger.debug("ldap NamingException: ",e);
355+
logger.debug("LDAP Exception: ", e);
356356
throw new NoLdapUserMatchingQueryException("groupName=" + groupName);
357357
} finally {
358358
closeContext(context);
@@ -390,7 +390,7 @@ public List<LdapUser> searchUsers(final String username) throws NoLdapUserMatchi
390390
final String escapedUsername = LdapUtils.escapeLDAPSearchFilter(username);
391391
return _ldapUserManagerFactory.getInstance(_ldapConfiguration.getLdapProvider(null)).getUsers("*" + escapedUsername + "*", context, null);
392392
} catch (NamingException | IOException e) {
393-
logger.debug("ldap Exception: ",e);
393+
logger.debug("LDAP Exception: ",e);
394394
throw new NoLdapUserMatchingQueryException(username);
395395
} finally {
396396
closeContext(context);
@@ -481,7 +481,7 @@ public LinkAccountToLdapResponse linkAccountToLdap(LinkAccountToLdapCmd cmd) {
481481
private void clearOldAccountMapping(LinkAccountToLdapCmd cmd) {
482482
// first find if exists log warning and update
483483
LdapTrustMapVO oldVo = _ldapTrustMapDao.findGroupInDomain(cmd.getDomainId(), cmd.getLdapDomain());
484-
if(oldVo != null) {
484+
if (oldVo != null) {
485485
// deal with edge cases, i.e. check if the old account is indeed deleted etc.
486486
if (oldVo.getAccountId() != 0l) {
487487
AccountVO oldAcount = accountDao.findByIdIncludingRemoved(oldVo.getAccountId());

0 commit comments

Comments
 (0)