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 @XNodeMap(value = "fieldMapping", key = "@name", type = HashMap.class, componentType = String.class) 074 public Map<String, String> fieldMapping = new HashMap<String, String>(); 075 076 public String[] searchClasses; 077 078 public String searchClassesFilter; 079 080 public String searchFilter; 081 082 public int searchScope = defaultSearchScope; // default value: onelevel 083 084 public String substringMatchType; 085 086 @XNode("creationBaseDn") 087 public String creationBaseDn; 088 089 @XNodeList(value = "creationClass", componentType = String.class, type = String[].class) 090 public String[] creationClasses; 091 092 @XNode("idField") 093 public String idField; 094 095 @XNode("rdnAttribute") 096 public String rdnAttribute; 097 098 @XNode("passwordField") 099 public String passwordField; 100 101 @XNode("passwordHashAlgorithm") 102 public String passwordHashAlgorithm; 103 104 @XNodeList(value = "references/ldapReference", type = LDAPReference[].class, componentType = LDAPReference.class) 105 private LDAPReference[] ldapReferences; 106 107 @XNodeList(value = "references/inverseReference", type = InverseReference[].class, componentType = InverseReference.class) 108 private InverseReference[] inverseReferences; 109 110 @XNodeList(value = "references/ldapTreeReference", type = LDAPTreeReference[].class, componentType = LDAPTreeReference.class) 111 private LDAPTreeReference[] ldapTreeReferences; 112 113 @XNodeList(value = "permissions/permission", type = PermissionDescriptor[].class, componentType = PermissionDescriptor.class) 114 public PermissionDescriptor[] permissions = null; 115 116 @XNode("emptyRefMarker") 117 public String emptyRefMarker = "cn=emptyRef"; 118 119 @XNode("missingIdFieldCase") 120 public String missingIdFieldCase = "unchanged"; 121 122 /** 123 * Since 5.4.2: force id case to upper or lower, or leaver it unchanged. 124 */ 125 @XNode("idCase") 126 public String idCase = "unchanged"; 127 128 @XNode("querySizeLimit") 129 private int querySizeLimit = 200; 130 131 @XNode("queryTimeLimit") 132 private int queryTimeLimit = 0; // default to wait indefinitely 133 134 // Add attribute to allow to ignore referrals resolution 135 /** 136 * Since 5.9.4 137 */ 138 @XNode("followReferrals") 139 protected boolean followReferrals = true; // default to true 140 141 protected EntryAdaptor entryAdaptor; 142 143 @XObject(value = "entryAdaptor") 144 public static class EntryAdaptorDescriptor { 145 146 @XNode("@class") 147 public Class<? extends EntryAdaptor> adaptorClass; 148 149 @XNodeMap(value = "parameter", key = "@name", type = HashMap.class, componentType = String.class) 150 public Map<String, String> parameters; 151 152 } 153 154 @XNode("entryAdaptor") 155 public void setEntryAdaptor(EntryAdaptorDescriptor adaptorDescriptor) throws InstantiationException, 156 IllegalAccessException { 157 entryAdaptor = adaptorDescriptor.adaptorClass.newInstance(); 158 for (Map.Entry<String, String> paramEntry : adaptorDescriptor.parameters.entrySet()) { 159 entryAdaptor.setParameter(paramEntry.getKey(), paramEntry.getValue()); 160 } 161 } 162 163 /** 164 * @since 5.7 : allow to contribute custom Exception Handler to extract LDAP validation error messages 165 */ 166 @XNode("ldapExceptionHandler") 167 protected Class<? extends LdapExceptionProcessor> exceptionProcessorClass; 168 169 protected LdapExceptionProcessor exceptionProcessor; 170 171 // XXX: passwordEncryption? 172 // XXX: ignoredFields? 173 // XXX: referenceFields? 174 public LDAPDirectoryDescriptor() { 175 } 176 177 public String getRdnAttribute() { 178 return rdnAttribute; 179 } 180 181 public String getCreationBaseDn() { 182 return creationBaseDn; 183 } 184 185 public String[] getCreationClasses() { 186 return creationClasses; 187 } 188 189 public String getIdField() { 190 return idField; 191 } 192 193 public String getIdCase() { 194 return idCase; 195 } 196 197 public String getSchemaName() { 198 return schemaName; 199 } 200 201 public String getSearchBaseDn() { 202 return searchBaseDn; 203 } 204 205 @XNodeList(value = "searchClass", componentType = String.class, type = String[].class) 206 public void setSearchClasses(String[] searchClasses) { 207 this.searchClasses = searchClasses; 208 if (searchClasses == null) { 209 // default searchClassesFilter 210 searchClassesFilter = defaultSearchClassesFilter; 211 return; 212 } 213 List<String> searchClassFilters = new ArrayList<String>(); 214 for (String searchClass : searchClasses) { 215 searchClassFilters.add("(objectClass=" + searchClass + ')'); 216 } 217 searchClassesFilter = StringUtils.join(searchClassFilters.toArray()); 218 219 // logical OR if several classes are provided 220 if (searchClasses.length > 1) { 221 searchClassesFilter = "(|" + searchClassesFilter + ')'; 222 } 223 } 224 225 public String[] getSearchClasses() { 226 return searchClasses; 227 } 228 229 @XNode("searchFilter") 230 public void setSearchFilter(String searchFilter) { 231 if ((searchFilter == null) || searchFilter.equals("(objectClass=*)")) { 232 this.searchFilter = null; 233 return; 234 } 235 if (!searchFilter.startsWith("(") && !searchFilter.endsWith(")")) { 236 searchFilter = '(' + searchFilter + ')'; 237 } 238 this.searchFilter = searchFilter; 239 } 240 241 public String getSearchFilter() { 242 return searchFilter; 243 } 244 245 @XNode("searchScope") 246 public void setSearchScope(String searchScope) throws DirectoryException { 247 if (null == searchScope) { 248 // restore default search scope 249 this.searchScope = defaultSearchScope; 250 return; 251 } 252 Integer scope = LdapScope.getIntegerScope(searchScope); 253 if (null == scope) { 254 // invalid scope 255 throw new DirectoryException("Invalid search scope: " + searchScope 256 + ". Valid options: object, onelevel, subtree"); 257 } 258 this.searchScope = scope.intValue(); 259 } 260 261 public int getSearchScope() { 262 return searchScope; 263 } 264 265 public String getSubstringMatchType() { 266 return substringMatchType; 267 } 268 269 @XNode("substringMatchType") 270 public void setSubstringMatchType(String substringMatchType) { 271 if (substringMatchType == null) { 272 // default behaviour 273 this.substringMatchType = LDAPSubstringMatchType.SUBINITIAL; 274 } else if (LDAPSubstringMatchType.SUBINITIAL.equals(substringMatchType) 275 || LDAPSubstringMatchType.SUBFINAL.equals(substringMatchType) 276 || LDAPSubstringMatchType.SUBANY.equals(substringMatchType)) { 277 this.substringMatchType = substringMatchType; 278 } else { 279 log.error("Invalid substring match type: " + substringMatchType 280 + ". Valid options: subinitial, subfinal, subany"); 281 this.substringMatchType = LDAPSubstringMatchType.SUBINITIAL; 282 } 283 } 284 285 public String getName() { 286 return name; 287 } 288 289 public String getServerName() { 290 return serverName; 291 } 292 293 public String getAggregatedSearchFilter() { 294 if (searchFilter == null) { 295 return searchClassesFilter; 296 } 297 return "(&" + searchClassesFilter + searchFilter + ')'; 298 } 299 300 public String getPasswordField() { 301 return passwordField; 302 } 303 304 public String getPasswordHashAlgorithmField() { 305 return passwordHashAlgorithm; 306 } 307 308 public Map<String, String> getFieldMapping() { 309 return fieldMapping; 310 } 311 312 public void setFieldMapping(Map<String, String> fieldMapping) { 313 this.fieldMapping = fieldMapping; 314 } 315 316 public Reference[] getInverseReferences() { 317 return inverseReferences; 318 } 319 320 public Reference[] getLdapReferences() { 321 List<Reference> refs = new ArrayList<Reference>(); 322 if (ldapReferences != null) { 323 refs.addAll(Arrays.asList(ldapReferences)); 324 } 325 if (ldapTreeReferences != null) { 326 refs.addAll(Arrays.asList(ldapTreeReferences)); 327 } 328 return refs.toArray(new Reference[] {}); 329 } 330 331 public boolean getReadOnly() { 332 return readOnly; 333 } 334 335 public void setReadOnly(boolean readOnly) { 336 this.readOnly = readOnly; 337 } 338 339 public String getEmptyRefMarker() { 340 return emptyRefMarker; 341 } 342 343 public void setEmptyRefMarker(String emptyRefMarker) { 344 this.emptyRefMarker = emptyRefMarker; 345 } 346 347 public int getQuerySizeLimit() { 348 return querySizeLimit; 349 } 350 351 public void setQuerySizeLimit(int querySizeLimit) { 352 this.querySizeLimit = querySizeLimit; 353 } 354 355 public void setQueryTimeLimit(int queryTimeLimit) { 356 this.queryTimeLimit = queryTimeLimit; 357 } 358 359 public int getQueryTimeLimit() { 360 return queryTimeLimit; 361 } 362 363 public EntryAdaptor getEntryAdaptor() { 364 return entryAdaptor; 365 } 366 367 public LdapExceptionProcessor getExceptionProcessor() { 368 if (exceptionProcessor == null) { 369 if (exceptionProcessorClass == null) { 370 exceptionProcessor = new DefaultLdapExceptionProcessor(); 371 } else { 372 try { 373 exceptionProcessor = exceptionProcessorClass.newInstance(); 374 } catch (ReflectiveOperationException e) { 375 log.error("Unable to instanciate custom Exception handler", e); 376 exceptionProcessor = new DefaultLdapExceptionProcessor(); 377 } 378 } 379 } 380 return exceptionProcessor; 381 } 382 383}