001    /**
002     * Copyright (c) 2000-2010 Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
014    
015    package com.liferay.portal.security.ldap;
016    
017    import com.liferay.portal.kernel.log.Log;
018    import com.liferay.portal.kernel.log.LogFactoryUtil;
019    import com.liferay.portal.kernel.log.LogUtil;
020    import com.liferay.portal.kernel.util.ArrayUtil;
021    import com.liferay.portal.kernel.util.GetterUtil;
022    import com.liferay.portal.kernel.util.PropertiesUtil;
023    import com.liferay.portal.kernel.util.PropsKeys;
024    import com.liferay.portal.kernel.util.StringBundler;
025    import com.liferay.portal.kernel.util.StringPool;
026    import com.liferay.portal.kernel.util.StringUtil;
027    import com.liferay.portal.kernel.util.Validator;
028    import com.liferay.portal.util.PrefsPropsUtil;
029    import com.liferay.portal.util.PropsValues;
030    
031    import java.util.ArrayList;
032    import java.util.List;
033    import java.util.Properties;
034    
035    import javax.naming.Binding;
036    import javax.naming.CompositeName;
037    import javax.naming.Context;
038    import javax.naming.Name;
039    import javax.naming.NamingEnumeration;
040    import javax.naming.OperationNotSupportedException;
041    import javax.naming.directory.Attribute;
042    import javax.naming.directory.Attributes;
043    import javax.naming.directory.SearchControls;
044    import javax.naming.directory.SearchResult;
045    import javax.naming.ldap.Control;
046    import javax.naming.ldap.InitialLdapContext;
047    import javax.naming.ldap.LdapContext;
048    import javax.naming.ldap.PagedResultsControl;
049    import javax.naming.ldap.PagedResultsResponseControl;
050    
051    /**
052     * @author Michael Young
053     * @author Brian Wing Shun Chan
054     * @author Jerry Niu
055     * @author Scott Lee
056     * @author Hervé Ménage
057     * @author Samuel Kong
058     * @author Ryan Park
059     * @author Wesley Gong
060     */
061    public class PortalLDAPUtil {
062    
063            public static LdapContext getContext(long ldapServerId, long companyId)
064                    throws Exception {
065    
066                    String postfix = LDAPSettingsUtil.getPropertyPostfix(ldapServerId);
067    
068                    String baseProviderURL = PrefsPropsUtil.getString(
069                            companyId, PropsKeys.LDAP_BASE_PROVIDER_URL + postfix);
070                    String pricipal = PrefsPropsUtil.getString(
071                            companyId, PropsKeys.LDAP_SECURITY_PRINCIPAL + postfix);
072                    String credentials = PrefsPropsUtil.getString(
073                            companyId, PropsKeys.LDAP_SECURITY_CREDENTIALS + postfix);
074    
075                    return getContext(companyId, baseProviderURL, pricipal, credentials);
076            }
077    
078            public static LdapContext getContext(
079                            long companyId, String providerURL, String principal,
080                            String credentials)
081                    throws Exception {
082    
083                    Properties env = new Properties();
084    
085                    env.put(
086                            Context.INITIAL_CONTEXT_FACTORY,
087                            PrefsPropsUtil.getString(
088                                    companyId, PropsKeys.LDAP_FACTORY_INITIAL));
089                    env.put(Context.PROVIDER_URL, providerURL);
090                    env.put(Context.SECURITY_PRINCIPAL, principal);
091                    env.put(Context.SECURITY_CREDENTIALS, credentials);
092                    env.put(
093                            Context.REFERRAL,
094                            PrefsPropsUtil.getString(companyId, PropsKeys.LDAP_REFERRAL));
095    
096                    // Enable pooling
097    
098                    env.put("com.sun.jndi.ldap.connect.pool", "true");
099                    env.put("com.sun.jndi.ldap.connect.pool.maxsize","50");
100                    env.put("com.sun.jndi.ldap.connect.pool.timeout", "10000");
101    
102                    LogUtil.debug(_log, env);
103    
104                    LdapContext ldapContext = null;
105    
106                    try {
107                            ldapContext = new InitialLdapContext(env, null);
108                    }
109                    catch (Exception e) {
110                            if (_log.isWarnEnabled()) {
111                                    _log.warn("Failed to bind to the LDAP server");
112                            }
113    
114                            if (_log.isDebugEnabled()) {
115                                    _log.debug(e, e);
116                            }
117                    }
118    
119                    return ldapContext;
120            }
121    
122            public static Attributes getGroupAttributes(
123                            long ldapServerId, long companyId, LdapContext ldapContext,
124                            String fullDistinguishedName)
125                    throws Exception {
126    
127                    return getGroupAttributes(ldapServerId, companyId, ldapContext,
128                            fullDistinguishedName, false);
129            }
130    
131            public static Attributes getGroupAttributes(
132                            long ldapServerId, long companyId, LdapContext ldapContext,
133                            String fullDistinguishedName, boolean includeReferenceAttributes)
134                    throws Exception {
135    
136                    Properties groupMappings = LDAPSettingsUtil.getGroupMappings(
137                            ldapServerId, companyId);
138    
139                    List<String> mappedGroupAttributeIds = new ArrayList<String>();
140    
141                    mappedGroupAttributeIds.add(groupMappings.getProperty("groupName"));
142                    mappedGroupAttributeIds.add(groupMappings.getProperty("description"));
143    
144                    if (includeReferenceAttributes) {
145                            mappedGroupAttributeIds.add(groupMappings.getProperty("user"));
146                    }
147    
148                    return _getAttributes(
149                            ldapContext, fullDistinguishedName,
150                            mappedGroupAttributeIds.toArray(new String[0]));
151            }
152    
153            public static byte[] getGroups(
154                            long companyId, LdapContext ldapContext, byte[] cookie,
155                            int maxResults, String baseDN, String groupFilter,
156                            List<SearchResult> searchResults)
157                    throws Exception {
158    
159                    return searchLDAP(
160                            companyId, ldapContext, cookie, maxResults, baseDN, groupFilter,
161                            null, searchResults);
162            }
163    
164            public static byte[] getGroups(
165                            long ldapServerId, long companyId, LdapContext ldapContext,
166                            byte[] cookie, int maxResults, List<SearchResult> searchResults)
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, cookie, maxResults, baseDN, groupFilter,
178                            searchResults);
179            }
180    
181            public static long getLdapServerId(long companyId, String screenName)
182                    throws Exception {
183    
184                    long[] ldapServerIds = StringUtil.split(
185                            PrefsPropsUtil.getString(companyId, "ldap.server.ids"), 0L);
186    
187                    for (long ldapServerId : ldapServerIds) {
188                            if (hasUser(ldapServerId, companyId, screenName)) {
189                                    return ldapServerId;
190                            }
191                    }
192    
193                    if (ldapServerIds.length > 0) {
194                            return ldapServerIds[0];
195                    }
196    
197                    return 0;
198            }
199    
200            public static Attribute getMultivaluedAttribute(
201                            long companyId, LdapContext ldapContext, String baseDN,
202                            String filter, Attribute attribute)
203                    throws Exception {
204    
205                    if (attribute.size() > 0) {
206                            return attribute;
207                    }
208    
209                    String[] attributeIds = {_getNextRange(attribute.getID())};
210    
211                    while (true) {
212                            List<SearchResult> searchResults = new ArrayList<SearchResult>();
213    
214                            searchLDAP(
215                                    companyId, ldapContext, new byte[0], 0, baseDN, filter,
216                                    attributeIds, searchResults);
217    
218                            if (searchResults.size() != 1) {
219                                    break;
220                            }
221    
222                            SearchResult searchResult = searchResults.get(0);
223    
224                            Attributes attributes = searchResult.getAttributes();
225    
226                            if (attributes.size() != 1) {
227                                    break;
228                            }
229    
230                            NamingEnumeration<? extends Attribute> enu = attributes.getAll();
231    
232                            if (!enu.hasMoreElements()) {
233                                    break;
234                            }
235    
236                            Attribute curAttribute = enu.nextElement();
237    
238                            for (int i = 0; i < curAttribute.size(); i++) {
239                                    attribute.add(curAttribute.get(i));
240                            }
241    
242                            if (StringUtil.endsWith(curAttribute.getID(), StringPool.STAR) ||
243                                    (curAttribute.size() < PropsValues.LDAP_RANGE_SIZE)) {
244    
245                                    break;
246                            }
247    
248                            attributeIds[0] = _getNextRange(attributeIds[0]);
249                    }
250    
251                    return attribute;
252            }
253    
254            public static String getNameInNamespace(
255                            long ldapServerId, long companyId, Binding binding)
256                    throws Exception {
257    
258                    String postfix = LDAPSettingsUtil.getPropertyPostfix(ldapServerId);
259    
260                    String baseDN = PrefsPropsUtil.getString(
261                            companyId, PropsKeys.LDAP_BASE_DN + postfix);
262    
263                    String name = binding.getName();
264    
265                    if (name.startsWith(StringPool.QUOTE) &&
266                            name.endsWith(StringPool.QUOTE)) {
267    
268                            name = name.substring(1, name.length() - 1);
269                    }
270    
271                    if (Validator.isNull(baseDN)) {
272                            return name.toString();
273                    }
274                    else {
275                            return name.concat(StringPool.COMMA).concat(baseDN);
276                    }
277            }
278    
279            public static Binding getUser(
280                            long ldapServerId, long companyId, String screenName)
281                    throws Exception {
282    
283                    String postfix = LDAPSettingsUtil.getPropertyPostfix(ldapServerId);
284    
285                    LdapContext ldapContext = getContext(ldapServerId, companyId);
286    
287                    NamingEnumeration<SearchResult> enu = null;
288    
289                    try {
290                            if (ldapContext == null) {
291                                    return null;
292                            }
293    
294                            String baseDN = PrefsPropsUtil.getString(
295                                    companyId, PropsKeys.LDAP_BASE_DN + postfix);
296    
297                            Properties userMappings = LDAPSettingsUtil.getUserMappings(
298                                    ldapServerId, companyId);
299    
300                            StringBundler filter = new StringBundler(5);
301    
302                            filter.append(StringPool.OPEN_PARENTHESIS);
303                            filter.append(userMappings.getProperty("screenName"));
304                            filter.append(StringPool.EQUAL);
305                            filter.append(screenName);
306                            filter.append(StringPool.CLOSE_PARENTHESIS);
307    
308                            SearchControls searchControls = new SearchControls(
309                                    SearchControls.SUBTREE_SCOPE, 1, 0, null, false, false);
310    
311                            enu = ldapContext.search(baseDN, filter.toString(), searchControls);
312                    }
313                    catch (Exception e) {
314                            throw e;
315                    }
316                    finally {
317                            if (ldapContext != null) {
318                                    ldapContext.close();
319                            }
320                    }
321    
322                    if (enu.hasMoreElements()) {
323                            Binding binding = enu.nextElement();
324    
325                            enu.close();
326    
327                            return binding;
328                    }
329                    else {
330                            return null;
331                    }
332            }
333    
334            public static Attributes getUserAttributes(
335                            long ldapServerId, long companyId, LdapContext ldapContext,
336                            String fullDistinguishedName)
337                    throws Exception {
338    
339                    Properties userMappings = LDAPSettingsUtil.getUserMappings(
340                            ldapServerId, companyId);
341                    Properties userExpandoMappings =
342                            LDAPSettingsUtil.getUserExpandoMappings(
343                                    ldapServerId, companyId);
344    
345                    PropertiesUtil.merge(userMappings, userExpandoMappings);
346    
347                    Properties contactMappings = LDAPSettingsUtil.getContactMappings(
348                            ldapServerId, companyId);
349                    Properties contactExpandoMappings =
350                            LDAPSettingsUtil.getContactExpandoMappings(ldapServerId, companyId);
351    
352                    PropertiesUtil.merge(contactMappings, contactExpandoMappings);
353    
354                    PropertiesUtil.merge(userMappings, contactMappings);
355    
356                    String[] mappedUserAttributeIds = ArrayUtil.toStringArray(
357                            userMappings.values().toArray(new Object[userMappings.size()]));
358    
359                    return _getAttributes(
360                            ldapContext, fullDistinguishedName, mappedUserAttributeIds);
361            }
362    
363            public static byte[] getUsers(
364                            long companyId, LdapContext ldapContext, byte[] cookie,
365                            int maxResults, String baseDN, String userFilter,
366                            List<SearchResult> searchResults)
367                    throws Exception {
368    
369                    return searchLDAP(
370                            companyId, ldapContext, cookie, maxResults, baseDN, userFilter,
371                            null, searchResults);
372            }
373    
374            public static byte[] getUsers(
375                            long ldapServerId, long companyId, LdapContext ldapContext,
376                            byte[] cookie, int maxResults, List<SearchResult> searchResults)
377                    throws Exception {
378    
379                    String postfix = LDAPSettingsUtil.getPropertyPostfix(ldapServerId);
380    
381                    String baseDN = PrefsPropsUtil.getString(
382                            companyId, PropsKeys.LDAP_BASE_DN + postfix);
383                    String userFilter = PrefsPropsUtil.getString(
384                            companyId, PropsKeys.LDAP_IMPORT_USER_SEARCH_FILTER + postfix);
385    
386                    return getUsers(
387                            companyId, ldapContext, cookie, maxResults, baseDN, userFilter,
388                            searchResults);
389            }
390    
391            public static String getUsersDN(long ldapServerId, long companyId)
392                    throws Exception {
393    
394                    String postfix = LDAPSettingsUtil.getPropertyPostfix(ldapServerId);
395    
396                    return PrefsPropsUtil.getString(
397                            companyId, PropsKeys.LDAP_USERS_DN + postfix);
398            }
399    
400            public static boolean hasUser(
401                            long ldapServerId, long companyId, String screenName)
402                    throws Exception {
403    
404                    if (getUser(ldapServerId, companyId, screenName) != null) {
405                            return true;
406                    }
407                    else {
408                            return false;
409                    }
410            }
411    
412            public static byte[] searchLDAP(
413                            long companyId, LdapContext ldapContext, byte[] cookie,
414                            int maxResults, String baseDN, String filter,
415                            String[] attributeIds, List<SearchResult> searchResults)
416                    throws Exception {
417    
418                    SearchControls searchControls = new SearchControls(
419                            SearchControls.SUBTREE_SCOPE, maxResults, 0, attributeIds, false,
420                            false);
421    
422                    try {
423                            if (cookie != null) {
424                                    if (cookie.length == 0) {
425                                            ldapContext.setRequestControls(
426                                                    new Control[] {
427                                                            new PagedResultsControl(
428                                                                    PropsValues.LDAP_PAGE_SIZE, Control.CRITICAL)
429                                                    });
430                                    }
431                                    else {
432                                            ldapContext.setRequestControls(
433                                                    new Control[] {
434                                                            new PagedResultsControl(
435                                                                    PropsValues.LDAP_PAGE_SIZE, cookie,
436                                                                    Control.CRITICAL)
437                                                    });
438                                    }
439    
440                                    NamingEnumeration<SearchResult> enu = ldapContext.search(
441                                            baseDN, filter, searchControls);
442    
443                                    while (enu.hasMoreElements()) {
444                                            searchResults.add(enu.nextElement());
445                                    }
446    
447                                    enu.close();
448    
449                                    return _getCookie(ldapContext.getResponseControls());
450                            }
451                    }
452                    catch (OperationNotSupportedException onse) {
453                            ldapContext.setRequestControls(null);
454    
455                            NamingEnumeration<SearchResult> enu = ldapContext.search(
456                                    baseDN, filter, searchControls);
457    
458                            while (enu.hasMoreElements()) {
459                                    searchResults.add(enu.nextElement());
460                            }
461    
462                            enu.close();
463                    }
464                    finally {
465                            ldapContext.setRequestControls(null);
466                    }
467    
468                    return null;
469            }
470    
471            private static Attributes _getAttributes(
472                            LdapContext ldapContext, String fullDistinguishedName,
473                            String[] attributeIds)
474                    throws Exception {
475    
476                    Name fullDN = new CompositeName().add(fullDistinguishedName);
477    
478                    Attributes attributes = null;
479    
480                    String[] auditAttributeIds = {
481                            "creatorsName", "createTimestamp", "modifiersName",
482                            "modifyTimestamp"
483                    };
484    
485                    if (attributeIds == null) {
486    
487                            // Get complete listing of LDAP attributes (slow)
488    
489                            attributes = ldapContext.getAttributes(fullDN);
490    
491                            NamingEnumeration<? extends Attribute> enu =
492                                    ldapContext.getAttributes(fullDN, auditAttributeIds).getAll();
493    
494                            while (enu.hasMoreElements()) {
495                                    attributes.put(enu.nextElement());
496                            }
497    
498                            enu.close();
499                    }
500                    else {
501    
502                            // Get specified LDAP attributes
503    
504                            int attributeCount = attributeIds.length + auditAttributeIds.length;
505    
506                            String[] allAttributeIds = new String[attributeCount];
507    
508                            System.arraycopy(
509                                    attributeIds, 0, allAttributeIds, 0, attributeIds.length);
510                            System.arraycopy(
511                                    auditAttributeIds, 0, allAttributeIds, attributeIds.length,
512                                    auditAttributeIds.length);
513    
514                            attributes = ldapContext.getAttributes(fullDN, allAttributeIds);
515                    }
516    
517                    return attributes;
518            }
519    
520            private static byte[] _getCookie(Control[] controls) {
521                    if (controls == null) {
522                            return null;
523                    }
524    
525                    for (Control control : controls) {
526                            if (control instanceof PagedResultsResponseControl) {
527                                    PagedResultsResponseControl pagedResultsResponseControl =
528                                            (PagedResultsResponseControl)control;
529    
530                                    return pagedResultsResponseControl.getCookie();
531                            }
532                    }
533    
534                    return null;
535            }
536    
537            private static String _getNextRange(String attributeId) {
538                    String originalAttributeId = null;
539                    int start = 0;
540                    int end = 0;
541    
542                    int x = attributeId.indexOf(StringPool.SEMICOLON);
543    
544                    if (x < 0) {
545                            originalAttributeId = attributeId;
546                            end = PropsValues.LDAP_RANGE_SIZE - 1;
547                    }
548                    else {
549                            int y = attributeId.indexOf(StringPool.EQUAL, x);
550                            int z = attributeId.indexOf(StringPool.DASH, y);
551    
552                            originalAttributeId = attributeId.substring(0, x);
553                            start = GetterUtil.getInteger(attributeId.substring(y + 1, z));
554                            end = GetterUtil.getInteger(attributeId.substring(z + 1));
555    
556                            start += PropsValues.LDAP_RANGE_SIZE;
557                            end += PropsValues.LDAP_RANGE_SIZE;
558                    }
559    
560                    StringBundler sb = new StringBundler(6);
561    
562                    sb.append(originalAttributeId);
563                    sb.append(StringPool.SEMICOLON);
564                    sb.append("range=");
565                    sb.append(start);
566                    sb.append(StringPool.DASH);
567                    sb.append(end);
568    
569                    return sb.toString();
570            }
571    
572            private static Log _log = LogFactoryUtil.getLog(PortalLDAPUtil.class);
573    
574    }