blob: e3db00adfd0dbf80b967a842af96c85284c255ce [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.jackrabbit.oak.security.authentication.ldap.impl;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import javax.jcr.Credentials;
import javax.jcr.SimpleCredentials;
import javax.net.ssl.SSLContext;
import javax.security.auth.login.LoginException;
import org.apache.commons.pool2.impl.DefaultPooledObject;
import org.apache.directory.api.ldap.model.constants.SchemaConstants;
import org.apache.directory.api.ldap.model.cursor.CursorException;
import org.apache.directory.api.ldap.model.cursor.SearchCursor;
import org.apache.directory.api.ldap.model.entry.Attribute;
import org.apache.directory.api.ldap.model.entry.Entry;
import org.apache.directory.api.ldap.model.entry.Value;
import org.apache.directory.api.ldap.model.exception.LdapAuthenticationException;
import org.apache.directory.api.ldap.model.exception.LdapException;
import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueException;
import org.apache.directory.api.ldap.model.message.Response;
import org.apache.directory.api.ldap.model.message.ResultCodeEnum;
import org.apache.directory.api.ldap.model.message.SearchRequest;
import org.apache.directory.api.ldap.model.message.SearchRequestImpl;
import org.apache.directory.api.ldap.model.message.SearchResultDone;
import org.apache.directory.api.ldap.model.message.SearchResultEntry;
import org.apache.directory.api.ldap.model.message.SearchScope;
import org.apache.directory.api.ldap.model.message.controls.PagedResults;
import org.apache.directory.api.ldap.model.message.controls.PagedResultsImpl;
import org.apache.directory.api.ldap.model.name.Dn;
import org.apache.directory.api.ldap.model.name.Rdn;
import org.apache.directory.ldap.client.api.AbstractPoolableLdapConnectionFactory;
import org.apache.directory.ldap.client.api.DefaultLdapConnectionValidator;
import org.apache.directory.ldap.client.api.LdapConnection;
import org.apache.directory.ldap.client.api.LdapConnectionConfig;
import org.apache.directory.ldap.client.api.LdapConnectionPool;
import org.apache.directory.ldap.client.api.LookupLdapConnectionValidator;
import org.apache.directory.ldap.client.api.NoVerificationTrustManager;
import org.apache.directory.ldap.client.api.ValidatingPoolableLdapConnectionFactory;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.ConfigurationPolicy;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Service;
import org.apache.jackrabbit.commons.iterator.AbstractLazyIterator;
import org.apache.jackrabbit.oak.commons.DebugTimer;
import org.apache.jackrabbit.oak.spi.security.ConfigurationParameters;
import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalGroup;
import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentity;
import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentityException;
import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentityProvider;
import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentityRef;
import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalUser;
import org.apache.jackrabbit.oak.spi.security.authentication.external.PrincipalNameResolver;
import org.apache.jackrabbit.util.Text;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* {@code LdapIdentityProvider} implements an external identity provider that reads users and groups from an ldap
* source.
*
* Please refer to {@link LdapProviderConfig} for configuration options.
*/
@Component(
// note that the metatype information is generated from LdapProviderConfig
policy = ConfigurationPolicy.REQUIRE
)
@Service
public class LdapIdentityProvider implements ExternalIdentityProvider, PrincipalNameResolver {
/**
* default logger
*/
private static final Logger log = LoggerFactory.getLogger(LdapIdentityProvider.class);
/**
* internal configuration
*/
private LdapProviderConfig config;
/**
* the connection pool with connections authenticated with the bind DN
*/
private LdapConnectionPool adminPool;
/**
* admin connection factory
*/
private AbstractPoolableLdapConnectionFactory adminConnectionFactory;
/**
* the connection pool with unbound connections
*/
private UnboundLdapConnectionPool userPool;
/**
* user connection factory
*/
private PoolableUnboundConnectionFactory userConnectionFactory;
/**
* SSL protocols (initialized on init)
*/
private String[] enabledSSLProtocols;
/**
* Default constructor for OSGi
*/
@SuppressWarnings("UnusedDeclaration")
public LdapIdentityProvider() {
}
/**
* Constructor for non-OSGi cases.
* @param config the configuration
*/
public LdapIdentityProvider(@NotNull LdapProviderConfig config) {
this.config = config;
init();
}
//----------------------------------------------------< SCR integration >---
@SuppressWarnings("UnusedDeclaration")
@Activate
private void activate(Map<String, Object> properties) {
ConfigurationParameters cfg = ConfigurationParameters.of(properties);
config = LdapProviderConfig.of(cfg);
init();
}
@SuppressWarnings("UnusedDeclaration")
@Deactivate
private void deactivate() {
close();
}
/**
* Closes this provider and releases the internal pool. This should be called by Non-OSGi users of this provider.
*/
public void close() {
if (adminPool != null) {
try {
adminPool.close();
} catch (Exception e) {
log.warn("Error while closing LDAP connection pool", e);
}
adminPool = null;
}
if (userPool != null) {
try {
userPool.close();
} catch (Exception e) {
log.warn("Error while closing LDAP connection pool", e);
}
userPool = null;
}
}
//----------------------------------------------< PrincipalNameResolver >---
@NotNull
@Override
public String fromExternalIdentityRef(@NotNull ExternalIdentityRef externalIdentityRef) throws ExternalIdentityException {
if (!isMyRef(externalIdentityRef)) {
throw new ExternalIdentityException("Foreign IDP " + externalIdentityRef.getString());
}
return externalIdentityRef.getId();
}
//-------------------------------------------< ExternalIdentityProvider >---
@NotNull
@Override
public String getName() {
return config.getName();
}
@Override
public ExternalIdentity getIdentity(@NotNull ExternalIdentityRef ref) throws ExternalIdentityException {
if (!isMyRef(ref)) {
return null;
}
LdapConnection connection = connect();
try {
Entry entry;
String id = ref.getId();
boolean useUidForExtId = config.getUseUidForExtId();
String userIdAttr = config.getUserConfig().getIdAttribute();
String groupIdAttr = config.getGroupConfig().getIdAttribute();
String[] ca = config.getCustomAttributes();
if (useUidForExtId) {
entry = getEntry(connection, config.getUserConfig(), id, config.getCustomAttributes());
if (entry == null) {
entry = getEntry(connection, config.getGroupConfig(), id, config.getCustomAttributes());
}
} else {
if (ca.length == 0) {
entry = connection.lookup(id, SchemaConstants.ALL_USER_ATTRIBUTES);
}
else {
List<String> attributes = new ArrayList<>(Arrays.asList(ca));
attributes.add("objectClass");
attributes.add(userIdAttr);
attributes.add(groupIdAttr);
String[] attributeArray = new String[attributes.size()];
attributes.toArray(attributeArray);
entry = connection.lookup(id, attributeArray);
}
}
if (entry == null) {
return null;
} else if (entry.hasObjectClass(config.getUserConfig().getObjectClasses())) {
return createUser(entry, null);
} else if (entry.hasObjectClass(config.getGroupConfig().getObjectClasses())) {
return createGroup(entry, null);
} else {
log.warn("referenced identity is neither user or group: {}", ref.getString());
return null;
}
} catch (LdapException | CursorException e) {
throw lookupFailedException(e, null);
} finally {
disconnect(connection);
}
}
@Override
public ExternalUser getUser(@NotNull String userId) throws ExternalIdentityException {
DebugTimer timer = new DebugTimer();
LdapConnection connection = connect();
timer.mark("connect");
try {
Entry entry = getEntry(connection, config.getUserConfig(), userId, config.getCustomAttributes());
timer.mark("lookup");
debug("getUser({}) {}", userId, timer.getString());
if (entry != null) {
return createUser(entry, userId);
} else {
return null;
}
} catch (LdapException | CursorException e) {
throw lookupFailedException(e, timer);
} finally {
disconnect(connection);
}
}
@Override
public ExternalGroup getGroup(@NotNull String name) throws ExternalIdentityException {
DebugTimer timer = new DebugTimer();
LdapConnection connection = connect();
timer.mark("connect");
try {
Entry entry = getEntry(connection, config.getGroupConfig(), name, config.getCustomAttributes());
timer.mark("lookup");
debug("getGroup({}) {}", name, timer.getString());
if (entry != null) {
return createGroup(entry, name);
} else {
return null;
}
} catch (LdapException | CursorException e) {
throw lookupFailedException(e, timer);
} finally {
disconnect(connection);
}
}
@NotNull
@Override
public Iterator<ExternalUser> listUsers() throws ExternalIdentityException {
try {
final Iterator<Entry> iter = getEntryIterator(config.getUserConfig());
return new AbstractLazyIterator<ExternalUser>() {
@Override
protected ExternalUser getNext() {
while (iter.hasNext()) {
try {
return createUser(iter.next(), null);
} catch (LdapInvalidAttributeValueException e) {
log.warn("Error while creating external user object", e);
}
}
return null;
}
};
} catch (LdapException | CursorException e) {
throw lookupFailedException(e, null);
}
}
@NotNull
@Override
public Iterator<ExternalGroup> listGroups() throws ExternalIdentityException {
try {
final Iterator<Entry> iter = getEntryIterator(config.getGroupConfig());
return new AbstractLazyIterator<ExternalGroup>() {
@Override
protected ExternalGroup getNext() {
while (iter.hasNext()) {
try {
return createGroup(iter.next(), null);
} catch (LdapInvalidAttributeValueException e) {
log.warn("Error while creating external group object", e);
}
}
return null;
}
};
} catch (LdapException | CursorException e) {
throw lookupFailedException(e, null);
}
}
@Override
public ExternalUser authenticate(@NotNull Credentials credentials) throws ExternalIdentityException, LoginException {
if (!(credentials instanceof SimpleCredentials)) {
log.debug("LDAP IDP can only authenticate SimpleCredentials.");
return null;
}
final SimpleCredentials creds = (SimpleCredentials) credentials;
final LdapUser user = (LdapUser)getUser(creds.getUserID());
if (user != null) {
// OAK-2078: check for non-empty passwords to avoid anonymous bind on weakly configured servers
// see http://tools.ietf.org/html/rfc4513#section-5.1.1 for details.
if (creds.getPassword().length == 0) {
throw new LoginException("Refusing to authenticate against LDAP server: Empty passwords not allowed.");
}
// authenticate
LdapConnection connection = null;
try {
DebugTimer timer = new DebugTimer();
connection = createUserConnection();
timer.mark("connect");
connection.bind(user.getEntry().getDn(), new String(creds.getPassword()));
timer.mark("bind");
debug("authenticate({}) {}", user.getId(), timer.getString());
} catch (LdapAuthenticationException e) {
throw new LoginException("Unable to authenticate against LDAP server: " + e.getMessage());
} catch (Exception e) {
throw error(e, "Error while binding user credentials");
} finally {
disconnectUserConnection(connection);
}
}
return user;
}
@NotNull
private LdapConnection createUserConnection() throws Exception {
if (userPool == null) {
return userConnectionFactory.create();
} else {
return userPool.getConnection();
}
}
private void disconnectUserConnection(@Nullable LdapConnection connection) {
if (connection != null) {
try {
if (userPool == null) {
userConnectionFactory.destroyObject(connection);
} else {
userPool.releaseConnection(connection);
}
} catch (Exception e) {
// ignore
}
}
}
//-----------------------------------------------------------< internal >---
/**
* Collects the declared (direct) groups of an identity
* @param ref reference to the identity
* @return map of identities where the key is the DN of the LDAP entity
*/
Map<String, ExternalIdentityRef> getDeclaredGroupRefs(ExternalIdentityRef ref, String dn) throws ExternalIdentityException {
if (!isMyRef(ref)) {
return Collections.emptyMap();
}
String searchFilter = config.getMemberOfSearchFilter(dn);
LdapConnection connection = null;
SearchCursor searchCursor = null;
try {
// Create the SearchRequest object
SearchRequest req = new SearchRequestImpl();
req.setScope(SearchScope.SUBTREE);
String idAttribute = config.getGroupConfig().getIdAttribute();
req.addAttributes(idAttribute == null? SchemaConstants.NO_ATTRIBUTE : idAttribute);
req.setTimeLimit((int) config.getSearchTimeout());
req.setBase(new Dn(config.getGroupConfig().getBaseDN()));
req.setFilter(searchFilter);
debug("getDeclaredGroupRefs: using SearchRequest {}.", req);
Map<String, ExternalIdentityRef> groups = new HashMap<>();
DebugTimer timer = new DebugTimer();
connection = connect();
timer.mark("connect");
searchCursor = connection.search(req);
timer.mark("search");
while (searchCursor.next()) {
Response response = searchCursor.get();
if (response instanceof SearchResultEntry) {
Entry resultEntry = ((SearchResultEntry) response).getEntry();
ExternalIdentityRef groupRef = new ExternalIdentityRef(resultEntry.getDn().toString(), this.getName());
groups.put(groupRef.getId(), groupRef);
}
}
timer.mark("iterate");
debug("getDeclaredGroupRefs: search below {} with {} found {} entries. {}",
config.getGroupConfig().getBaseDN(), searchFilter, groups.size(), timer.getString());
return groups;
} catch (Exception e) {
throw error(e, "Error during ldap membership search.");
} finally {
closeSearchCursor(searchCursor);
disconnect(connection);
}
}
/**
* Collects the declared (direct) members of a group
* @param ref the reference to the group
* @return map of identity refers
* @throws ExternalIdentityException if an error occurs
*/
Map<String, ExternalIdentityRef> getDeclaredMemberRefs(ExternalIdentityRef ref, String dn) throws ExternalIdentityException {
if (!isMyRef(ref)) {
return Collections.emptyMap();
}
LdapConnection connection = null;
try {
Map<String, ExternalIdentityRef> members = new HashMap<>();
DebugTimer timer = new DebugTimer();
connection = connect();
timer.mark("connect");
Entry entry = connection.lookup(dn);
timer.mark("lookup");
Attribute attr = entry.get(config.getGroupMemberAttribute());
if (attr == null) {
log.warn("LDAP group does not have configured attribute: {}", config.getGroupMemberAttribute());
} else {
for (Value value: attr) {
ExternalIdentityRef memberRef = new ExternalIdentityRef(value.getString(), this.getName());
members.put(memberRef.getId(), memberRef);
}
}
timer.mark("iterate");
debug("members lookup of {} found {} members. {}", ref.getId(), members.size(), timer.getString());
return members;
} catch (Exception e) {
throw error(e, "Error during ldap group members lookup.");
} finally {
disconnect(connection);
}
}
//------------------------------------------------------------< private >---
/**
* Initializes the ldap identity provider.
*/
private void init() {
if (adminConnectionFactory != null) {
throw new IllegalStateException("Provider already initialized.");
}
// make sure the JVM supports the TLSv1.1
try {
enabledSSLProtocols = null;
SSLContext.getInstance("TLSv1.1");
} catch (NoSuchAlgorithmException e) {
log.warn("JDK does not support TLSv1.1. Disabling it.");
enabledSSLProtocols = new String[]{"TLSv1"};
}
// setup admin connection pool
LdapConnectionConfig cc = createConnectionConfig();
String bindDN = config.getBindDN();
if (bindDN != null && !bindDN.isEmpty()) {
cc.setName(bindDN);
cc.setCredentials(config.getBindPassword());
}
adminConnectionFactory = new ValidatingPoolableLdapConnectionFactory(cc);
if (config.getAdminPoolConfig().lookupOnValidate()) {
adminConnectionFactory.setValidator(new LookupLdapConnectionValidator());
} else {
adminConnectionFactory.setValidator(new DefaultLdapConnectionValidator());
}
if (config.getAdminPoolConfig().getMaxActive() != 0) {
adminPool = new LdapConnectionPool(adminConnectionFactory);
adminPool.setTestOnBorrow(true);
adminPool.setMaxTotal(config.getAdminPoolConfig().getMaxActive());
adminPool.setBlockWhenExhausted(true);
adminPool.setMinEvictableIdleTimeMillis(config.getAdminPoolConfig().getMinEvictableIdleTimeMillis());
adminPool.setTimeBetweenEvictionRunsMillis(config.getAdminPoolConfig().getTimeBetweenEvictionRunsMillis());
adminPool.setNumTestsPerEvictionRun(config.getAdminPoolConfig().getNumTestsPerEvictionRun());
}
// setup unbound connection pool. let's create a new version of the config
cc = createConnectionConfig();
userConnectionFactory = new PoolableUnboundConnectionFactory(cc);
if (config.getUserPoolConfig().lookupOnValidate()) {
userConnectionFactory.setValidator(new UnboundLookupConnectionValidator());
} else {
userConnectionFactory.setValidator(new UnboundConnectionValidator());
}
if (config.getUserPoolConfig().getMaxActive() != 0) {
userPool = new UnboundLdapConnectionPool(userConnectionFactory);
userPool.setTestOnBorrow(true);
userPool.setMaxTotal(config.getUserPoolConfig().getMaxActive());
userPool.setBlockWhenExhausted(true);
userPool.setMinEvictableIdleTimeMillis(config.getUserPoolConfig().getMinEvictableIdleTimeMillis());
userPool.setTimeBetweenEvictionRunsMillis(config.getUserPoolConfig().getTimeBetweenEvictionRunsMillis());
userPool.setNumTestsPerEvictionRun(config.getUserPoolConfig().getNumTestsPerEvictionRun());
}
log.info("LdapIdentityProvider initialized: {}", config);
}
/**
* Creates a new connection config based on the config.
* @return the connection config.
*/
@NotNull
private LdapConnectionConfig createConnectionConfig() {
LdapConnectionConfig cc = new LdapConnectionConfig();
cc.setLdapHost(config.getHostname());
cc.setLdapPort(config.getPort());
cc.setUseSsl(config.useSSL());
cc.setUseTls(config.useTLS());
// todo: implement better trustmanager/keystore management (via sling/felix)
if (config.noCertCheck()) {
cc.setTrustManagers(new NoVerificationTrustManager());
}
if (enabledSSLProtocols != null) {
cc.setEnabledProtocols(enabledSSLProtocols);
}
return cc;
}
@Nullable
private Entry getEntry(@NotNull LdapConnection connection, @NotNull LdapProviderConfig.Identity idConfig, @NotNull String id, @NotNull String[] customAttributes)
throws CursorException, LdapException {
String searchFilter = idConfig.getSearchFilter(id);
// Create the SearchRequest object
SearchRequest req = new SearchRequestImpl();
req.setScope(SearchScope.SUBTREE);
if (customAttributes.length == 0) {
req.addAttributes(SchemaConstants.ALL_USER_ATTRIBUTES);
} else {
req.addAttributes(customAttributes);
}
req.setTimeLimit((int) config.getSearchTimeout());
req.setBase(new Dn(idConfig.getBaseDN()));
req.setFilter(searchFilter);
debug("getEntry: using SearchRequest {}.", req);
// Process the request
SearchCursor searchCursor = null;
Entry resultEntry = null;
try {
searchCursor = connection.search(req);
while (searchCursor.next()) {
if (resultEntry != null) {
log.warn("search for {} returned more than one entry. discarding additional ones.", searchFilter);
} else {
// process the SearchResultEntry
Response response = searchCursor.get();
if (response instanceof SearchResultEntry) {
resultEntry = ((SearchResultEntry) response).getEntry();
}
}
}
} finally {
closeSearchCursor(searchCursor);
}
if (resultEntry == null) {
debug("getEntry: search below {} with {} found 0 entries.", idConfig.getBaseDN(), searchFilter);
} else {
debug("getEntry: search below {} with {} found {}", idConfig.getBaseDN(), searchFilter, resultEntry.getDn());
}
return resultEntry;
}
@NotNull
private SearchResultIterator getEntryIterator(@NotNull LdapProviderConfig.Identity idConfig) throws LdapException, CursorException, ExternalIdentityException {
StringBuilder filter = new StringBuilder();
int num = 0;
for (String objectClass: idConfig.getObjectClasses()) {
num++;
filter.append("(objectclass=")
.append(LdapProviderConfig.encodeFilterValue(objectClass))
.append(')');
}
String extraFilter = idConfig.getExtraFilter();
if (extraFilter != null && !extraFilter.isEmpty()) {
num++;
filter.append(extraFilter);
}
String searchFilter = num > 1
? "(&" + filter + ')'
: filter.toString();
return new SearchResultIterator(searchFilter, idConfig);
}
private final class SearchResultIterator implements Iterator<Entry> {
private final String searchFilter;
private final LdapProviderConfig.Identity idConfig;
private byte[] cookie;
private List<Entry> page = Collections.emptyList();
private boolean searchComplete;
private int pos = -1;
public SearchResultIterator(
@NotNull String searchFilter,
@NotNull LdapProviderConfig.Identity idConfig) throws LdapException, CursorException, ExternalIdentityException {
this.searchFilter = searchFilter;
this.idConfig = idConfig;
findNextEntry();
}
//-------------------------------------------------------< Iterator >---
@Override
public boolean hasNext() {
return pos >= 0;
}
@Override
public Entry next() {
if (hasNext()) {
try {
Entry entry = page.get(pos);
findNextEntry();
return entry;
} catch (LdapException | CursorException | ExternalIdentityException e) {
log.error("Error while performing LDAP search", e);
}
}
throw new NoSuchElementException();
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
//-------------------------------------------------------< internal >---
private SearchRequest createSearchRequest(byte[] cookie, @NotNull String[] userAttributes) throws LdapException {
SearchRequest req = new SearchRequestImpl();
req.setScope(SearchScope.SUBTREE);
if (userAttributes.length == 0) {
req.addAttributes(SchemaConstants.ALL_USER_ATTRIBUTES);
} else {
req.addAttributes(userAttributes);
}
req.setTimeLimit((int) config.getSearchTimeout());
req.setBase(new Dn(idConfig.getBaseDN()));
req.setFilter(searchFilter);
// do paged searches (OAK-2874)
PagedResultsImpl pagedResults = new PagedResultsImpl();
pagedResults.setSize(1000);
pagedResults.setCookie(cookie);
req.addControl(pagedResults);
return req;
}
private boolean loadNextPage() throws ExternalIdentityException, LdapException, CursorException {
if (searchComplete) {
return false;
}
SearchCursor searchCursor = null;
DebugTimer timer = new DebugTimer();
LdapConnection connection = connect();
timer.mark("connect");
page = new ArrayList<>();
try {
SearchRequest req = createSearchRequest(cookie, config.getCustomAttributes());
debug("loadNextPage: using SearchRequest {}.", req);
searchCursor = connection.search(req);
while (searchCursor.next()) {
Response response = searchCursor.get();
if (response instanceof SearchResultEntry) {
Entry resultEntry = ((SearchResultEntry) response).getEntry();
page.add(resultEntry);
debug("loadNextPage: search below {} with {} found {}", idConfig.getBaseDN(), searchFilter, resultEntry.getDn());
}
}
boolean done = searchCursor.isDone();
cookie = null;
if (done) {
SearchResultDone searchResultDone = searchCursor.getSearchResultDone();
if (searchResultDone != null && searchResultDone.getLdapResult().getResultCode() != ResultCodeEnum.UNWILLING_TO_PERFORM) {
PagedResults ctrl = (PagedResults) searchResultDone.getControl(PagedResults.OID);
if (ctrl != null) {
cookie = ctrl.getCookie();
}
}
}
searchComplete = cookie == null || cookie.length == 0;
timer.mark("lookup");
return !page.isEmpty();
} finally {
closeSearchCursor(searchCursor);
disconnect(connection);
}
}
private void findNextEntry() throws LdapException, CursorException, ExternalIdentityException {
if (pos == -1 && !loadNextPage()) {
return;
}
if (pos + 1 == page.size()) {
pos = -1;
page = Collections.emptyList();
if (!loadNextPage()) {
return;
}
}
pos++;
}
}
@NotNull
private ExternalUser createUser(@NotNull Entry entry, @Nullable String id)
throws LdapInvalidAttributeValueException {
return (ExternalUser) createIdentity(entry, id, false);
}
@NotNull
private ExternalGroup createGroup(@NotNull Entry entry, @Nullable String id)
throws LdapInvalidAttributeValueException {
return (ExternalGroup) createIdentity(entry, id, true);
}
@NotNull
private ExternalIdentity createIdentity(@NotNull Entry entry, @Nullable String id, boolean isGroup)
throws LdapInvalidAttributeValueException {
LdapProviderConfig.Identity cfg = isGroup ? config.getGroupConfig() : config.getUserConfig();
if (id == null) {
String idAttribute = cfg.getIdAttribute();
Attribute attr = entry.get(idAttribute);
if (attr == null) {
throw new LdapInvalidAttributeValueException(ResultCodeEnum.CONSTRAINT_VIOLATION,
"no value found for attribute '" + idAttribute + "' for entry " + entry);
}
id = attr.getString();
}
String extId = config.getUseUidForExtId() ? id : entry.getDn().getName();
ExternalIdentityRef ref = new ExternalIdentityRef(extId, this.getName());
String path = cfg.makeDnPath()
? createDNPath(entry.getDn())
: null;
LdapIdentity identity = isGroup ? new LdapGroup(this, ref, id, path, entry)
: new LdapUser(this, ref, id, path, entry);
Map<String, Object> props = identity.getProperties();
applyAttributes(props, entry);
return identity;
}
private void applyAttributes(Map<String, Object> props, Entry entry)
throws LdapInvalidAttributeValueException {
for (Attribute attr: entry.getAttributes()) {
if (attr.isHumanReadable()) {
final Object propValue;
// for multivalue properties, store as collection
if (attr.size() > 1) {
List<String> values = new ArrayList<>();
for (Value value : attr) {
values.add(value.getString());
}
propValue = values;
} else {
propValue = attr.getString();
}
props.put(attr.getId(), propValue);
}
}
}
@NotNull
private LdapConnection connect() throws ExternalIdentityException {
try {
if (adminPool == null) {
return adminConnectionFactory.makeObject().getObject();
} else {
return adminPool.getConnection();
}
} catch (Exception e) {
throw error(e, "Error while connecting to the ldap server.");
}
}
private void disconnect(@Nullable LdapConnection connection) {
try {
if (connection != null) {
if (adminPool == null) {
adminConnectionFactory.destroyObject(new DefaultPooledObject<>(connection));
} else {
adminPool.releaseConnection(connection);
}
}
} catch (Exception e) {
log.warn("Error while disconnecting from the ldap server.", e);
}
}
private boolean isMyRef(@NotNull ExternalIdentityRef ref) {
final String refProviderName = ref.getProviderName();
return refProviderName == null || refProviderName.isEmpty() || getName().equals(refProviderName);
}
/**
* Makes the intermediate path of an DN by splitting along the RDNs
* @param dn the dn of the identity
* @return the intermediate path or {@code null} if disabled by config
*/
private static String createDNPath(Dn dn) {
StringBuilder path = new StringBuilder();
for (Rdn rnd: dn.getRdns()) {
if (path.length() > 0) {
path.append('/');
}
path.append(Text.escapeIllegalJcrChars(rnd.toString()));
}
return path.toString();
}
private static ExternalIdentityException lookupFailedException(@NotNull Exception e, @Nullable DebugTimer timer) {
String msg = "Error during ldap lookup. {}";
log.error(msg, ((timer != null) ? timer.getString() : ""), e);
return new ExternalIdentityException(msg, e);
}
private static ExternalIdentityException error(@NotNull Exception e, @NotNull String msg) {
log.error(msg, e);
return new ExternalIdentityException(msg, e);
}
private static void debug(@NotNull String msg, @NotNull Object... vars) {
if (log.isDebugEnabled()) {
log.debug(msg, vars);
}
}
private static void closeSearchCursor(@Nullable SearchCursor searchCursor) {
if (searchCursor != null) {
try {
searchCursor.close();
} catch (IOException e) {
log.warn("Failed to close search cursor.", e);
}
}
}
}