1
0
mirror of https://github.com/moparisthebest/davmail synced 2024-12-13 19:22:22 -05:00

LDAP: experimental contact search support

git-svn-id: http://svn.code.sf.net/p/davmail/code/trunk@682 3d1905a2-6b24-0410-a738-b14d5a86fcbd
This commit is contained in:
mguessan 2009-08-29 23:49:45 +00:00
parent 692d9faab8
commit 689727a389
2 changed files with 220 additions and 32 deletions

View File

@ -85,6 +85,7 @@ public class ExchangeSession {
WELL_KNOWN_FOLDERS.add(DavPropertyName.create("sendmsg", URN_SCHEMAS_HTTPMAIL)); WELL_KNOWN_FOLDERS.add(DavPropertyName.create("sendmsg", URN_SCHEMAS_HTTPMAIL));
WELL_KNOWN_FOLDERS.add(DavPropertyName.create("drafts", URN_SCHEMAS_HTTPMAIL)); WELL_KNOWN_FOLDERS.add(DavPropertyName.create("drafts", URN_SCHEMAS_HTTPMAIL));
WELL_KNOWN_FOLDERS.add(DavPropertyName.create("calendar", URN_SCHEMAS_HTTPMAIL)); WELL_KNOWN_FOLDERS.add(DavPropertyName.create("calendar", URN_SCHEMAS_HTTPMAIL));
WELL_KNOWN_FOLDERS.add(DavPropertyName.create("contacts", URN_SCHEMAS_HTTPMAIL));
} }
protected static final DavPropertyNameSet DISPLAY_NAME = new DavPropertyNameSet(); protected static final DavPropertyNameSet DISPLAY_NAME = new DavPropertyNameSet();
@ -126,6 +127,7 @@ public class ExchangeSession {
private String sendmsgUrl; private String sendmsgUrl;
private String draftsUrl; private String draftsUrl;
private String calendarUrl; private String calendarUrl;
private String contactsUrl;
/** /**
* Base user mailboxes path (used to select folder) * Base user mailboxes path (used to select folder)
@ -511,12 +513,14 @@ public class ExchangeSession {
sendmsgUrl = getURIPropertyIfExists(properties, "sendmsg", URN_SCHEMAS_HTTPMAIL); sendmsgUrl = getURIPropertyIfExists(properties, "sendmsg", URN_SCHEMAS_HTTPMAIL);
draftsUrl = getURIPropertyIfExists(properties, "drafts", URN_SCHEMAS_HTTPMAIL); draftsUrl = getURIPropertyIfExists(properties, "drafts", URN_SCHEMAS_HTTPMAIL);
calendarUrl = getURIPropertyIfExists(properties, "calendar", URN_SCHEMAS_HTTPMAIL); calendarUrl = getURIPropertyIfExists(properties, "calendar", URN_SCHEMAS_HTTPMAIL);
contactsUrl = getURIPropertyIfExists(properties, "contacts", URN_SCHEMAS_HTTPMAIL);
LOGGER.debug("Inbox URL : " + inboxUrl + LOGGER.debug("Inbox URL : " + inboxUrl +
" Trash URL : " + deleteditemsUrl + " Trash URL : " + deleteditemsUrl +
" Sent URL : " + sentitemsUrl + " Sent URL : " + sentitemsUrl +
" Send URL : " + sendmsgUrl + " Send URL : " + sendmsgUrl +
" Drafts URL : " + draftsUrl + " Drafts URL : " + draftsUrl +
" Calendar URL : " + calendarUrl " Calendar URL : " + calendarUrl +
" Contacts URL : " + contactsUrl
); );
} }
@ -1983,7 +1987,7 @@ public class ExchangeSession {
writer.write(participants.organizer); writer.write(participants.organizer);
writer.write("\r\n"); writer.write("\r\n");
} }
// if not organizer, set REPLYTIME to force Outlook in attendee mode // if not organizer, set REPLYTIME to force Outlook in attendee mode
if (participants.organizer != null && !email.equalsIgnoreCase(participants.organizer)) { if (participants.organizer != null && !email.equalsIgnoreCase(participants.organizer)) {
if (icsBody.indexOf("METHOD:") < 0) { if (icsBody.indexOf("METHOD:") < 0) {
icsBody = icsBody.replaceAll("BEGIN:VCALENDAR", "BEGIN:VCALENDAR\r\nMETHOD:REQUEST"); icsBody = icsBody.replaceAll("BEGIN:VCALENDAR", "BEGIN:VCALENDAR\r\nMETHOD:REQUEST");
@ -2394,6 +2398,112 @@ public class ExchangeSession {
return results; return results;
} }
/**
* Search users in contacts folder
*
* @param searchFilter search filter
* @return List of users
* @throws IOException on error
*/
public Map<String, Map<String, String>> contactFind(String searchFilter) throws IOException {
StringBuilder searchRequest = new StringBuilder();
searchRequest.append("Select \"DAV:uid\", \"urn:schemas:contacts:email1\", \"urn:schemas:contacts:cn\"," +
" \"urn:schemas:contacts:givenName\",\"urn:schemas:contacts:sn\", \"urn:schemas:contacts:title\"," +
"\"urn:schemas:contacts:o\", \"urn:schemas:contacts:location\", \"urn:schemas:contacts:department\"," +
"\"urn:schemas:contacts:telephoneNumber\", \"urn:schemas:contacts:initials\", \"urn:schemas:contacts:street\"," +
"\"urn:schemas:contacts:st\", \"urn:schemas:contacts:c\", \"urn:schemas:contacts:mobile\"")
.append(" FROM Scope('SHALLOW TRAVERSAL OF \"").append(contactsUrl).append("\"')\n");
if (searchFilter != null) {
searchRequest.append(" WHERE ").append(searchFilter);
}
MultiStatusResponse[] responses = DavGatewayHttpClientFacade.executeSearchMethod(
httpClient, URIUtil.encodePath(contactsUrl), searchRequest.toString());
Map<String, Map<String, String>> results = new HashMap<String, Map<String, String>>();
Map<String, String> item;
for (MultiStatusResponse response : responses) {
DavPropertySet properties = response.getProperties(HttpStatus.SC_OK);
// TODO: improve mapping
String uid = getPropertyIfExists(properties, "uid", Namespace.getNamespace("DAV:"));
String email1 = getPropertyIfExists(properties, "email1", Namespace.getNamespace("urn:schemas:contacts:"));
// patch email
if (email1 != null && email1.startsWith("\"")) {
int endIndex = email1.indexOf('\"', 1);
if (endIndex > 0) {
email1 = email1.substring(1, endIndex);
}
}
String cn = getPropertyIfExists(properties, "cn", Namespace.getNamespace("urn:schemas:contacts:"));
String givenName = getPropertyIfExists(properties, "givenName", Namespace.getNamespace("urn:schemas:contacts:"));
String sn = getPropertyIfExists(properties, "sn", Namespace.getNamespace("urn:schemas:contacts:"));
String title = getPropertyIfExists(properties, "title", Namespace.getNamespace("urn:schemas:contacts:"));
String company = getPropertyIfExists(properties, "o", Namespace.getNamespace("urn:schemas:contacts:"));
String location = getPropertyIfExists(properties, "location", Namespace.getNamespace("urn:schemas:contacts:"));
String department = getPropertyIfExists(properties, "department", Namespace.getNamespace("urn:schemas:contacts:"));
String telephoneNumber = getPropertyIfExists(properties, "telephoneNumber", Namespace.getNamespace("urn:schemas:contacts:"));
String initials = getPropertyIfExists(properties, "initials", Namespace.getNamespace("urn:schemas:contacts:"));
String street = getPropertyIfExists(properties, "street", Namespace.getNamespace("urn:schemas:contacts:"));
String st = getPropertyIfExists(properties, "st", Namespace.getNamespace("urn:schemas:contacts:"));
String postalcode = getPropertyIfExists(properties, "postalcode", Namespace.getNamespace("urn:schemas:contacts:"));
String c = getPropertyIfExists(properties, "c", Namespace.getNamespace("urn:schemas:contacts:"));
String mobile = getPropertyIfExists(properties, "mobile", Namespace.getNamespace("urn:schemas:contacts:"));
item = new HashMap<String, String>();
if (uid != null) {
item.put("AN", uid);
}
if (email1 != null) {
item.put("EM", email1);
}
if (cn != null) {
item.put("DN", cn);
}
if (givenName != null) {
item.put("first", givenName);
}
if (sn != null) {
item.put("last", sn);
}
if (title != null) {
item.put("TL", title);
}
if (company != null) {
item.put("CP", company);
}
if (location != null) {
item.put("OFFICE", location);
}
if (department != null) {
item.put("department", department);
}
if (telephoneNumber != null) {
item.put("PH", telephoneNumber);
}
if (initials != null) {
item.put("initials", initials);
}
if (street != null) {
item.put("street", street);
}
if (st != null) {
item.put("state", st);
}
if (postalcode != null) {
item.put("zip", postalcode);
}
if (c != null) {
item.put("country", c);
}
if (mobile != null) {
item.put("mobile", mobile);
}
results.put(uid, item);
}
LOGGER.debug("contactFind " + searchFilter + ": " + results.size() + " result(s)");
return results;
}
/** /**
* Get extended address book information for person with gallookup. * Get extended address book information for person with gallookup.
* Does not work with Exchange 2007 * Does not work with Exchange 2007
@ -2413,8 +2523,10 @@ public class ExchangeSession {
// add detailed information // add detailed information
if (!results.isEmpty()) { if (!results.isEmpty()) {
Map<String, String> fullperson = results.get(person.get("AN").toLowerCase()); Map<String, String> fullperson = results.get(person.get("AN").toLowerCase());
for (Map.Entry<String, String> entry : fullperson.entrySet()) { if (fullperson != null) {
person.put(entry.getKey(), entry.getValue()); for (Map.Entry<String, String> entry : fullperson.entrySet()) {
person.put(entry.getKey(), entry.getValue());
}
} }
} }
} catch (IOException e) { } catch (IOException e) {

View File

@ -1,5 +1,5 @@
/* /*
* DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
* Copyright (C) 2009 Mickael Guessant * Copyright (C) 2009 Mickael Guessant
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
@ -201,8 +201,25 @@ public class LdapConnection extends AbstractConnection {
CRITERIA_MAP.put("apple-group-realname", "DP"); CRITERIA_MAP.put("apple-group-realname", "DP");
} }
static final HashMap<String, String> CONTACT_MAP = new HashMap<String, String>();
static {
CONTACT_MAP.put("uid", "DAV:uid");
CONTACT_MAP.put("mail", "urn:schemas:contacts:email1");
CONTACT_MAP.put("displayname", "urn:schemas:contacts:cn");
CONTACT_MAP.put("cn", "urn:schemas:contacts:cn");
CONTACT_MAP.put("givenname", "urn:schemas:contacts:givenName");
CONTACT_MAP.put("sn", "urn:schemas:contacts:sn");
CONTACT_MAP.put("title", "urn:schemas:contacts:title");
CONTACT_MAP.put("company", "urn:schemas:contacts:company");
CONTACT_MAP.put("o", "urn:schemas:contacts:company");
CONTACT_MAP.put("l", "urn:schemas:contacts:location");
CONTACT_MAP.put("department", "urn:schemas:contacts:department");
CONTACT_MAP.put("apple-group-realname", "urn:schemas:contacts:department");
}
/** /**
* LDAP to Exchange Criteria Map * LDAP filter attributes ignore map
*/ */
static final HashSet<String> IGNORE_MAP = new HashSet<String>(); static final HashSet<String> IGNORE_MAP = new HashSet<String>();
@ -504,6 +521,13 @@ public class LdapConnection extends AbstractConnection {
if (session != null) { if (session != null) {
Map<String, Map<String, String>> persons = new HashMap<String, Map<String, String>>(); Map<String, Map<String, String>> persons = new HashMap<String, Map<String, String>>();
if (ldapFilter.isFullSearch()) { if (ldapFilter.isFullSearch()) {
// append personal contacts first
for (Map<String, String> person : session.contactFind(null).values()) {
persons.put(person.get("AN"), person);
if (persons.size() == sizeLimit) {
break;
}
}
// full search // full search
for (char c = 'A'; c < 'Z'; c++) { for (char c = 'A'; c < 'Z'; c++) {
if (persons.size() < sizeLimit) { if (persons.size() < sizeLimit) {
@ -519,12 +543,20 @@ public class LdapConnection extends AbstractConnection {
} }
} }
} else { } else {
for (Map.Entry<String, SimpleFilter> entry : ldapFilter.getOrFilterEntrySet()) { // append personal contacts first
for (Map<String, String> person : session.contactFind(ldapFilter.getContactSearchFilter()).values()) {
persons.put(person.get("AN"), person);
if (persons.size() == sizeLimit) {
break;
}
}
for (SimpleFilter simpleFilter : ldapFilter.getOrFilterSet()) {
if (persons.size() < sizeLimit) { if (persons.size() < sizeLimit) {
for (Map<String, String> person : session.galFind(entry.getKey(), entry.getValue().value).values()) { for (Map<String, String> person : session.galFind(simpleFilter.getGalFindAttributeName(), simpleFilter.value).values()) {
if ((entry.getValue().operator == LDAP_FILTER_SUBSTRINGS) if ((simpleFilter.operator == LDAP_FILTER_SUBSTRINGS)
|| (entry.getValue().operator == LDAP_FILTER_EQUALITY && // exclude non exact match on exact search
entry.getValue().value.equalsIgnoreCase(person.get(entry.getKey())))) { || (simpleFilter.operator == LDAP_FILTER_EQUALITY &&
simpleFilter.value.equalsIgnoreCase(person.get(simpleFilter.getGalFindAttributeName())))) {
persons.put(person.get("AN"), person); persons.put(person.get("AN"), person);
} }
if (persons.size() == sizeLimit) { if (persons.size() == sizeLimit) {
@ -584,7 +616,7 @@ public class LdapConnection extends AbstractConnection {
LdapFilter ldapFilter = new LdapFilter(); LdapFilter ldapFilter = new 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(attributeName, new SimpleFilter()); ldapFilter.addFilter(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()));
} }
@ -654,7 +686,7 @@ public class LdapConnection extends AbstractConnection {
} }
} }
ldapFilter.addFilter(attributeName, new SimpleFilter(sValue, ldapFilterOperator)); ldapFilter.addFilter(new SimpleFilter(attributeName, sValue, ldapFilterOperator));
} }
protected Set<String> parseReturningAttributes(BerDecoder reqBer) throws IOException { protected Set<String> parseReturningAttributes(BerDecoder reqBer) throws IOException {
@ -885,28 +917,53 @@ public class LdapConnection extends AbstractConnection {
final StringBuilder filterString = new StringBuilder(); final StringBuilder filterString = new StringBuilder();
int ldapFilterType; int ldapFilterType;
boolean isFullSearch = true; boolean isFullSearch = true;
final Map<String, SimpleFilter> orCriteria = new HashMap<String, SimpleFilter>(); final Set<SimpleFilter> orCriteria = new HashSet<SimpleFilter>();
final Map<String, SimpleFilter> andCriteria = new HashMap<String, SimpleFilter>(); final Set<SimpleFilter> andCriteria = new HashSet<SimpleFilter>();
public void addFilter(String attributeName, SimpleFilter simpleFilter) { public void addFilter(SimpleFilter simpleFilter) {
filterString.append('(').append(attributeName).append('=').append(simpleFilter.toString()).append(')'); filterString.append('(').append(simpleFilter.toString()).append(')');
String attributeName = simpleFilter.getAttributeName();
String exchangeAttributeName = CRITERIA_MAP.get(attributeName); // known search attribute
if (exchangeAttributeName != null) { if (CRITERIA_MAP.get(attributeName) != null) {
isFullSearch = false; isFullSearch = false;
if (ldapFilterType == 0 || ldapFilterType == LDAP_FILTER_OR) { if (ldapFilterType == 0 || ldapFilterType == LDAP_FILTER_OR) {
orCriteria.put(exchangeAttributeName, simpleFilter); orCriteria.add(simpleFilter);
} else if (ldapFilterType == LDAP_FILTER_AND) { } else if (ldapFilterType == LDAP_FILTER_AND) {
andCriteria.put(exchangeAttributeName, simpleFilter); andCriteria.add(simpleFilter);
} }
// full search (objectclass=*) filter
} else if ("objectclass".equals(attributeName) && SimpleFilter.STAR.equals(simpleFilter.value)) { } else if ("objectclass".equals(attributeName) && SimpleFilter.STAR.equals(simpleFilter.value)) {
isFullSearch = true; isFullSearch = true;
// ignore search attribute
} else if (IGNORE_MAP.contains(attributeName)) {
DavGatewayTray.debug(new BundleMessage("LOG_LDAP_IGNORE_FILTER_ATTRIBUTE", attributeName, simpleFilter.value));
// unknown search attribute: warn
} else { } else {
if (IGNORE_MAP.contains(attributeName)) { DavGatewayTray.warn(new BundleMessage("LOG_LDAP_UNSUPPORTED_FILTER_ATTRIBUTE", attributeName, simpleFilter.value));
DavGatewayTray.debug(new BundleMessage("LOG_LDAP_IGNORE_FILTER_ATTRIBUTE", attributeName, simpleFilter.value)); }
} else { }
DavGatewayTray.warn(new BundleMessage("LOG_LDAP_UNSUPPORTED_FILTER_ATTRIBUTE", attributeName, simpleFilter.value));
public String getContactSearchFilter() {
if (orCriteria.isEmpty()) {
return null;
} else {
StringBuilder buffer = new StringBuilder();
for (SimpleFilter simpleFilter : orCriteria) {
if (buffer.length() > 0) {
buffer.append(" OR ");
}
buffer.append('"').append(simpleFilter.getContactAttributeName()).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("%'");
}
} }
return buffer.toString();
} }
} }
@ -934,35 +991,54 @@ public class LdapConnection extends AbstractConnection {
return isFullSearch; return isFullSearch;
} }
public Set<Map.Entry<String, SimpleFilter>> getOrFilterEntrySet() { public Set<SimpleFilter> getOrFilterSet() {
return orCriteria.entrySet(); return orCriteria;
} }
} }
static class SimpleFilter { static class SimpleFilter {
static final String STAR = "*"; static final String STAR = "*";
final String attributeName;
final String value; final String value;
final int operator; final int operator;
SimpleFilter() { SimpleFilter(String attributeName) {
this.attributeName = attributeName;
this.value = SimpleFilter.STAR; this.value = SimpleFilter.STAR;
this.operator = LDAP_FILTER_SUBSTRINGS; this.operator = LDAP_FILTER_SUBSTRINGS;
} }
SimpleFilter(String value, int ldapFilterOperator) { SimpleFilter(String attributeName, String value, int ldapFilterOperator) {
this.attributeName = attributeName;
this.value = value; this.value = value;
this.operator = ldapFilterOperator; this.operator = ldapFilterOperator;
} }
@Override @Override
public String toString() { public String toString() {
StringBuilder buffer = new StringBuilder();
buffer.append(attributeName);
buffer.append('=');
if (SimpleFilter.STAR.equals(value)) { if (SimpleFilter.STAR.equals(value)) {
return SimpleFilter.STAR; buffer.append(SimpleFilter.STAR);
} else if (operator == LDAP_FILTER_SUBSTRINGS) { } else if (operator == LDAP_FILTER_SUBSTRINGS) {
return SimpleFilter.STAR + value + SimpleFilter.STAR; buffer.append(SimpleFilter.STAR).append(value).append(SimpleFilter.STAR);
} else { } else {
return value; buffer.append(value);
} }
return buffer.toString();
}
public String getAttributeName() {
return attributeName;
}
public String getGalFindAttributeName() {
return CRITERIA_MAP.get(attributeName);
}
public String getContactAttributeName() {
return CONTACT_MAP.get(attributeName);
} }
} }