/* * (C) Copyright 2006-2016 Nuxeo SA (http://nuxeo.com/) and others. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Contributors: * Nuxeo - initial API and implementation * Florent Guillaume */ package org.nuxeo.ecm.directory.ldap; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.naming.directory.SearchControls; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.nuxeo.common.xmap.annotation.XNode; import org.nuxeo.common.xmap.annotation.XNodeList; import org.nuxeo.common.xmap.annotation.XNodeMap; import org.nuxeo.common.xmap.annotation.XObject; import org.nuxeo.ecm.directory.BaseDirectoryDescriptor; import org.nuxeo.ecm.directory.DirectoryException; import org.nuxeo.ecm.directory.EntryAdaptor; import org.nuxeo.ecm.directory.InverseReference; import org.nuxeo.ecm.directory.Reference; @XObject(value = "directory") public class LDAPDirectoryDescriptor extends BaseDirectoryDescriptor { public static final Log log = LogFactory.getLog(LDAPDirectoryDescriptor.class); public static final int DEFAULT_SEARCH_SCOPE = SearchControls.ONELEVEL_SCOPE; public static final String DEFAULT_SEARCH_CLASSES_FILTER = "(objectClass=*)"; public static final String DEFAULT_EMPTY_REF_MARKER = "cn=emptyRef"; public static final String DEFAULT_MISSING_ID_FIELD_CASE = "unchanged"; public static final String DEFAULT_ID_CASE = "unchanged"; public static final int DEFAULT_QUERY_SIZE_LIMIT = 200; public static final int DEFAULT_QUERY_TIME_LIMIT = 0; // default to wait indefinitely public static final boolean DEFAULT_FOLLOW_REFERRALS = true; @XNode("server") public String serverName; @XNode("searchBaseDn") public String searchBaseDn; @XNodeMap(value = "fieldMapping", key = "@name", type = HashMap.class, componentType = String.class) public Map<String, String> fieldMapping = new HashMap<String, String>(); public String[] searchClasses; public String searchClassesFilter; public String searchFilter; public Integer searchScope; @XNode("creationBaseDn") public String creationBaseDn; @XNodeList(value = "creationClass", componentType = String.class, type = String[].class) public String[] creationClasses; @XNode("rdnAttribute") public String rdnAttribute; @XNodeList(value = "references/ldapReference", type = LDAPReference[].class, componentType = LDAPReference.class) private LDAPReference[] ldapReferences; @XNodeList(value = "references/inverseReference", type = InverseReference[].class, componentType = InverseReference.class) private InverseReference[] inverseReferences; @XNodeList(value = "references/ldapTreeReference", type = LDAPTreeReference[].class, componentType = LDAPTreeReference.class) private LDAPTreeReference[] ldapTreeReferences; @XNode("emptyRefMarker") public String emptyRefMarker; @XNode("missingIdFieldCase") public String missingIdFieldCase; public String getMissingIdFieldCase() { return missingIdFieldCase == null ? DEFAULT_MISSING_ID_FIELD_CASE : missingIdFieldCase; } /** * Since 5.4.2: force id case to upper or lower, or leaver it unchanged. */ @XNode("idCase") public String idCase = DEFAULT_ID_CASE; @XNode("querySizeLimit") private Integer querySizeLimit; @XNode("queryTimeLimit") private Integer queryTimeLimit; // Add attribute to allow to ignore referrals resolution /** * Since 5.9.4 */ @XNode("followReferrals") protected Boolean followReferrals; public boolean getFollowReferrals() { return followReferrals == null ? DEFAULT_FOLLOW_REFERRALS : followReferrals.booleanValue(); } protected EntryAdaptor entryAdaptor; @XObject(value = "entryAdaptor") public static class EntryAdaptorDescriptor { @XNode("@class") public Class<? extends EntryAdaptor> adaptorClass; @XNodeMap(value = "parameter", key = "@name", type = HashMap.class, componentType = String.class) public Map<String, String> parameters; } @XNode("entryAdaptor") public void setEntryAdaptor(EntryAdaptorDescriptor adaptorDescriptor) throws InstantiationException, IllegalAccessException { entryAdaptor = adaptorDescriptor.adaptorClass.newInstance(); for (Map.Entry<String, String> paramEntry : adaptorDescriptor.parameters.entrySet()) { entryAdaptor.setParameter(paramEntry.getKey(), paramEntry.getValue()); } } /** * @since 5.7 : allow to contribute custom Exception Handler to extract LDAP validation error messages */ @XNode("ldapExceptionHandler") protected Class<? extends LdapExceptionProcessor> exceptionProcessorClass; protected LdapExceptionProcessor exceptionProcessor; // XXX: passwordEncryption? // XXX: ignoredFields? // XXX: referenceFields? public LDAPDirectoryDescriptor() { } public String getRdnAttribute() { return rdnAttribute; } public String getCreationBaseDn() { return creationBaseDn; } public String[] getCreationClasses() { return creationClasses; } public String getIdCase() { return idCase; } public String getSearchBaseDn() { return searchBaseDn; } @XNodeList(value = "searchClass", componentType = String.class, type = String[].class) public void setSearchClasses(String[] searchClasses) { this.searchClasses = searchClasses; if (searchClasses == null) { // default searchClassesFilter searchClassesFilter = DEFAULT_SEARCH_CLASSES_FILTER; return; } List<String> searchClassFilters = new ArrayList<String>(); for (String searchClass : searchClasses) { searchClassFilters.add("(objectClass=" + searchClass + ')'); } searchClassesFilter = StringUtils.join(searchClassFilters.toArray()); // logical OR if several classes are provided if (searchClasses.length > 1) { searchClassesFilter = "(|" + searchClassesFilter + ')'; } } public String[] getSearchClasses() { return searchClasses; } @XNode("searchFilter") public void setSearchFilter(String searchFilter) { if ((searchFilter == null) || searchFilter.equals("(objectClass=*)")) { this.searchFilter = null; return; } if (!searchFilter.startsWith("(") && !searchFilter.endsWith(")")) { searchFilter = '(' + searchFilter + ')'; } this.searchFilter = searchFilter; } public String getSearchFilter() { return searchFilter; } @XNode("searchScope") public void setSearchScope(String searchScope) throws DirectoryException { if (searchScope == null) { // restore default search scope this.searchScope = null; return; } Integer scope = LdapScope.getIntegerScope(searchScope); if (scope == null) { // invalid scope throw new DirectoryException( "Invalid search scope: " + searchScope + ". Valid options: object, onelevel, subtree"); } this.searchScope = scope; } public int getSearchScope() { return searchScope == null ? DEFAULT_SEARCH_SCOPE : searchScope.intValue(); } public String getServerName() { return serverName; } public String getAggregatedSearchFilter() { if (searchFilter == null) { return searchClassesFilter; } return "(&" + searchClassesFilter + searchFilter + ')'; } public Map<String, String> getFieldMapping() { return fieldMapping; } public void setFieldMapping(Map<String, String> fieldMapping) { this.fieldMapping = fieldMapping; } public Reference[] getInverseReferences() { return inverseReferences; } public Reference[] getLdapReferences() { List<Reference> refs = new ArrayList<Reference>(); if (ldapReferences != null) { refs.addAll(Arrays.asList(ldapReferences)); } if (ldapTreeReferences != null) { refs.addAll(Arrays.asList(ldapTreeReferences)); } return refs.toArray(new Reference[] {}); } public String getEmptyRefMarker() { return emptyRefMarker == null ? DEFAULT_EMPTY_REF_MARKER : emptyRefMarker; } public void setEmptyRefMarker(String emptyRefMarker) { this.emptyRefMarker = emptyRefMarker; } public int getQuerySizeLimit() { return querySizeLimit == null ? DEFAULT_QUERY_SIZE_LIMIT : querySizeLimit.intValue(); } public void setQuerySizeLimit(int querySizeLimit) { this.querySizeLimit = Integer.valueOf(querySizeLimit); } public void setQueryTimeLimit(int queryTimeLimit) { this.queryTimeLimit = Integer.valueOf(queryTimeLimit); } public int getQueryTimeLimit() { return queryTimeLimit == null ? DEFAULT_QUERY_TIME_LIMIT : queryTimeLimit.intValue(); } public EntryAdaptor getEntryAdaptor() { return entryAdaptor; } public LdapExceptionProcessor getExceptionProcessor() { if (exceptionProcessor == null) { if (exceptionProcessorClass == null) { exceptionProcessor = new DefaultLdapExceptionProcessor(); } else { try { exceptionProcessor = exceptionProcessorClass.newInstance(); } catch (ReflectiveOperationException e) { log.error("Unable to instanciate custom Exception handler", e); exceptionProcessor = new DefaultLdapExceptionProcessor(); } } } return exceptionProcessor; } @Override public void merge(BaseDirectoryDescriptor other) { super.merge(other); if (other instanceof LDAPDirectoryDescriptor) { merge((LDAPDirectoryDescriptor) other); } } protected void merge(LDAPDirectoryDescriptor other) { if (other.serverName != null) { serverName = other.serverName; } if (other.searchBaseDn != null) { searchBaseDn = other.searchBaseDn; } if (other.fieldMapping != null) { fieldMapping.putAll(other.fieldMapping); } if (other.searchClasses != null && other.searchClasses.length > 0) { searchClasses = other.searchClasses.clone(); } if (other.searchClassesFilter != null) { searchClassesFilter = other.searchClassesFilter; } if (other.searchFilter != null) { searchFilter = other.searchFilter; } if (other.searchScope != null) { searchScope = other.searchScope; } if (other.creationBaseDn != null) { creationBaseDn = other.creationBaseDn; } if (other.creationClasses != null && other.creationClasses.length > 0) { creationClasses = other.creationClasses.clone(); } if (other.rdnAttribute != null) { rdnAttribute = other.rdnAttribute; } if (other.ldapReferences != null && other.ldapReferences.length > 0) { ldapReferences = other.ldapReferences; } if (other.inverseReferences != null && other.inverseReferences.length > 0) { inverseReferences = other.inverseReferences; } if (other.ldapTreeReferences != null && other.ldapTreeReferences.length > 0) { ldapTreeReferences = other.ldapTreeReferences; } if (other.emptyRefMarker != null) { emptyRefMarker = other.emptyRefMarker; } if (other.missingIdFieldCase != null) { missingIdFieldCase = other.missingIdFieldCase; } if (other.idCase != null) { idCase = other.idCase; } if (other.querySizeLimit != null) { querySizeLimit = other.querySizeLimit; } if (other.queryTimeLimit != null) { queryTimeLimit = other.queryTimeLimit; } if (other.followReferrals != null) { followReferrals = other.followReferrals; } if (other.entryAdaptor != null) { entryAdaptor = other.entryAdaptor; } if (other.exceptionProcessorClass != null) { exceptionProcessorClass = other.exceptionProcessorClass; exceptionProcessor = other.exceptionProcessor; } } @Override public LDAPDirectoryDescriptor clone() { LDAPDirectoryDescriptor clone = (LDAPDirectoryDescriptor) super.clone(); // basic fields are already copied by super.clone() if (fieldMapping != null) { clone.fieldMapping = new HashMap<>(fieldMapping); } if (searchClasses != null) { clone.searchClasses = searchClasses.clone(); } if (creationClasses != null) { creationClasses = creationClasses.clone(); } if (ldapReferences != null) { clone.ldapReferences = new LDAPReference[ldapReferences.length]; for (int i = 0; i < ldapReferences.length; i++) { clone.ldapReferences[i] = ldapReferences[i].clone(); } } if (inverseReferences != null) { clone.inverseReferences = new InverseReference[inverseReferences.length]; for (int i = 0; i < inverseReferences.length; i++) { clone.inverseReferences[i] = inverseReferences[i].clone(); } } if (ldapTreeReferences != null) { clone.ldapTreeReferences = new LDAPTreeReference[ldapTreeReferences.length]; for (int i = 0; i < ldapTreeReferences.length; i++) { clone.ldapTreeReferences[i] = ldapTreeReferences[i].clone(); } } return clone; } @Override public LDAPDirectory newDirectory() { return new LDAPDirectory(this); } }