1
0
mirror of https://github.com/moparisthebest/davmail synced 2024-12-14 11:42:23 -05:00

LDAP: major refactoring from Dan Foody to improve complex filters handling

git-svn-id: http://svn.code.sf.net/p/davmail/code/trunk@769 3d1905a2-6b24-0410-a738-b14d5a86fcbd
This commit is contained in:
mguessan 2009-10-06 21:13:41 +00:00
parent f268474051
commit db4b9d5a94

View File

@ -22,9 +22,10 @@ import com.sun.jndi.ldap.Ber;
import com.sun.jndi.ldap.BerDecoder; import com.sun.jndi.ldap.BerDecoder;
import com.sun.jndi.ldap.BerEncoder; import com.sun.jndi.ldap.BerEncoder;
import davmail.AbstractConnection; import davmail.AbstractConnection;
import davmail.Settings;
import davmail.BundleMessage; import davmail.BundleMessage;
import davmail.Settings;
import davmail.exception.DavMailException; import davmail.exception.DavMailException;
import davmail.exchange.ExchangeSession;
import davmail.exchange.ExchangeSessionFactory; import davmail.exchange.ExchangeSessionFactory;
import davmail.ui.tray.DavGatewayTray; import davmail.ui.tray.DavGatewayTray;
@ -33,11 +34,7 @@ import java.io.BufferedOutputStream;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.net.Socket; import java.net.*;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.net.InetAddress;
import java.util.*; import java.util.*;
/** /**
@ -522,7 +519,8 @@ public class LdapConnection extends AbstractConnection {
// single user request // single user request
String uid = dn.substring("uid=".length(), dn.indexOf(',')); String uid = dn.substring("uid=".length(), dn.indexOf(','));
// first search in contact // first search in contact
Map<String, Map<String, String>> persons = session.contactFind("\"DAV:uid\"='" + uid + '\''); Map<String, Map<String, String>> persons = session.contactFindByUid(uid);
// then in GAL // then in GAL
if (persons.isEmpty()) { if (persons.isEmpty()) {
persons = session.galFind("AN", uid); persons = session.galFind("AN", uid);
@ -575,32 +573,24 @@ public class LdapConnection extends AbstractConnection {
} }
} else { } else {
// append personal contacts first // append personal contacts first
for (Map<String, String> person : session.contactFind(ldapFilter.getContactSearchFilter()).values()) { String filter = ldapFilter.getContactSearchFilter();
DavGatewayTray.debug(new BundleMessage("LOG_LDAP_REQ_SEARCH", currentMessageId, dn, scope, sizeLimit, timelimit, filter, returningAttributes));
for (Map<String, String> person : session.contactFind(filter).values()) {
persons.put(person.get("uid"), person); persons.put(person.get("uid"), person);
if (persons.size() == sizeLimit) { if (persons.size() == sizeLimit) {
break; break;
} }
} }
for (SimpleFilter simpleFilter : ldapFilter.getOrFilterSet()) {
if (persons.size() < sizeLimit) { for (Map<String, String> person : ldapFilter.findInGAL(session).values()) {
String attributeName = simpleFilter.getGalFindAttributeName();
if (attributeName != null) {
for (Map<String, String> person : session.galFind(attributeName, simpleFilter.value).values()) {
if ((simpleFilter.operator == LDAP_FILTER_SUBSTRINGS)
// exclude non exact match on exact search
|| (simpleFilter.operator == LDAP_FILTER_EQUALITY &&
simpleFilter.value.equalsIgnoreCase(person.get(attributeName)))) {
persons.put(person.get("AN"), person);
}
if (persons.size() == sizeLimit) {
break;
}
}
}
}
if (persons.size() == sizeLimit) { if (persons.size() == sizeLimit) {
break; break;
} }
persons.put(person.get("AN"), person);
} }
} }
@ -648,10 +638,10 @@ public class LdapConnection extends AbstractConnection {
} }
protected LdapFilter parseFilter(BerDecoder reqBer) throws IOException { protected LdapFilter parseFilter(BerDecoder reqBer) throws IOException {
LdapFilter ldapFilter = new LdapFilter(); LdapFilter ldapFilter;
if (reqBer.peekByte() == LDAP_FILTER_PRESENT) { if (reqBer.peekByte() == LDAP_FILTER_PRESENT) {
String attributeName = reqBer.parseStringWithTag(LDAP_FILTER_PRESENT, isLdapV3(), null).toLowerCase(); String attributeName = reqBer.parseStringWithTag(LDAP_FILTER_PRESENT, isLdapV3(), null).toLowerCase();
ldapFilter.addFilter(new SimpleFilter(attributeName)); ldapFilter = new SimpleFilter(attributeName);
if (!"objectclass".equals(attributeName)) { if (!"objectclass".equals(attributeName)) {
DavGatewayTray.warn(new BundleMessage("LOG_LDAP_UNSUPPORTED_FILTER", ldapFilter.toString())); DavGatewayTray.warn(new BundleMessage("LOG_LDAP_UNSUPPORTED_FILTER", ldapFilter.toString()));
} }
@ -660,35 +650,33 @@ public class LdapConnection extends AbstractConnection {
int ldapFilterType = reqBer.parseSeq(seqSize); int ldapFilterType = reqBer.parseSeq(seqSize);
int end = reqBer.getParsePosition() + seqSize[0]; int end = reqBer.getParsePosition() + seqSize[0];
parseNestedFilter(reqBer, ldapFilter, ldapFilterType, end); ldapFilter = parseNestedFilter(reqBer, ldapFilterType, end);
} }
return ldapFilter; return ldapFilter;
} }
protected void parseNestedFilter(BerDecoder reqBer, LdapFilter ldapFilter, int ldapFilterType, int end) throws IOException { protected LdapFilter parseNestedFilter(BerDecoder reqBer, int ldapFilterType, int end) throws IOException {
if (ldapFilterType == LDAP_FILTER_OR) { LdapFilter nestedFilter;
ldapFilter.startFilter(LDAP_FILTER_OR);
// OR filter if ((ldapFilterType == LDAP_FILTER_OR) || (ldapFilterType == LDAP_FILTER_AND)) {
nestedFilter = new CompoundFilter(ldapFilterType);
while (reqBer.getParsePosition() < end && reqBer.bytesLeft() > 0) { while (reqBer.getParsePosition() < end && reqBer.bytesLeft() > 0) {
int ldapFilterOperator = reqBer.parseSeq(null); int[] seqSize = new int[1];
parseNestedFilter(reqBer, ldapFilter, ldapFilterOperator, end); int ldapFilterOperator = reqBer.parseSeq(seqSize);
int subEnd = reqBer.getParsePosition() + seqSize[0];
nestedFilter.add(parseNestedFilter(reqBer, ldapFilterOperator, subEnd));
} }
ldapFilter.endFilter();
} else if (ldapFilterType == LDAP_FILTER_AND) {
ldapFilter.startFilter(LDAP_FILTER_AND);
// AND filter
while (reqBer.getParsePosition() < end && reqBer.bytesLeft() > 0) {
int ldapFilterOperator = reqBer.parseSeq(null);
parseNestedFilter(reqBer, ldapFilter, ldapFilterOperator, end);
}
ldapFilter.endFilter();
} else { } else {
// simple filter // simple filter
parseSimpleFilter(reqBer, ldapFilter, ldapFilterType); nestedFilter = parseSimpleFilter(reqBer, ldapFilterType);
} }
return nestedFilter;
} }
protected void parseSimpleFilter(BerDecoder reqBer, LdapFilter ldapFilter, int ldapFilterOperator) throws IOException { protected LdapFilter parseSimpleFilter(BerDecoder reqBer, int ldapFilterOperator) throws IOException {
String attributeName = reqBer.parseString(isLdapV3()).toLowerCase(); String attributeName = reqBer.parseString(isLdapV3()).toLowerCase();
StringBuilder value = new StringBuilder(); StringBuilder value = new StringBuilder();
@ -721,7 +709,7 @@ public class LdapConnection extends AbstractConnection {
} }
} }
ldapFilter.addFilter(new SimpleFilter(attributeName, sValue, ldapFilterOperator)); return new SimpleFilter(attributeName, sValue, ldapFilterOperator);
} }
protected Set<String> parseReturningAttributes(BerDecoder reqBer) throws IOException { protected Set<String> parseReturningAttributes(BerDecoder reqBer) throws IOException {
@ -873,6 +861,7 @@ public class LdapConnection extends AbstractConnection {
} }
protected String hostName() throws UnknownHostException { protected String hostName() throws UnknownHostException {
// TODO: send multiple computer context with getHostName and getCanonicalHostName
return InetAddress.getLocalHost().getCanonicalHostName(); return InetAddress.getLocalHost().getCanonicalHostName();
} }
@ -979,115 +968,237 @@ public class LdapConnection extends AbstractConnection {
os.flush(); os.flush();
} }
static class LdapFilter { static interface LdapFilter {
final StringBuilder filterString = new StringBuilder(); public String getContactSearchFilter();
int ldapFilterType;
boolean isFullSearch = true;
final Set<SimpleFilter> orCriteria = new HashSet<SimpleFilter>();
final Set<SimpleFilter> andCriteria = new HashSet<SimpleFilter>();
public void addFilter(SimpleFilter simpleFilter) { public Map<String, Map<String, String>> findInGAL(ExchangeSession session) throws IOException;
filterString.append('(').append(simpleFilter.toString()).append(')');
String attributeName = simpleFilter.getAttributeName();
// full search (objectclass=*) filter public void add(LdapFilter filter);
if ("objectclass".equals(attributeName) && SimpleFilter.STAR.equals(simpleFilter.value)) {
isFullSearch = true;
// known search attribute
} else {
isFullSearch = false;
if (ldapFilterType == 0 || ldapFilterType == LDAP_FILTER_OR) {
orCriteria.add(simpleFilter);
} else if (ldapFilterType == LDAP_FILTER_AND) {
andCriteria.add(simpleFilter);
}
// unknown search attribute: warn
if (!IGNORE_MAP.contains(attributeName) && CRITERIA_MAP.get(attributeName) == null && CONTACT_MAP.get(attributeName) == null) {
DavGatewayTray.debug(new BundleMessage("LOG_LDAP_UNSUPPORTED_FILTER_ATTRIBUTE", attributeName, simpleFilter.value));
}
}
} public boolean isFullSearch();
public String getContactSearchFilter() { public boolean isMatch(Map<String, String> person);
if (orCriteria.isEmpty()) { }
return null;
} else { static class CompoundFilter implements LdapFilter {
StringBuilder buffer = new StringBuilder(); final Set<LdapFilter> criteria = new HashSet<LdapFilter>();
for (SimpleFilter simpleFilter : orCriteria) { int type;
String contactAttributeName = simpleFilter.getContactAttributeName();
if (contactAttributeName != null) {
if (buffer.length() > 0) { CompoundFilter(int filterType) {
buffer.append(" OR "); type = filterType;
}
buffer.append('"').append(contactAttributeName).append('"');
if (simpleFilter.operator == LDAP_FILTER_EQUALITY) {
buffer.append(" = '").append(simpleFilter.value).append('\'');
} else if ("mail".equals(simpleFilter.getAttributeName())) {
buffer.append(" LIKE '\"").append(simpleFilter.value).append("%'");
} else {
buffer.append(" LIKE '").append(simpleFilter.value).append("%'");
}
}
}
// if criteria not empty but filter is, add a fake filter
if (!orCriteria.isEmpty() && buffer.length() == 0) {
buffer.append("\"DAV:uid\"='#INVALID#'");
}
return buffer.toString();
}
} }
@Override @Override
public String toString() { public String toString() {
return filterString.toString(); StringBuilder buffer = new StringBuilder();
}
public void startFilter(int ldapFilterType) { if (type == LDAP_FILTER_OR) {
this.ldapFilterType = ldapFilterType; buffer.append("(|");
filterString.append('('); } else {
if (ldapFilterType == LDAP_FILTER_OR) { buffer.append("(&");
filterString.append('|');
} else if (ldapFilterType == LDAP_FILTER_AND) {
filterString.append('&');
} }
for (LdapFilter child : criteria) {
buffer.append(child.toString());
}
buffer.append(')');
return buffer.toString();
} }
public void endFilter() { /**
ldapFilterType = 0; * Add child filter
filterString.append(')'); *
* @param filter inner filter
*/
public void add(LdapFilter filter) {
criteria.add(filter);
} }
/**
* This is only a full search if every child
* is also a full search
*
* @return true if full search filter
*/
public boolean isFullSearch() { public boolean isFullSearch() {
return isFullSearch; for (LdapFilter child : criteria) {
if (!child.isFullSearch()) {
return false;
}
}
return true;
} }
public Set<SimpleFilter> getOrFilterSet() { /**
return orCriteria; * Build search filter for Contacts folder search.
* Use Exchange SEARCH syntax
*
* @return contact search filter
*/
public String getContactSearchFilter() {
StringBuilder buffer = new StringBuilder();
String op;
if (type == LDAP_FILTER_OR) {
op = " OR ";
} else {
op = " AND ";
}
buffer.append('(');
for (LdapFilter child : criteria) {
String childFilter = child.getContactSearchFilter();
if (childFilter != null) {
if (buffer.length() > 1) {
buffer.append(op);
}
buffer.append(childFilter);
}
}
// empty filter
if (buffer.length() == 1) {
return null;
}
buffer.append(')');
return buffer.toString();
}
/**
* Test if person matches the current filter.
*
* @param person person attributes map
* @return true if filter match
*/
public boolean isMatch(Map<String, String> person) {
if (type == LDAP_FILTER_OR) {
for (LdapFilter child : criteria) {
if (!child.isFullSearch()) {
if (child.isMatch(person)) {
// We've found a match
return true;
}
}
}
// No subconditions are met
return false;
} else if (type == LDAP_FILTER_AND) {
for (LdapFilter child : criteria) {
if (!child.isFullSearch()) {
if (!child.isMatch(person)) {
// We've found a miss
return false;
}
}
}
// All subconditions are met
return true;
}
return false;
}
/**
* Find persons in Exchange GAL matching filter.
* Iterate over child filters to build results.
*
* @param session Exchange session
* @return persons map
* @throws IOException on error
*/
public Map<String, Map<String, String>> findInGAL(ExchangeSession session) throws IOException {
Map<String, Map<String, String>> persons = null;
for (LdapFilter child : criteria) {
Map<String, Map<String, String>> childFind = child.findInGAL(session);
if (childFind != null) {
if (persons == null) {
persons = childFind;
} else if (type == LDAP_FILTER_OR) {
// Create the union of the existing results and the child found results
persons.putAll(childFind);
} else if (type == LDAP_FILTER_AND) {
// Append current child filter results that match all child filters to persons.
// The hard part is that, due to the 100-item-returned galFind limit
// we may catch new items that match all child filters in each child search.
// Thus, instead of building the intersection, we check each result against
// all filters.
for (Map<String, String> result : childFind.values()) {
if (isMatch(result)) {
// This item from the child result set matches all sub-criteria, add it
persons.put(result.get("AN"), result);
}
}
}
}
}
if ((persons == null) && !isFullSearch()) {
// return an empty map (indicating no results were found)
return new HashMap<String, Map<String, String>>();
}
return persons;
} }
} }
static class SimpleFilter { static class SimpleFilter implements LdapFilter {
static final String STAR = "*"; static final String STAR = "*";
final String attributeName; final String attributeName;
final String value; final String value;
final int operator; final int operator;
boolean canIgnore;
SimpleFilter(String attributeName) { SimpleFilter(String attributeName) {
this.attributeName = attributeName; this.attributeName = attributeName;
this.value = SimpleFilter.STAR; this.value = SimpleFilter.STAR;
this.operator = LDAP_FILTER_SUBSTRINGS; this.operator = LDAP_FILTER_SUBSTRINGS;
this.canIgnore = checkIgnore();
} }
SimpleFilter(String attributeName, String value, int ldapFilterOperator) { SimpleFilter(String attributeName, String value, int ldapFilterOperator) {
this.attributeName = attributeName; this.attributeName = attributeName;
this.value = value; this.value = value;
this.operator = ldapFilterOperator; this.operator = ldapFilterOperator;
this.canIgnore = checkIgnore();
}
private boolean checkIgnore() {
if ("objectclass".equals(attributeName) && STAR.equals(value)) {
// ignore cases where any object class can match
return true;
} else if (IGNORE_MAP.contains(attributeName)) {
// Ignore this specific attribute
return true;
} else if (CRITERIA_MAP.get(attributeName) == null && CONTACT_MAP.get(attributeName) == null) {
DavGatewayTray.debug(new BundleMessage("LOG_LDAP_UNSUPPORTED_FILTER_ATTRIBUTE",
attributeName, value));
return true;
}
return false;
}
public boolean isFullSearch() {
// This is a full search if we ignore this attribute
return canIgnore;
} }
@Override @Override
public String toString() { public String toString() {
StringBuilder buffer = new StringBuilder(); StringBuilder buffer = new StringBuilder();
buffer.append('(');
buffer.append(attributeName); buffer.append(attributeName);
buffer.append('='); buffer.append('=');
if (SimpleFilter.STAR.equals(value)) { if (SimpleFilter.STAR.equals(value)) {
@ -1097,9 +1208,99 @@ public class LdapConnection extends AbstractConnection {
} else { } else {
buffer.append(value); buffer.append(value);
} }
buffer.append(')');
return buffer.toString(); return buffer.toString();
} }
public String getContactSearchFilter() {
StringBuilder buffer;
String contactAttributeName = getContactAttributeName();
if (canIgnore || (contactAttributeName == null)) {
return null;
}
buffer = new StringBuilder();
buffer.append('"').append(contactAttributeName).append('"');
if (operator == LDAP_FILTER_EQUALITY) {
buffer.append("='").append(value).append('\'');
} else {
buffer.append(" LIKE '%").append(value).append("%'");
}
// TODO: handle startsWith
// TODO: handle empty after parse filter
/*
// if criteria not empty but filter is, add a fake filter
if (!orCriteria.isEmpty() && buffer.length() == 0) {
buffer.append("\"DAV:uid\"='#INVALID#'");
}
*/
return buffer.toString();
}
public boolean isMatch(Map<String, String> person) {
if (canIgnore) {
// Ignore this filter
return true;
}
String personAttributeValue = person.get(getGalFindAttributeName());
if (personAttributeValue == null) {
// No value to allow for filter match
return false;
} else if (value == null) {
// This is a presence filter: found
return true;
} else if ((operator == LDAP_FILTER_EQUALITY) && personAttributeValue.equalsIgnoreCase(value)) {
// Found an exact match
return true;
} else if ((operator == LDAP_FILTER_SUBSTRINGS) && (personAttributeValue.toLowerCase().indexOf(value.toLowerCase()) >= 0)) {
// Found a substring match
return true;
}
return false;
}
public Map<String, Map<String, String>> findInGAL(ExchangeSession session) throws IOException {
if (canIgnore) {
return null;
}
String galFindAttributeName = getGalFindAttributeName();
if (galFindAttributeName != null) {
Map<String, Map<String, String>> galPersons = session.galFind(galFindAttributeName, value);
if (operator == LDAP_FILTER_EQUALITY) {
// Make sure only exact matches are returned
Map<String, Map<String, String>> results = new HashMap<String, Map<String, String>>();
for (Map<String, String> person : galPersons.values()) {
if (isMatch(person)) {
// Found an exact match
results.put(person.get("AN"), person);
}
}
return results;
} else {
return galPersons;
}
}
return null;
}
public void add(LdapFilter filter) {
// Should never be called
DavGatewayTray.error(new BundleMessage("LOG_LDAP_UNSUPPORTED_FILTER", "nested simple filters"));
}
public String getAttributeName() { public String getAttributeName() {
return attributeName; return attributeName;
} }