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}