1   /**
2    * Copyright (c) 2000-2010 Liferay, Inc. All rights reserved.
3    *
4    * This library is free software; you can redistribute it and/or modify it under
5    * the terms of the GNU Lesser General Public License as published by the Free
6    * Software Foundation; either version 2.1 of the License, or (at your option)
7    * any later version.
8    *
9    * This library is distributed in the hope that it will be useful, but WITHOUT
10   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11   * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
12   * details.
13   */
14  
15  package com.liferay.portal.security.ldap;
16  
17  import com.liferay.portal.kernel.log.Log;
18  import com.liferay.portal.kernel.log.LogFactoryUtil;
19  import com.liferay.portal.kernel.log.LogUtil;
20  import com.liferay.portal.kernel.util.ArrayUtil;
21  import com.liferay.portal.kernel.util.GetterUtil;
22  import com.liferay.portal.kernel.util.PropertiesUtil;
23  import com.liferay.portal.kernel.util.PropsKeys;
24  import com.liferay.portal.kernel.util.StringBundler;
25  import com.liferay.portal.kernel.util.StringPool;
26  import com.liferay.portal.kernel.util.StringUtil;
27  import com.liferay.portal.kernel.util.Validator;
28  import com.liferay.portal.util.PrefsPropsUtil;
29  import com.liferay.portal.util.PropsValues;
30  
31  import java.util.ArrayList;
32  import java.util.List;
33  import java.util.Properties;
34  
35  import javax.naming.Binding;
36  import javax.naming.CompositeName;
37  import javax.naming.Context;
38  import javax.naming.Name;
39  import javax.naming.NamingEnumeration;
40  import javax.naming.OperationNotSupportedException;
41  import javax.naming.directory.Attribute;
42  import javax.naming.directory.Attributes;
43  import javax.naming.directory.SearchControls;
44  import javax.naming.directory.SearchResult;
45  import javax.naming.ldap.Control;
46  import javax.naming.ldap.InitialLdapContext;
47  import javax.naming.ldap.LdapContext;
48  import javax.naming.ldap.PagedResultsControl;
49  import javax.naming.ldap.PagedResultsResponseControl;
50  
51  /**
52   * <a href="PortalLDAPUtil.java.html"><b><i>View Source</i></b></a>
53   *
54   * @author Michael Young
55   * @author Brian Wing Shun Chan
56   * @author Jerry Niu
57   * @author Scott Lee
58   * @author Hervé Ménage
59   * @author Samuel Kong
60   * @author Ryan Park
61   * @author Wesley Gong
62   */
63  public class PortalLDAPUtil {
64  
65      public static LdapContext getContext(long ldapServerId, long companyId)
66          throws Exception {
67  
68          String postfix = LDAPSettingsUtil.getPropertyPostfix(ldapServerId);
69  
70          String baseProviderURL = PrefsPropsUtil.getString(
71              companyId, PropsKeys.LDAP_BASE_PROVIDER_URL + postfix);
72          String pricipal = PrefsPropsUtil.getString(
73              companyId, PropsKeys.LDAP_SECURITY_PRINCIPAL + postfix);
74          String credentials = PrefsPropsUtil.getString(
75              companyId, PropsKeys.LDAP_SECURITY_CREDENTIALS + postfix);
76  
77          return getContext(companyId, baseProviderURL, pricipal, credentials);
78      }
79  
80      public static LdapContext getContext(
81              long companyId, String providerURL, String principal,
82              String credentials)
83          throws Exception {
84  
85          Properties env = new Properties();
86  
87          env.put(
88              Context.INITIAL_CONTEXT_FACTORY,
89              PrefsPropsUtil.getString(
90                  companyId, PropsKeys.LDAP_FACTORY_INITIAL));
91          env.put(Context.PROVIDER_URL, providerURL);
92          env.put(Context.SECURITY_PRINCIPAL, principal);
93          env.put(Context.SECURITY_CREDENTIALS, credentials);
94          env.put(
95              Context.REFERRAL,
96              PrefsPropsUtil.getString(companyId, PropsKeys.LDAP_REFERRAL));
97  
98          // Enable pooling
99  
100         env.put("com.sun.jndi.ldap.connect.pool", "true");
101         env.put("com.sun.jndi.ldap.connect.pool.maxsize","50");
102         env.put("com.sun.jndi.ldap.connect.pool.timeout", "10000");
103 
104         LogUtil.debug(_log, env);
105 
106         LdapContext ldapContext = null;
107 
108         try {
109             ldapContext = new InitialLdapContext(env, null);
110         }
111         catch (Exception e) {
112             if (_log.isWarnEnabled()) {
113                 _log.warn("Failed to bind to the LDAP server");
114             }
115 
116             if (_log.isDebugEnabled()) {
117                 _log.debug(e, e);
118             }
119         }
120 
121         return ldapContext;
122     }
123 
124     public static Attributes getGroupAttributes(
125             long ldapServerId, long companyId, LdapContext ldapContext,
126             String fullDistinguishedName)
127         throws Exception {
128 
129         return getGroupAttributes(ldapServerId, companyId, ldapContext,
130             fullDistinguishedName, false);
131     }
132 
133     public static Attributes getGroupAttributes(
134             long ldapServerId, long companyId, LdapContext ldapContext,
135             String fullDistinguishedName, boolean includeReferenceAttributes)
136         throws Exception {
137 
138         Properties groupMappings = LDAPSettingsUtil.getGroupMappings(
139             ldapServerId, companyId);
140 
141         List<String> mappedGroupAttributeIds = new ArrayList<String>();
142 
143         mappedGroupAttributeIds.add(groupMappings.getProperty("groupName"));
144         mappedGroupAttributeIds.add(groupMappings.getProperty("description"));
145 
146         if (includeReferenceAttributes) {
147             mappedGroupAttributeIds.add(groupMappings.getProperty("user"));
148         }
149 
150         return _getAttributes(
151             ldapContext, fullDistinguishedName,
152             mappedGroupAttributeIds.toArray(new String[0]));
153     }
154 
155     public static List<SearchResult> getGroups(
156             long companyId, LdapContext ldapContext, int maxResults,
157             String baseDN, String groupFilter)
158         throws Exception {
159 
160         return searchLDAP(
161             companyId, ldapContext, maxResults, baseDN, groupFilter, null);
162     }
163 
164     public static List<SearchResult> getGroups(
165             long ldapServerId, long companyId, LdapContext ldapContext,
166             int maxResults)
167         throws Exception {
168 
169         String postfix = LDAPSettingsUtil.getPropertyPostfix(ldapServerId);
170 
171         String baseDN = PrefsPropsUtil.getString(
172             companyId, PropsKeys.LDAP_BASE_DN + postfix);
173         String groupFilter = PrefsPropsUtil.getString(
174             companyId, PropsKeys.LDAP_IMPORT_GROUP_SEARCH_FILTER + postfix);
175 
176         return getGroups(
177             companyId, ldapContext, maxResults, baseDN, groupFilter);
178     }
179 
180     public static long getLdapServerId(long companyId, String screenName)
181         throws Exception {
182 
183         long[] ldapServerIds = StringUtil.split(
184             PrefsPropsUtil.getString(companyId, "ldap.server.ids"), 0L);
185 
186         for (long ldapServerId : ldapServerIds) {
187             if (hasUser(ldapServerId, companyId, screenName)) {
188                 return ldapServerId;
189             }
190         }
191 
192         if (ldapServerIds.length > 0) {
193             return ldapServerIds[0];
194         }
195 
196         return 0;
197     }
198 
199     public static Attribute getMultivaluedAttribute(
200             long companyId, LdapContext ldapContext, String baseDN,
201             String filter, Attribute attribute)
202         throws Exception {
203 
204         if (attribute.size() > 0) {
205             return attribute;
206         }
207 
208         String[] attributeIds = {_getNextRange(attribute.getID())};
209 
210         while (true) {
211             List<SearchResult> searchResults = searchLDAP(
212                 companyId, ldapContext, 0, baseDN, filter, attributeIds);
213 
214             if (searchResults.size() != 1) {
215                 break;
216             }
217 
218             SearchResult searchResult = searchResults.get(0);
219 
220             Attributes attributes = searchResult.getAttributes();
221 
222             if (attributes.size() != 1) {
223                 break;
224             }
225 
226             NamingEnumeration<? extends Attribute> enu = attributes.getAll();
227 
228             if (!enu.hasMoreElements()) {
229                 break;
230             }
231 
232             Attribute curAttribute = enu.nextElement();
233 
234             for (int i = 0; i < curAttribute.size(); i++) {
235                 attribute.add(curAttribute.get(i));
236             }
237 
238             if (StringUtil.endsWith(curAttribute.getID(), StringPool.STAR) ||
239                 (curAttribute.size() < PropsValues.LDAP_RANGE_SIZE)) {
240 
241                 break;
242             }
243 
244             attributeIds[0] = _getNextRange(attributeIds[0]);
245         }
246 
247         return attribute;
248     }
249 
250     public static String getNameInNamespace(
251             long ldapServerId, long companyId, Binding binding)
252         throws Exception {
253 
254         String postfix = LDAPSettingsUtil.getPropertyPostfix(ldapServerId);
255 
256         String baseDN = PrefsPropsUtil.getString(
257             companyId, PropsKeys.LDAP_BASE_DN + postfix);
258 
259         String name = binding.getName();
260 
261         if (name.startsWith(StringPool.QUOTE) &&
262             name.endsWith(StringPool.QUOTE)) {
263 
264             name = name.substring(1, name.length() - 1);
265         }
266 
267         if (Validator.isNull(baseDN)) {
268             return name.toString();
269         }
270         else {
271             return name.concat(StringPool.COMMA).concat(baseDN);
272         }
273     }
274 
275     public static Binding getUser(
276             long ldapServerId, long companyId, String screenName)
277         throws Exception {
278 
279         String postfix = LDAPSettingsUtil.getPropertyPostfix(ldapServerId);
280 
281         LdapContext ldapContext = getContext(ldapServerId, companyId);
282 
283         NamingEnumeration<SearchResult> enu = null;
284 
285         try {
286             if (ldapContext == null) {
287                 return null;
288             }
289 
290             String baseDN = PrefsPropsUtil.getString(
291                 companyId, PropsKeys.LDAP_BASE_DN + postfix);
292 
293             Properties userMappings = LDAPSettingsUtil.getUserMappings(
294                 ldapServerId, companyId);
295 
296             StringBundler filter = new StringBundler(5);
297 
298             filter.append(StringPool.OPEN_PARENTHESIS);
299             filter.append(userMappings.getProperty("screenName"));
300             filter.append(StringPool.EQUAL);
301             filter.append(screenName);
302             filter.append(StringPool.CLOSE_PARENTHESIS);
303 
304             SearchControls cons = new SearchControls(
305                 SearchControls.SUBTREE_SCOPE, 1, 0, null, false, false);
306 
307             enu = ldapContext.search(baseDN, filter.toString(), cons);
308         }
309         catch (Exception e) {
310             throw e;
311         }
312         finally {
313             if (ldapContext != null) {
314                 ldapContext.close();
315             }
316         }
317 
318         if (enu.hasMoreElements()) {
319             Binding binding = enu.nextElement();
320 
321             enu.close();
322 
323             return binding;
324         }
325         else {
326             return null;
327         }
328     }
329 
330     public static Attributes getUserAttributes(
331             long ldapServerId, long companyId, LdapContext ldapContext,
332             String fullDistinguishedName)
333         throws Exception {
334 
335         Properties userMappings = LDAPSettingsUtil.getUserMappings(
336             ldapServerId, companyId);
337         Properties userExpandoMappings =
338             LDAPSettingsUtil.getUserExpandoMappings(
339                 ldapServerId, companyId);
340 
341         PropertiesUtil.merge(userMappings, userExpandoMappings);
342 
343         Properties contactMappings = LDAPSettingsUtil.getContactMappings(
344             ldapServerId, companyId);
345         Properties contactExpandoMappings =
346             LDAPSettingsUtil.getContactExpandoMappings(ldapServerId, companyId);
347 
348         PropertiesUtil.merge(contactMappings, contactExpandoMappings);
349 
350         PropertiesUtil.merge(userMappings, contactMappings);
351 
352         String[] mappedUserAttributeIds = ArrayUtil.toStringArray(
353             userMappings.values().toArray(new Object[userMappings.size()]));
354 
355         return _getAttributes(
356             ldapContext, fullDistinguishedName, mappedUserAttributeIds);
357     }
358 
359     public static List<SearchResult> getUsers(
360             long companyId, LdapContext ldapContext, int maxResults,
361             String baseDN, String userFilter)
362         throws Exception {
363 
364         return searchLDAP(
365             companyId, ldapContext, maxResults, baseDN, userFilter, null);
366     }
367 
368     public static List<SearchResult> getUsers(
369             long ldapServerId, long companyId, LdapContext ldapContext,
370             int maxResults)
371         throws Exception {
372 
373         String postfix = LDAPSettingsUtil.getPropertyPostfix(ldapServerId);
374 
375         String baseDN = PrefsPropsUtil.getString(
376             companyId, PropsKeys.LDAP_BASE_DN + postfix);
377         String userFilter = PrefsPropsUtil.getString(
378             companyId, PropsKeys.LDAP_IMPORT_USER_SEARCH_FILTER + postfix);
379 
380         return getUsers(companyId, ldapContext, maxResults, baseDN, userFilter);
381     }
382 
383     public static String getUsersDN(long ldapServerId, long companyId)
384         throws Exception {
385 
386         String postfix = LDAPSettingsUtil.getPropertyPostfix(ldapServerId);
387 
388         return PrefsPropsUtil.getString(
389             companyId, PropsKeys.LDAP_USERS_DN + postfix);
390     }
391 
392     public static boolean hasUser(
393             long ldapServerId, long companyId, String screenName)
394         throws Exception {
395 
396         if (getUser(ldapServerId, companyId, screenName) != null) {
397             return true;
398         }
399         else {
400             return false;
401         }
402     }
403 
404     public static List<SearchResult> searchLDAP(
405             long companyId, LdapContext ldapContext, int maxResults,
406             String baseDN, String filter, String[] attributeIds)
407         throws Exception {
408 
409         List<SearchResult> searchResults = new ArrayList<SearchResult>();
410 
411         SearchControls cons = new SearchControls(
412             SearchControls.SUBTREE_SCOPE, maxResults, 0, attributeIds, false,
413             false);
414 
415         try {
416             byte[] cookie = new byte[0];
417 
418             while (cookie != null) {
419                 if (cookie.length == 0) {
420                     ldapContext.setRequestControls(
421                         new Control[] {
422                             new PagedResultsControl(
423                                 PropsValues.LDAP_PAGE_SIZE, Control.CRITICAL)
424                         });
425                 }
426                 else {
427                     ldapContext.setRequestControls(
428                         new Control[] {
429                             new PagedResultsControl(
430                                 PropsValues.LDAP_PAGE_SIZE, cookie,
431                                 Control.CRITICAL)
432                         });
433                 }
434 
435                 NamingEnumeration<SearchResult> enu = ldapContext.search(
436                     baseDN, filter, cons);
437 
438                 while (enu.hasMoreElements()) {
439                     searchResults.add(enu.nextElement());
440                 }
441 
442                 enu.close();
443 
444                 cookie = _getCookie(ldapContext.getResponseControls());
445             }
446         }
447         catch (OperationNotSupportedException onse) {
448             ldapContext.setRequestControls(null);
449 
450             NamingEnumeration<SearchResult> enu = ldapContext.search(
451                 baseDN, filter, cons);
452 
453             while (enu.hasMoreElements()) {
454                 searchResults.add(enu.nextElement());
455             }
456 
457             enu.close();
458         }
459         finally {
460             ldapContext.setRequestControls(null);
461         }
462 
463         return searchResults;
464     }
465 
466     private static Attributes _getAttributes(
467             LdapContext ldapContext, String fullDistinguishedName,
468             String[] attributeIds)
469         throws Exception {
470 
471         Name fullDN = new CompositeName().add(fullDistinguishedName);
472 
473         Attributes attributes = null;
474 
475         String[] auditAttributeIds = {
476             "creatorsName", "createTimestamp", "modifiersName",
477             "modifyTimestamp"
478         };
479 
480         if (attributeIds == null) {
481 
482             // Get complete listing of LDAP attributes (slow)
483 
484             attributes = ldapContext.getAttributes(fullDN);
485 
486             NamingEnumeration<? extends Attribute> enu =
487                 ldapContext.getAttributes(fullDN, auditAttributeIds).getAll();
488 
489             while (enu.hasMoreElements()) {
490                 attributes.put(enu.nextElement());
491             }
492 
493             enu.close();
494         }
495         else {
496 
497             // Get specified LDAP attributes
498 
499             int attributeCount = attributeIds.length + auditAttributeIds.length;
500 
501             String[] allAttributeIds = new String[attributeCount];
502 
503             System.arraycopy(
504                 attributeIds, 0, allAttributeIds, 0, attributeIds.length);
505             System.arraycopy(
506                 auditAttributeIds, 0, allAttributeIds, attributeIds.length,
507                 auditAttributeIds.length);
508 
509             attributes = ldapContext.getAttributes(fullDN, allAttributeIds);
510         }
511 
512         return attributes;
513     }
514 
515     private static byte[] _getCookie(Control[] controls) {
516         if (controls == null) {
517             return null;
518         }
519 
520         for (Control control : controls) {
521             if (control instanceof PagedResultsResponseControl) {
522                 PagedResultsResponseControl pagedResultsResponseControl =
523                     (PagedResultsResponseControl)control;
524 
525                 return pagedResultsResponseControl.getCookie();
526             }
527         }
528 
529         return null;
530     }
531 
532     private static String _getNextRange(String attributeId) {
533         String originalAttributeId = null;
534         int start = 0;
535         int end = 0;
536 
537         int x = attributeId.indexOf(StringPool.SEMICOLON);
538 
539         if (x < 0) {
540             originalAttributeId = attributeId;
541             end = PropsValues.LDAP_RANGE_SIZE - 1;
542         }
543         else {
544             int y = attributeId.indexOf(StringPool.EQUAL, x);
545             int z = attributeId.indexOf(StringPool.DASH, y);
546 
547             originalAttributeId = attributeId.substring(0, x);
548             start = GetterUtil.getInteger(attributeId.substring(y + 1, z));
549             end = GetterUtil.getInteger(attributeId.substring(z + 1));
550 
551             start += PropsValues.LDAP_RANGE_SIZE;
552             end += PropsValues.LDAP_RANGE_SIZE;
553         }
554 
555         StringBundler sb = new StringBundler(6);
556 
557         sb.append(originalAttributeId);
558         sb.append(StringPool.SEMICOLON);
559         sb.append("range=");
560         sb.append(start);
561         sb.append(StringPool.DASH);
562         sb.append(end);
563 
564         return sb.toString();
565     }
566 
567     private static Log _log = LogFactoryUtil.getLog(PortalLDAPUtil.class);
568 
569 }