package org.apereo.cas.pm.ldap;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.validator.routines.EmailValidator;
import org.apereo.cas.CipherExecutor;
import org.apereo.cas.authentication.Credential;
import org.apereo.cas.authentication.UsernamePasswordCredential;
import org.apereo.cas.configuration.model.support.pm.PasswordManagementProperties;
import org.apereo.cas.configuration.support.Beans;
import org.apereo.cas.pm.BasePasswordManagementService;
import org.apereo.cas.pm.PasswordChangeBean;
import org.apereo.cas.util.LdapUtils;
import org.apereo.inspektr.audit.annotation.Audit;
import org.ldaptive.ConnectionFactory;
import org.ldaptive.LdapAttribute;
import org.ldaptive.LdapEntry;
import org.ldaptive.Response;
import org.ldaptive.SearchFilter;
import org.ldaptive.SearchResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;
import java.io.Serializable;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* This is {@link LdapPasswordManagementService}.
*
* @author Misagh Moayyed
* @since 5.0.0
*/
public class LdapPasswordManagementService extends BasePasswordManagementService {
private static final Logger LOGGER = LoggerFactory.getLogger(LdapPasswordManagementService.class);
public LdapPasswordManagementService(final CipherExecutor<Serializable, String> cipherExecutor,
final String issuer,
final PasswordManagementProperties passwordManagementProperties) {
super(cipherExecutor, issuer, passwordManagementProperties);
}
@Override
public String findEmail(final String username) {
try {
final PasswordManagementProperties.Ldap ldap = passwordManagementProperties.getLdap();
final SearchFilter filter = Beans.newLdaptiveSearchFilter(ldap.getUserFilter(),
Beans.LDAP_SEARCH_FILTER_DEFAULT_PARAM_NAME,
Arrays.asList(username));
LOGGER.debug("Constructed LDAP filter [{}] to locate account email", filter);
final ConnectionFactory factory = Beans.newLdaptivePooledConnectionFactory(ldap);
final Response<SearchResult> response = LdapUtils.executeSearchOperation(factory, ldap.getBaseDn(), filter);
LOGGER.debug("LDAP response to locate account email is [{}]", response);
if (LdapUtils.containsResultEntry(response)) {
final LdapEntry entry = response.getResult().getEntry();
LOGGER.debug("Found LDAP entry [{}] to use for the account email", entry);
final String attributeName = passwordManagementProperties.getReset().getEmailAttribute();
final LdapAttribute attr = entry.getAttribute(attributeName);
if (attr != null) {
final String email = attr.getStringValue();
LOGGER.debug("Found email address [{}] for user [{}]. Validating...", email, username);
if (EmailValidator.getInstance().isValid(email)) {
LOGGER.debug("Email address [{}] matches a valid email address", email);
return email;
}
LOGGER.error("Email [{}] is not a valid address", email);
} else {
LOGGER.error("Could not locate an LDAP attribute [{}] for [{}] and base DN [{}]",
attributeName, filter.format(), ldap.getBaseDn());
}
return null;
}
LOGGER.error("Could not locate an LDAP entry for [{}] and base DN [{}]", filter.format(), ldap.getBaseDn());
} catch (final Exception e) {
LOGGER.error(e.getMessage(), e);
}
return null;
}
@Audit(action = "CHANGE_PASSWORD",
actionResolverName = "CHANGE_PASSWORD_ACTION_RESOLVER",
resourceResolverName = "CHANGE_PASSWORD_RESOURCE_RESOLVER")
@Override
public boolean change(final Credential credential, final PasswordChangeBean bean) {
Assert.notNull(credential, "Credential cannot be null");
Assert.notNull(bean, "PasswordChangeBean cannot be null");
try {
final PasswordManagementProperties.Ldap ldap = passwordManagementProperties.getLdap();
final UsernamePasswordCredential c = (UsernamePasswordCredential) credential;
final SearchFilter filter = Beans.newLdaptiveSearchFilter(ldap.getUserFilter(),
Beans.LDAP_SEARCH_FILTER_DEFAULT_PARAM_NAME,
Arrays.asList(c.getId()));
LOGGER.debug("Constructed LDAP filter [{}] to update account password", filter);
final ConnectionFactory factory = Beans.newLdaptivePooledConnectionFactory(ldap);
final Response<SearchResult> response = LdapUtils.executeSearchOperation(factory, ldap.getBaseDn(), filter);
LOGGER.debug("LDAP response to update password is [{}]", response);
if (LdapUtils.containsResultEntry(response)) {
final String dn = response.getResult().getEntry().getDn();
LOGGER.debug("Updating account password for [{}]", dn);
if (LdapUtils.executePasswordModifyOperation(dn, factory, c.getPassword(), bean.getPassword(),
passwordManagementProperties.getLdap().getType())) {
LOGGER.debug("Successfully updated the account password for [{}]", dn);
return true;
}
LOGGER.error("Could not update the LDAP entry's password for [{}] and base DN [{}]", filter.format(), ldap.getBaseDn());
} else {
LOGGER.error("Could not locate an LDAP entry for [{}] and base DN [{}]", filter.format(), ldap.getBaseDn());
}
} catch (final Exception e) {
LOGGER.error(e.getMessage(), e);
}
return false;
}
@Override
public Map<String, String> getSecurityQuestions(final String username) {
final Map<String, String> set = new LinkedHashMap<>();
try {
final PasswordManagementProperties.Ldap ldap = passwordManagementProperties.getLdap();
final SearchFilter filter = Beans.newLdaptiveSearchFilter(ldap.getUserFilter(),
Beans.LDAP_SEARCH_FILTER_DEFAULT_PARAM_NAME,
Arrays.asList(username));
LOGGER.debug("Constructed LDAP filter [{}] to locate security questions", filter);
final ConnectionFactory factory = Beans.newLdaptivePooledConnectionFactory(ldap);
final Response<SearchResult> response = LdapUtils.executeSearchOperation(factory, ldap.getBaseDn(), filter);
LOGGER.debug("LDAP response for security questions [{}]", response);
if (LdapUtils.containsResultEntry(response)) {
final LdapEntry entry = response.getResult().getEntry();
LOGGER.debug("Located LDAP entry [{}] in the response", entry);
final Map<String, String> qs = passwordManagementProperties.getLdap().getSecurityQuestionsAttributes();
LOGGER.debug("Security question attributes are defined to be [{}]", qs);
qs.forEach((k, v) -> {
final LdapAttribute q = entry.getAttribute(k);
final LdapAttribute a = entry.getAttribute(v);
if (q != null && a != null && StringUtils.isNotBlank(q.getStringValue())
&& StringUtils.isNotBlank(a.getStringValue())) {
LOGGER.debug("Added security question [{}]", q.getStringValue());
set.put(q.getStringValue(), a.getStringValue());
}
});
} else {
LOGGER.debug("LDAP response did not contain a result for security questions");
}
} catch (final Exception e) {
LOGGER.error(e.getMessage(), e);
}
return set;
}
}