001/* 002 * (C) Copyright 2006-2007 Nuxeo SAS (http://nuxeo.com/) and contributors. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * Nuxeo - initial API and implementation 016 * 017 * $Id$ 018 */ 019 020package org.nuxeo.ecm.directory.ldap; 021 022import java.util.ArrayList; 023import java.util.Arrays; 024import java.util.HashMap; 025import java.util.List; 026import java.util.Map; 027 028import javax.naming.directory.SearchControls; 029 030import org.apache.commons.lang.StringUtils; 031import org.apache.commons.logging.Log; 032import org.apache.commons.logging.LogFactory; 033import org.nuxeo.common.xmap.annotation.XNode; 034import org.nuxeo.common.xmap.annotation.XNodeList; 035import org.nuxeo.common.xmap.annotation.XNodeMap; 036import org.nuxeo.common.xmap.annotation.XObject; 037import org.nuxeo.ecm.directory.DirectoryException; 038import org.nuxeo.ecm.directory.EntryAdaptor; 039import org.nuxeo.ecm.directory.InverseReference; 040import org.nuxeo.ecm.directory.PermissionDescriptor; 041import org.nuxeo.ecm.directory.Reference; 042 043@XObject(value = "directory") 044public class LDAPDirectoryDescriptor { 045 046 public static final Log log = LogFactory.getLog(LDAPDirectoryDescriptor.class); 047 048 public static final int defaultSearchScope = SearchControls.ONELEVEL_SCOPE; 049 050 public static final String defaultSearchClassesFilter = "(objectClass=*)"; 051 052 @XNode("@name") 053 public String name; 054 055 @XNode("server") 056 public String serverName; 057 058 @XNode("schema") 059 public String schemaName; 060 061 @XNode("searchBaseDn") 062 public String searchBaseDn; 063 064 @XNode("readOnly") 065 public boolean readOnly = true; 066 067 @XNode("cacheEntryName") 068 public String cacheEntryName = null; 069 070 @XNode("cacheEntryWithoutReferencesName") 071 public String cacheEntryWithoutReferencesName = null; 072 073 @XNode("negativeCaching") 074 public Boolean negativeCaching; 075 076 @XNodeMap(value = "fieldMapping", key = "@name", type = HashMap.class, componentType = String.class) 077 public Map<String, String> fieldMapping = new HashMap<String, String>(); 078 079 public String[] searchClasses; 080 081 public String searchClassesFilter; 082 083 public String searchFilter; 084 085 public int searchScope = defaultSearchScope; // default value: onelevel 086 087 public String substringMatchType; 088 089 @XNode("creationBaseDn") 090 public String creationBaseDn; 091 092 @XNodeList(value = "creationClass", componentType = String.class, type = String[].class) 093 public String[] creationClasses; 094 095 @XNode("idField") 096 public String idField; 097 098 @XNode("rdnAttribute") 099 public String rdnAttribute; 100 101 @XNode("passwordField") 102 public String passwordField; 103 104 @XNode("passwordHashAlgorithm") 105 public String passwordHashAlgorithm; 106 107 @XNodeList(value = "references/ldapReference", type = LDAPReference[].class, componentType = LDAPReference.class) 108 private LDAPReference[] ldapReferences; 109 110 @XNodeList(value = "references/inverseReference", type = InverseReference[].class, componentType = InverseReference.class) 111 private InverseReference[] inverseReferences; 112 113 @XNodeList(value = "references/ldapTreeReference", type = LDAPTreeReference[].class, componentType = LDAPTreeReference.class) 114 private LDAPTreeReference[] ldapTreeReferences; 115 116 @XNodeList(value = "permissions/permission", type = PermissionDescriptor[].class, componentType = PermissionDescriptor.class) 117 public PermissionDescriptor[] permissions = null; 118 119 @XNode("emptyRefMarker") 120 public String emptyRefMarker = "cn=emptyRef"; 121 122 @XNode("missingIdFieldCase") 123 public String missingIdFieldCase = "unchanged"; 124 125 /** 126 * Since 5.4.2: force id case to upper or lower, or leaver it unchanged. 127 */ 128 @XNode("idCase") 129 public String idCase = "unchanged"; 130 131 @XNode("querySizeLimit") 132 private int querySizeLimit = 200; 133 134 @XNode("queryTimeLimit") 135 private int queryTimeLimit = 0; // default to wait indefinitely 136 137 // Add attribute to allow to ignore referrals resolution 138 /** 139 * Since 5.9.4 140 */ 141 @XNode("followReferrals") 142 protected boolean followReferrals = true; // default to true 143 144 protected EntryAdaptor entryAdaptor; 145 146 @XObject(value = "entryAdaptor") 147 public static class EntryAdaptorDescriptor { 148 149 @XNode("@class") 150 public Class<? extends EntryAdaptor> adaptorClass; 151 152 @XNodeMap(value = "parameter", key = "@name", type = HashMap.class, componentType = String.class) 153 public Map<String, String> parameters; 154 155 } 156 157 @XNode("entryAdaptor") 158 public void setEntryAdaptor(EntryAdaptorDescriptor adaptorDescriptor) throws InstantiationException, 159 IllegalAccessException { 160 entryAdaptor = adaptorDescriptor.adaptorClass.newInstance(); 161 for (Map.Entry<String, String> paramEntry : adaptorDescriptor.parameters.entrySet()) { 162 entryAdaptor.setParameter(paramEntry.getKey(), paramEntry.getValue()); 163 } 164 } 165 166 /** 167 * @since 5.7 : allow to contribute custom Exception Handler to extract LDAP validation error messages 168 */ 169 @XNode("ldapExceptionHandler") 170 protected Class<? extends LdapExceptionProcessor> exceptionProcessorClass; 171 172 protected LdapExceptionProcessor exceptionProcessor; 173 174 // XXX: passwordEncryption? 175 // XXX: ignoredFields? 176 // XXX: referenceFields? 177 public LDAPDirectoryDescriptor() { 178 } 179 180 public String getRdnAttribute() { 181 return rdnAttribute; 182 } 183 184 public String getCreationBaseDn() { 185 return creationBaseDn; 186 } 187 188 public String[] getCreationClasses() { 189 return creationClasses; 190 } 191 192 public String getIdField() { 193 return idField; 194 } 195 196 public String getIdCase() { 197 return idCase; 198 } 199 200 public String getSchemaName() { 201 return schemaName; 202 } 203 204 public String getSearchBaseDn() { 205 return searchBaseDn; 206 } 207 208 @XNodeList(value = "searchClass", componentType = String.class, type = String[].class) 209 public void setSearchClasses(String[] searchClasses) { 210 this.searchClasses = searchClasses; 211 if (searchClasses == null) { 212 // default searchClassesFilter 213 searchClassesFilter = defaultSearchClassesFilter; 214 return; 215 } 216 List<String> searchClassFilters = new ArrayList<String>(); 217 for (String searchClass : searchClasses) { 218 searchClassFilters.add("(objectClass=" + searchClass + ')'); 219 } 220 searchClassesFilter = StringUtils.join(searchClassFilters.toArray()); 221 222 // logical OR if several classes are provided 223 if (searchClasses.length > 1) { 224 searchClassesFilter = "(|" + searchClassesFilter + ')'; 225 } 226 } 227 228 public String[] getSearchClasses() { 229 return searchClasses; 230 } 231 232 @XNode("searchFilter") 233 public void setSearchFilter(String searchFilter) { 234 if ((searchFilter == null) || searchFilter.equals("(objectClass=*)")) { 235 this.searchFilter = null; 236 return; 237 } 238 if (!searchFilter.startsWith("(") && !searchFilter.endsWith(")")) { 239 searchFilter = '(' + searchFilter + ')'; 240 } 241 this.searchFilter = searchFilter; 242 } 243 244 public String getSearchFilter() { 245 return searchFilter; 246 } 247 248 @XNode("searchScope") 249 public void setSearchScope(String searchScope) throws DirectoryException { 250 if (null == searchScope) { 251 // restore default search scope 252 this.searchScope = defaultSearchScope; 253 return; 254 } 255 Integer scope = LdapScope.getIntegerScope(searchScope); 256 if (null == scope) { 257 // invalid scope 258 throw new DirectoryException("Invalid search scope: " + searchScope 259 + ". Valid options: object, onelevel, subtree"); 260 } 261 this.searchScope = scope.intValue(); 262 } 263 264 public int getSearchScope() { 265 return searchScope; 266 } 267 268 public String getSubstringMatchType() { 269 return substringMatchType; 270 } 271 272 @XNode("substringMatchType") 273 public void setSubstringMatchType(String substringMatchType) { 274 if (substringMatchType == null) { 275 // default behaviour 276 this.substringMatchType = LDAPSubstringMatchType.SUBINITIAL; 277 } else if (LDAPSubstringMatchType.SUBINITIAL.equals(substringMatchType) 278 || LDAPSubstringMatchType.SUBFINAL.equals(substringMatchType) 279 || LDAPSubstringMatchType.SUBANY.equals(substringMatchType)) { 280 this.substringMatchType = substringMatchType; 281 } else { 282 log.error("Invalid substring match type: " + substringMatchType 283 + ". Valid options: subinitial, subfinal, subany"); 284 this.substringMatchType = LDAPSubstringMatchType.SUBINITIAL; 285 } 286 } 287 288 public String getName() { 289 return name; 290 } 291 292 public String getServerName() { 293 return serverName; 294 } 295 296 public String getAggregatedSearchFilter() { 297 if (searchFilter == null) { 298 return searchClassesFilter; 299 } 300 return "(&" + searchClassesFilter + searchFilter + ')'; 301 } 302 303 public String getPasswordField() { 304 return passwordField; 305 } 306 307 public String getPasswordHashAlgorithmField() { 308 return passwordHashAlgorithm; 309 } 310 311 public Map<String, String> getFieldMapping() { 312 return fieldMapping; 313 } 314 315 public void setFieldMapping(Map<String, String> fieldMapping) { 316 this.fieldMapping = fieldMapping; 317 } 318 319 public Reference[] getInverseReferences() { 320 return inverseReferences; 321 } 322 323 public Reference[] getLdapReferences() { 324 List<Reference> refs = new ArrayList<Reference>(); 325 if (ldapReferences != null) { 326 refs.addAll(Arrays.asList(ldapReferences)); 327 } 328 if (ldapTreeReferences != null) { 329 refs.addAll(Arrays.asList(ldapTreeReferences)); 330 } 331 return refs.toArray(new Reference[] {}); 332 } 333 334 public boolean getReadOnly() { 335 return readOnly; 336 } 337 338 public void setReadOnly(boolean readOnly) { 339 this.readOnly = readOnly; 340 } 341 342 public String getEmptyRefMarker() { 343 return emptyRefMarker; 344 } 345 346 public void setEmptyRefMarker(String emptyRefMarker) { 347 this.emptyRefMarker = emptyRefMarker; 348 } 349 350 public int getQuerySizeLimit() { 351 return querySizeLimit; 352 } 353 354 public void setQuerySizeLimit(int querySizeLimit) { 355 this.querySizeLimit = querySizeLimit; 356 } 357 358 public void setQueryTimeLimit(int queryTimeLimit) { 359 this.queryTimeLimit = queryTimeLimit; 360 } 361 362 public int getQueryTimeLimit() { 363 return queryTimeLimit; 364 } 365 366 public EntryAdaptor getEntryAdaptor() { 367 return entryAdaptor; 368 } 369 370 public LdapExceptionProcessor getExceptionProcessor() { 371 if (exceptionProcessor == null) { 372 if (exceptionProcessorClass == null) { 373 exceptionProcessor = new DefaultLdapExceptionProcessor(); 374 } else { 375 try { 376 exceptionProcessor = exceptionProcessorClass.newInstance(); 377 } catch (ReflectiveOperationException e) { 378 log.error("Unable to instanciate custom Exception handler", e); 379 exceptionProcessor = new DefaultLdapExceptionProcessor(); 380 } 381 } 382 } 383 return exceptionProcessor; 384 } 385 386}