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 contactMappings = LDAPSettingsUtil.getContactMappings(
338             ldapServerId, companyId);
339 
340         PropertiesUtil.merge(userMappings, contactMappings);
341 
342         String[] mappedUserAttributeIds = ArrayUtil.toStringArray(
343             userMappings.values().toArray(new Object[userMappings.size()]));
344 
345         return _getAttributes(
346             ldapContext, fullDistinguishedName, mappedUserAttributeIds);
347     }
348 
349     public static List<SearchResult> getUsers(
350             long companyId, LdapContext ldapContext, int maxResults,
351             String baseDN, String userFilter)
352         throws Exception {
353 
354         return searchLDAP(
355             companyId, ldapContext, maxResults, baseDN, userFilter, null);
356     }
357 
358     public static List<SearchResult> getUsers(
359             long ldapServerId, long companyId, LdapContext ldapContext,
360             int maxResults)
361         throws Exception {
362 
363         String postfix = LDAPSettingsUtil.getPropertyPostfix(ldapServerId);
364 
365         String baseDN = PrefsPropsUtil.getString(
366             companyId, PropsKeys.LDAP_BASE_DN + postfix);
367         String userFilter = PrefsPropsUtil.getString(
368             companyId, PropsKeys.LDAP_IMPORT_USER_SEARCH_FILTER + postfix);
369 
370         return getUsers(companyId, ldapContext, maxResults, baseDN, userFilter);
371     }
372 
373     public static String getUsersDN(long ldapServerId, long companyId)
374         throws Exception {
375 
376         String postfix = LDAPSettingsUtil.getPropertyPostfix(ldapServerId);
377 
378         return PrefsPropsUtil.getString(
379             companyId, PropsKeys.LDAP_USERS_DN + postfix);
380     }
381 
382     public static boolean hasUser(
383             long ldapServerId, long companyId, String screenName)
384         throws Exception {
385 
386         if (getUser(ldapServerId, companyId, screenName) != null) {
387             return true;
388         }
389         else {
390             return false;
391         }
392     }
393 
394     public static List<SearchResult> searchLDAP(
395             long companyId, LdapContext ldapContext, int maxResults,
396             String baseDN, String filter, String[] attributeIds)
397         throws Exception {
398 
399         List<SearchResult> searchResults = new ArrayList<SearchResult>();
400 
401         SearchControls cons = new SearchControls(
402             SearchControls.SUBTREE_SCOPE, maxResults, 0, attributeIds, false,
403             false);
404 
405         try {
406             byte[] cookie = new byte[0];
407 
408             while (cookie != null) {
409                 if (cookie.length == 0) {
410                     ldapContext.setRequestControls(
411                         new Control[] {
412                             new PagedResultsControl(
413                                 PropsValues.LDAP_PAGE_SIZE, Control.CRITICAL)
414                         });
415                 }
416                 else {
417                     ldapContext.setRequestControls(
418                         new Control[] {
419                             new PagedResultsControl(
420                                 PropsValues.LDAP_PAGE_SIZE, cookie,
421                                 Control.CRITICAL)
422                         });
423                 }
424 
425                 NamingEnumeration<SearchResult> enu = ldapContext.search(
426                     baseDN, filter, cons);
427 
428                 while (enu.hasMoreElements()) {
429                     searchResults.add(enu.nextElement());
430                 }
431 
432                 enu.close();
433 
434                 cookie = _getCookie(ldapContext.getResponseControls());
435             }
436         }
437         catch (OperationNotSupportedException onse) {
438             ldapContext.setRequestControls(null);
439 
440             NamingEnumeration<SearchResult> enu = ldapContext.search(
441                 baseDN, filter, cons);
442 
443             while (enu.hasMoreElements()) {
444                 searchResults.add(enu.nextElement());
445             }
446 
447             enu.close();
448         }
449         finally {
450             ldapContext.setRequestControls(null);
451         }
452 
453         return searchResults;
454     }
455 
456     private static Attributes _getAttributes(
457             LdapContext ldapContext, String fullDistinguishedName,
458             String[] attributeIds)
459         throws Exception {
460 
461         Name fullDN = new CompositeName().add(fullDistinguishedName);
462 
463         Attributes attributes = null;
464 
465         String[] auditAttributeIds = {
466             "creatorsName", "createTimestamp", "modifiersName",
467             "modifyTimestamp"
468         };
469 
470         if (attributeIds == null) {
471 
472             // Get complete listing of LDAP attributes (slow)
473 
474             attributes = ldapContext.getAttributes(fullDN);
475 
476             NamingEnumeration<? extends Attribute> enu =
477                 ldapContext.getAttributes(fullDN, auditAttributeIds).getAll();
478 
479             while (enu.hasMoreElements()) {
480                 attributes.put(enu.nextElement());
481             }
482 
483             enu.close();
484         }
485         else {
486 
487             // Get specified LDAP attributes
488 
489             int attributeCount = attributeIds.length + auditAttributeIds.length;
490 
491             String[] allAttributeIds = new String[attributeCount];
492 
493             System.arraycopy(
494                 attributeIds, 0, allAttributeIds, 0, attributeIds.length);
495             System.arraycopy(
496                 auditAttributeIds, 0, allAttributeIds, attributeIds.length,
497                 auditAttributeIds.length);
498 
499             attributes = ldapContext.getAttributes(fullDN, allAttributeIds);
500         }
501 
502         return attributes;
503     }
504 
505     private static byte[] _getCookie(Control[] controls) {
506         if (controls == null) {
507             return null;
508         }
509 
510         for (Control control : controls) {
511             if (control instanceof PagedResultsResponseControl) {
512                 PagedResultsResponseControl pagedResultsResponseControl =
513                     (PagedResultsResponseControl)control;
514 
515                 return pagedResultsResponseControl.getCookie();
516             }
517         }
518 
519         return null;
520     }
521 
522     private static String _getNextRange(String attributeId) {
523         String originalAttributeId = null;
524         int start = 0;
525         int end = 0;
526 
527         int x = attributeId.indexOf(StringPool.SEMICOLON);
528 
529         if (x < 0) {
530             originalAttributeId = attributeId;
531             end = PropsValues.LDAP_RANGE_SIZE - 1;
532         }
533         else {
534             int y = attributeId.indexOf(StringPool.EQUAL, x);
535             int z = attributeId.indexOf(StringPool.DASH, y);
536 
537             originalAttributeId = attributeId.substring(0, x);
538             start = GetterUtil.getInteger(attributeId.substring(y + 1, z));
539             end = GetterUtil.getInteger(attributeId.substring(z + 1));
540 
541             start += PropsValues.LDAP_RANGE_SIZE;
542             end += PropsValues.LDAP_RANGE_SIZE;
543         }
544 
545         StringBundler sb = new StringBundler(6);
546 
547         sb.append(originalAttributeId);
548         sb.append(StringPool.SEMICOLON);
549         sb.append("range=");
550         sb.append(start);
551         sb.append(StringPool.DASH);
552         sb.append(end);
553 
554         return sb.toString();
555     }
556 
557     private static Log _log = LogFactoryUtil.getLog(PortalLDAPUtil.class);
558 
559 }