org.apereo.cas.pm.LdapPasswordManagementService Maven / Gradle / Ivy
package org.apereo.cas.pm;
import org.apereo.cas.authentication.Credential;
import org.apereo.cas.configuration.model.support.pm.LdapPasswordManagementProperties;
import org.apereo.cas.configuration.model.support.pm.PasswordManagementProperties;
import org.apereo.cas.pm.impl.BasePasswordManagementService;
import org.apereo.cas.util.CollectionUtils;
import org.apereo.cas.util.LdapConnectionFactory;
import org.apereo.cas.util.LdapUtils;
import org.apereo.cas.util.crypto.CipherExecutor;
import org.apereo.cas.util.function.FunctionUtils;
import org.apereo.cas.util.spring.SpringExpressionLanguageValueResolver;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.commons.validator.routines.EmailValidator;
import org.jooq.lambda.Unchecked;
import org.ldaptive.ConnectionFactory;
import org.ldaptive.LdapAttribute;
import org.ldaptive.LdapEntry;
import org.springframework.beans.factory.DisposableBean;
import java.io.Serializable;
import java.util.ArrayDeque;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
/**
* This is {@link LdapPasswordManagementService}.
*
* @author Misagh Moayyed
* @since 5.0.0
*/
@Slf4j
public class LdapPasswordManagementService extends BasePasswordManagementService implements DisposableBean {
private final List ldapProperties;
private final Map connectionFactoryMap;
public LdapPasswordManagementService(final CipherExecutor cipherExecutor,
final String issuer,
final PasswordManagementProperties passwordManagementProperties,
final PasswordHistoryService passwordHistoryService,
final Map connectionFactoryMap) {
super(passwordManagementProperties, cipherExecutor, issuer, passwordHistoryService);
this.ldapProperties = passwordManagementProperties.getLdap();
this.connectionFactoryMap = connectionFactoryMap;
}
@Override
public void destroy() {
this.connectionFactoryMap.forEach((ldap, connectionFactory) ->
connectionFactory.close());
}
@Override
public String findEmail(final PasswordManagementQuery query) {
val email = findAttribute(query, properties.getReset().getMail().getAttributeName(),
CollectionUtils.wrap(query.getUsername()));
if (EmailValidator.getInstance().isValid(email)) {
LOGGER.debug("Email address [{}] for [{}] appears valid", email, query.getUsername());
return email;
}
LOGGER.warn("Email address [{}] for [{}] is not valid", email, query.getUsername());
return null;
}
@Override
public String findPhone(final PasswordManagementQuery query) {
return findAttribute(query, properties.getReset().getSms().getAttributeName(), CollectionUtils.wrap(query.getUsername()));
}
@Override
public String findUsername(final PasswordManagementQuery query) {
return findAttribute(query, properties.getLdap().stream()
.map(LdapPasswordManagementProperties::getUsernameAttribute)
.collect(Collectors.toList()), CollectionUtils.wrap(query.getEmail()));
}
@Override
public void updateSecurityQuestions(final PasswordManagementQuery query) {
findEntries(CollectionUtils.wrap(query.getUsername()))
.forEach((entry, ldap) -> {
LOGGER.debug("Located LDAP entry [{}] in the response", entry);
val questionsAndAnswers = new ArrayDeque<>(ldap.getSecurityQuestionsAttributes().entrySet());
LOGGER.debug("Security question attributes are defined to be [{}]", questionsAndAnswers);
val ldapConnectionFactory = new LdapConnectionFactory(connectionFactoryMap.get(ldap.getLdapUrl()));
val attributes = new LinkedHashMap>();
query.getSecurityQuestions().forEach((question, answers) -> {
val attrEntry = questionsAndAnswers.pop();
attributes.put(attrEntry.getKey(), Set.of(question));
attributes.put(attrEntry.getValue(), Set.copyOf(answers));
});
ldapConnectionFactory.executeModifyOperation(entry.getDn(), attributes);
});
}
@Override
public boolean unlockAccount(final Credential credential) {
return findEntries(CollectionUtils.wrap(credential.getId())).entrySet().stream().allMatch(entry -> {
LOGGER.debug("Located LDAP entry [{}] in the response", entry);
val ldapConnectionFactory = new LdapConnectionFactory(connectionFactoryMap.get(entry.getValue().getLdapUrl()));
val attributes = new LinkedHashMap>();
attributes.put(entry.getValue().getAccountLockedAttribute(), Set.of(entry.getValue().getAccountUnlockedAttributeValues()));
return ldapConnectionFactory.executeModifyOperation(entry.getKey().getDn(), attributes);
});
}
@Override
public Map getSecurityQuestions(final PasswordManagementQuery query) {
val results = new LinkedHashMap(0);
findEntries(CollectionUtils.wrap(query.getUsername()))
.forEach((entry, ldap) -> {
LOGGER.debug("Located LDAP entry [{}] in the response", entry);
val questionsAndAnswers = ldap.getSecurityQuestionsAttributes();
LOGGER.debug("Security question attributes are defined to be [{}]", questionsAndAnswers);
questionsAndAnswers.forEach((key, value) -> {
val questionAndAnswer = getSecurityQuestionAndAnswer(entry, ldap, key, value);
val question = questionAndAnswer.getKey();
val answer = questionAndAnswer.getValue();
if (StringUtils.isNotBlank(question) && StringUtils.isNotBlank(answer)) {
LOGGER.debug("Added security question [{}] with answer [{}]", question, answer);
results.put(question, answer);
}
});
});
return results;
}
protected Pair getSecurityQuestionAndAnswer(
final LdapEntry entry,
final LdapPasswordManagementProperties properties,
final String questionAttributeName,
final String answerAttributeName) {
val questionAttribute = entry.getAttribute(questionAttributeName);
val answerAttribute = entry.getAttribute(answerAttributeName);
val question = Optional.ofNullable(questionAttribute)
.map(LdapAttribute::getStringValue)
.orElse(StringUtils.EMPTY);
val answer = Optional.ofNullable(answerAttribute)
.map(LdapAttribute::getStringValue)
.orElse(StringUtils.EMPTY);
return Pair.of(question, answer);
}
@Override
public boolean changeInternal(final PasswordChangeRequest bean) {
return FunctionUtils.doAndHandle(() -> {
val results = findEntries(CollectionUtils.wrap(bean.getUsername()))
.entrySet()
.stream()
.map(entry -> {
val dn = entry.getKey().getDn();
LOGGER.debug("Updating account password for [{}]", dn);
val ldapConnectionFactory = new LdapConnectionFactory(connectionFactoryMap.get(entry.getValue().getLdapUrl()));
if (ldapConnectionFactory.executePasswordModifyOperation(dn, bean.getCurrentPassword(),
bean.getPassword(), entry.getValue().getType())) {
LOGGER.debug("Successfully updated the account password for [{}]", dn);
return Boolean.TRUE;
}
LOGGER.error("Could not update the LDAP entry's password for [{}]", dn);
return Boolean.FALSE;
}).toList();
return results.stream().allMatch(result -> result);
}, e -> false).get();
}
/**
* Perform LDAP search by username, returning the requested attribute.
*
* @param context the context
* @param attributeNames name of the attribute
* @param ldapFilterParam the ldap filter param
* @return String value of attribute; null if user/attribute not present
*/
protected String findAttribute(final PasswordManagementQuery context,
final List attributeNames,
final List ldapFilterParam) {
return findEntries(ldapFilterParam)
.keySet()
.stream()
.map(entry -> {
LOGGER.debug("Found LDAP entry [{}] to use", entry);
return attributeNames
.stream()
.map(attributeName -> SpringExpressionLanguageValueResolver.getInstance().resolve(attributeName))
.map(attributeName -> {
val attr = entry.getAttribute(attributeName);
if (attr != null) {
val attributeValue = attr.getStringValue();
LOGGER.debug("Found [{}] [{}] for user [{}].", attributeName,
attributeValue, context.getUsername());
return attributeValue;
}
LOGGER.warn("Could not locate LDAP attribute [{}] for [{}]",
attributeName, entry.getDn());
return null;
})
.filter(Objects::nonNull)
.findFirst()
.orElse(null);
})
.filter(Objects::nonNull)
.findFirst()
.orElse(null);
}
private Map findEntries(final List filterValues) {
val results = new LinkedHashMap(0);
ldapProperties
.stream()
.sorted(Comparator.comparing(LdapPasswordManagementProperties::getName))
.forEach(Unchecked.consumer(ldap -> {
val filter = LdapUtils.newLdaptiveSearchFilter(ldap.getSearchFilter(),
LdapUtils.LDAP_SEARCH_FILTER_DEFAULT_PARAM_NAME, filterValues);
LOGGER.debug("Constructed LDAP filter [{}]", filter);
val ldapConnectionFactory = new LdapConnectionFactory(connectionFactoryMap.get(ldap.getLdapUrl()));
val response = ldapConnectionFactory.executeSearchOperation(ldap.getBaseDn(), filter, ldap.getPageSize());
LOGGER.debug("LDAP response [{}]", response);
if (LdapUtils.containsResultEntry(response)) {
results.put(response.getEntry(), ldap);
}
}));
return results;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy