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