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}