package org.apereo.cas.util;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.apereo.cas.configuration.model.support.ldap.AbstractLdapProperties;
import org.apereo.cas.configuration.support.Beans;
import org.ldaptive.AddOperation;
import org.ldaptive.AddRequest;
import org.ldaptive.AttributeModification;
import org.ldaptive.AttributeModificationType;
import org.ldaptive.Connection;
import org.ldaptive.ConnectionFactory;
import org.ldaptive.Credential;
import org.ldaptive.DeleteOperation;
import org.ldaptive.DeleteRequest;
import org.ldaptive.LdapAttribute;
import org.ldaptive.LdapEntry;
import org.ldaptive.LdapException;
import org.ldaptive.ModifyOperation;
import org.ldaptive.ModifyRequest;
import org.ldaptive.Response;
import org.ldaptive.ResultCode;
import org.ldaptive.ReturnAttributes;
import org.ldaptive.SearchFilter;
import org.ldaptive.SearchOperation;
import org.ldaptive.SearchRequest;
import org.ldaptive.SearchResult;
import org.ldaptive.ad.UnicodePwdAttribute;
import org.ldaptive.extended.PasswordModifyOperation;
import org.ldaptive.extended.PasswordModifyRequest;
import org.ldaptive.referral.DeleteReferralHandler;
import org.ldaptive.referral.ModifyReferralHandler;
import org.ldaptive.referral.SearchReferralHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.URI;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Utilities related to LDAP functions.
*
* @author Scott Battaglia
* @author Misagh Moayyed
* @since 3.0.0
*/
public final class LdapUtils {
/**
* The objectClass attribute.
*/
public static final String OBJECTCLASS_ATTRIBUTE = "objectClass";
private static final Logger LOGGER = LoggerFactory.getLogger(LdapUtils.class);
private static final String LDAP_PREFIX = "ldap";
/**
* Instantiates a new ldap utils.
*/
private LdapUtils() {
// private constructor so that no one can instantiate.
}
/**
* Reads a Boolean value from the LdapEntry.
*
* @param ctx the ldap entry
* @param attribute the attribute name
* @return {@code true} if the attribute's value matches (case-insensitive) {@code "true"}, otherwise false
*/
public static Boolean getBoolean(final LdapEntry ctx, final String attribute) {
return getBoolean(ctx, attribute, Boolean.FALSE);
}
/**
* Reads a Boolean value from the LdapEntry.
*
* @param ctx the ldap entry
* @param attribute the attribute name
* @param nullValue the value which should be returning in case of a null value
* @return {@code true} if the attribute's value matches (case-insensitive) {@code "true"}, otherwise false
*/
public static Boolean getBoolean(final LdapEntry ctx, final String attribute, final Boolean nullValue) {
final String v = getString(ctx, attribute, nullValue.toString());
if (v != null) {
return v.equalsIgnoreCase(Boolean.TRUE.toString());
}
return nullValue;
}
/**
* Reads a Long value from the LdapEntry.
*
* @param ctx the ldap entry
* @param attribute the attribute name
* @return the long value
*/
public static Long getLong(final LdapEntry ctx, final String attribute) {
return getLong(ctx, attribute, Long.MIN_VALUE);
}
/**
* Reads a Long value from the LdapEntry.
*
* @param entry the ldap entry
* @param attribute the attribute name
* @param nullValue the value which should be returning in case of a null value
* @return the long value
*/
public static Long getLong(final LdapEntry entry, final String attribute, final Long nullValue) {
final String v = getString(entry, attribute, nullValue.toString());
if (v != null && NumberUtils.isCreatable(v)) {
return Long.valueOf(v);
}
return nullValue;
}
/**
* Reads a String value from the LdapEntry.
*
* @param entry the ldap entry
* @param attribute the attribute name
* @return the string
*/
public static String getString(final LdapEntry entry, final String attribute) {
return getString(entry, attribute, null);
}
/**
* Reads a String value from the LdapEntry.
*
* @param entry the ldap entry
* @param attribute the attribute name
* @param nullValue the value which should be returning in case of a null value
* @return the string
*/
public static String getString(final LdapEntry entry, final String attribute, final String nullValue) {
final LdapAttribute attr = entry.getAttribute(attribute);
if (attr == null) {
return nullValue;
}
final String v;
if (attr.isBinary()) {
final byte[] b = attr.getBinaryValue();
v = new String(b, StandardCharsets.UTF_8);
} else {
v = attr.getStringValue();
}
if (StringUtils.isNotBlank(v)) {
return v;
}
return nullValue;
}
/**
* Execute search operation.
*
* @param connectionFactory the connection factory
* @param baseDn the base dn
* @param filter the filter
* @param binaryAttributes the binary attributes
* @param returnAttributes the return attributes
* @return the response
* @throws LdapException the ldap exception
*/
public static Response<SearchResult> executeSearchOperation(final ConnectionFactory connectionFactory,
final String baseDn,
final SearchFilter filter,
final String[] binaryAttributes,
final String[] returnAttributes) throws LdapException {
try (Connection connection = createConnection(connectionFactory)) {
final SearchOperation searchOperation = new SearchOperation(connection);
final SearchRequest request = Beans.newLdaptiveSearchRequest(baseDn, filter, binaryAttributes, returnAttributes);
request.setReferralHandler(new SearchReferralHandler());
return searchOperation.execute(request);
}
}
/**
* Execute search operation response.
*
* @param connectionFactory the connection factory
* @param baseDn the base dn
* @param filter the filter
* @return the response
* @throws LdapException the ldap exception
*/
public static Response<SearchResult> executeSearchOperation(final ConnectionFactory connectionFactory,
final String baseDn,
final SearchFilter filter) throws LdapException {
return executeSearchOperation(connectionFactory, baseDn, filter, ReturnAttributes.ALL_USER.value(), ReturnAttributes.ALL_USER.value());
}
/**
* Checks to see if response has a result.
*
* @param response the response
* @return true, if successful
*/
public static boolean containsResultEntry(final Response<SearchResult> response) {
final SearchResult result = response.getResult();
return result != null && result.getEntry() != null;
}
/**
* Gets connection from the factory.
* Opens the connection if needed.
*
* @param connectionFactory the connection factory
* @return the connection
* @throws LdapException the ldap exception
*/
public static Connection createConnection(final ConnectionFactory connectionFactory) throws LdapException {
final Connection c = connectionFactory.getConnection();
if (!c.isOpen()) {
c.open();
}
return c;
}
/**
* Execute a password modify operation.
*
* @param currentDn the current dn
* @param connectionFactory the connection factory
* @param oldPassword the old password
* @param newPassword the new password
* @param type the type
* @return true /false
*/
public static boolean executePasswordModifyOperation(final String currentDn,
final ConnectionFactory connectionFactory,
final String oldPassword,
final String newPassword,
final AbstractLdapProperties.LdapType type) {
try (Connection modifyConnection = createConnection(connectionFactory)) {
if (!modifyConnection.getConnectionConfig().getUseSSL()
&& !modifyConnection.getConnectionConfig().getUseStartTLS()) {
LOGGER.warn("Executing password modification op under a non-secure LDAP connection; "
+ "To modify password attributes, the connection to the LDAP server SHOULD be secured and/or encrypted.");
}
if (type == AbstractLdapProperties.LdapType.AD) {
LOGGER.debug("Executing password modification op for active directory based on "
+ "[https://support.microsoft.com/en-us/kb/269190]");
final ModifyOperation operation = new ModifyOperation(modifyConnection);
final Response response = operation.execute(new ModifyRequest(currentDn,
new AttributeModification(AttributeModificationType.REPLACE, new UnicodePwdAttribute(newPassword))));
LOGGER.debug("Result code [{}], message: [{}]", response.getResult(), response.getMessage());
return response.getResultCode() == ResultCode.SUCCESS;
}
LOGGER.debug("Executing password modification op for generic LDAP");
final PasswordModifyOperation operation = new PasswordModifyOperation(modifyConnection);
final Response response = operation.execute(new PasswordModifyRequest(currentDn,
StringUtils.isNotBlank(oldPassword) ? new Credential(oldPassword) : null,
new Credential(newPassword)));
LOGGER.debug("Result code [{}], message: [{}]", response.getResult(), response.getMessage());
return response.getResultCode() == ResultCode.SUCCESS;
} catch (final LdapException e) {
LOGGER.error(e.getMessage(), e);
}
return false;
}
/**
* Execute modify operation boolean.
*
* @param currentDn the current dn
* @param connectionFactory the connection factory
* @param attributes the attributes
* @return true/false
*/
public static boolean executeModifyOperation(final String currentDn, final ConnectionFactory connectionFactory,
final Map<String, Set<String>> attributes) {
try (Connection modifyConnection = createConnection(connectionFactory)) {
final ModifyOperation operation = new ModifyOperation(modifyConnection);
final List<AttributeModification> mods = attributes.entrySet()
.stream().map(entry -> new AttributeModification(AttributeModificationType.REPLACE,
new LdapAttribute(entry.getKey(), entry.getValue().toArray(new String[]{})))).collect(Collectors.toList());
final ModifyRequest request = new ModifyRequest(currentDn,
mods.toArray(new AttributeModification[]{}));
request.setReferralHandler(new ModifyReferralHandler());
operation.execute(request);
return true;
} catch (final LdapException e) {
LOGGER.error(e.getMessage(), e);
}
return false;
}
/**
* Execute modify operation boolean.
*
* @param currentDn the current dn
* @param connectionFactory the connection factory
* @param entry the entry
* @return true/false
*/
public static boolean executeModifyOperation(final String currentDn, final ConnectionFactory connectionFactory, final LdapEntry entry) {
final Map<String, Set<String>> attributes = entry.getAttributes().stream()
.collect(Collectors.toMap(LdapAttribute::getName, ldapAttribute -> new HashSet<>(ldapAttribute.getStringValues())));
return executeModifyOperation(currentDn, connectionFactory, attributes);
}
/**
* Execute add operation boolean.
*
* @param connectionFactory the connection factory
* @param entry the entry
* @return true/false
* @throws LdapException the ldap exception
*/
public static boolean executeAddOperation(final ConnectionFactory connectionFactory, final LdapEntry entry) throws LdapException {
try (Connection connection = createConnection(connectionFactory)) {
final AddOperation operation = new AddOperation(connection);
operation.execute(new AddRequest(entry.getDn(), entry.getAttributes()));
return true;
} catch (final LdapException e) {
LOGGER.error(e.getMessage(), e);
}
return false;
}
/**
* Execute delete operation boolean.
*
* @param connectionFactory the connection factory
* @param entry the entry
* @return true/false
* @throws LdapException the ldap exception
*/
public static boolean executeDeleteOperation(final ConnectionFactory connectionFactory, final LdapEntry entry) throws LdapException {
try (Connection connection = createConnection(connectionFactory)) {
final DeleteOperation delete = new DeleteOperation(connection);
final DeleteRequest request = new DeleteRequest(entry.getDn());
request.setReferralHandler(new DeleteReferralHandler());
final Response<Void> res = delete.execute(request);
return res.getResultCode() == ResultCode.SUCCESS;
} catch (final LdapException e) {
LOGGER.error(e.getMessage(), e);
}
return false;
}
/**
* Is ldap connection url?.
*
* @param r the resource
* @return true/false
*/
public static boolean isLdapConnectionUrl(final String r) {
return r.toLowerCase().startsWith(LDAP_PREFIX);
}
/**
* Is ldap connection url?.
*
* @param r the resource
* @return true/false
*/
public static boolean isLdapConnectionUrl(final URI r) {
return r.getScheme().equalsIgnoreCase(LDAP_PREFIX);
}
/**
* Is ldap connection url?.
*
* @param r the resource
* @return true/false
*/
public static boolean isLdapConnectionUrl(final URL r) {
return r.getProtocol().equalsIgnoreCase(LDAP_PREFIX);
}
}