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}