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 List<SearchResult> getGroups(
154                            long companyId, LdapContext ldapContext, int maxResults,
155                            String baseDN, String groupFilter)
156                    throws Exception {
157    
158                    return searchLDAP(
159                            companyId, ldapContext, maxResults, baseDN, groupFilter, null);
160            }
161    
162            public static List<SearchResult> getGroups(
163                            long ldapServerId, long companyId, LdapContext ldapContext,
164                            int maxResults)
165                    throws Exception {
166    
167                    String postfix = LDAPSettingsUtil.getPropertyPostfix(ldapServerId);
168    
169                    String baseDN = PrefsPropsUtil.getString(
170                            companyId, PropsKeys.LDAP_BASE_DN + postfix);
171                    String groupFilter = PrefsPropsUtil.getString(
172                            companyId, PropsKeys.LDAP_IMPORT_GROUP_SEARCH_FILTER + postfix);
173    
174                    return getGroups(
175                            companyId, ldapContext, maxResults, baseDN, groupFilter);
176            }
177    
178            public static long getLdapServerId(long companyId, String screenName)
179                    throws Exception {
180    
181                    long[] ldapServerIds = StringUtil.split(
182                            PrefsPropsUtil.getString(companyId, "ldap.server.ids"), 0L);
183    
184                    for (long ldapServerId : ldapServerIds) {
185                            if (hasUser(ldapServerId, companyId, screenName)) {
186                                    return ldapServerId;
187                            }
188                    }
189    
190                    if (ldapServerIds.length > 0) {
191                            return ldapServerIds[0];
192                    }
193    
194                    return 0;
195            }
196    
197            public static Attribute getMultivaluedAttribute(
198                            long companyId, LdapContext ldapContext, String baseDN,
199                            String filter, Attribute attribute)
200                    throws Exception {
201    
202                    if (attribute.size() > 0) {
203                            return attribute;
204                    }
205    
206                    String[] attributeIds = {_getNextRange(attribute.getID())};
207    
208                    while (true) {
209                            List<SearchResult> searchResults = searchLDAP(
210                                    companyId, ldapContext, 0, baseDN, filter, attributeIds);
211    
212                            if (searchResults.size() != 1) {
213                                    break;
214                            }
215    
216                            SearchResult searchResult = searchResults.get(0);
217    
218                            Attributes attributes = searchResult.getAttributes();
219    
220                            if (attributes.size() != 1) {
221                                    break;
222                            }
223    
224                            NamingEnumeration<? extends Attribute> enu = attributes.getAll();
225    
226                            if (!enu.hasMoreElements()) {
227                                    break;
228                            }
229    
230                            Attribute curAttribute = enu.nextElement();
231    
232                            for (int i = 0; i < curAttribute.size(); i++) {
233                                    attribute.add(curAttribute.get(i));
234                            }
235    
236                            if (StringUtil.endsWith(curAttribute.getID(), StringPool.STAR) ||
237                                    (curAttribute.size() < PropsValues.LDAP_RANGE_SIZE)) {
238    
239                                    break;
240                            }
241    
242                            attributeIds[0] = _getNextRange(attributeIds[0]);
243                    }
244    
245                    return attribute;
246            }
247    
248            public static String getNameInNamespace(
249                            long ldapServerId, long companyId, Binding binding)
250                    throws Exception {
251    
252                    String postfix = LDAPSettingsUtil.getPropertyPostfix(ldapServerId);
253    
254                    String baseDN = PrefsPropsUtil.getString(
255                            companyId, PropsKeys.LDAP_BASE_DN + postfix);
256    
257                    String name = binding.getName();
258    
259                    if (name.startsWith(StringPool.QUOTE) &&
260                            name.endsWith(StringPool.QUOTE)) {
261    
262                            name = name.substring(1, name.length() - 1);
263                    }
264    
265                    if (Validator.isNull(baseDN)) {
266                            return name.toString();
267                    }
268                    else {
269                            return name.concat(StringPool.COMMA).concat(baseDN);
270                    }
271            }
272    
273            public static Binding getUser(
274                            long ldapServerId, long companyId, String screenName)
275                    throws Exception {
276    
277                    String postfix = LDAPSettingsUtil.getPropertyPostfix(ldapServerId);
278    
279                    LdapContext ldapContext = getContext(ldapServerId, companyId);
280    
281                    NamingEnumeration<SearchResult> enu = null;
282    
283                    try {
284                            if (ldapContext == null) {
285                                    return null;
286                            }
287    
288                            String baseDN = PrefsPropsUtil.getString(
289                                    companyId, PropsKeys.LDAP_BASE_DN + postfix);
290    
291                            Properties userMappings = LDAPSettingsUtil.getUserMappings(
292                                    ldapServerId, companyId);
293    
294                            StringBundler filter = new StringBundler(5);
295    
296                            filter.append(StringPool.OPEN_PARENTHESIS);
297                            filter.append(userMappings.getProperty("screenName"));
298                            filter.append(StringPool.EQUAL);
299                            filter.append(screenName);
300                            filter.append(StringPool.CLOSE_PARENTHESIS);
301    
302                            SearchControls cons = new SearchControls(
303                                    SearchControls.SUBTREE_SCOPE, 1, 0, null, false, false);
304    
305                            enu = ldapContext.search(baseDN, filter.toString(), cons);
306                    }
307                    catch (Exception e) {
308                            throw e;
309                    }
310                    finally {
311                            if (ldapContext != null) {
312                                    ldapContext.close();
313                            }
314                    }
315    
316                    if (enu.hasMoreElements()) {
317                            Binding binding = enu.nextElement();
318    
319                            enu.close();
320    
321                            return binding;
322                    }
323                    else {
324                            return null;
325                    }
326            }
327    
328            public static Attributes getUserAttributes(
329                            long ldapServerId, long companyId, LdapContext ldapContext,
330                            String fullDistinguishedName)
331                    throws Exception {
332    
333                    Properties userMappings = LDAPSettingsUtil.getUserMappings(
334                            ldapServerId, companyId);
335                    Properties userExpandoMappings =
336                            LDAPSettingsUtil.getUserExpandoMappings(
337                                    ldapServerId, companyId);
338    
339                    PropertiesUtil.merge(userMappings, userExpandoMappings);
340    
341                    Properties contactMappings = LDAPSettingsUtil.getContactMappings(
342                            ldapServerId, companyId);
343                    Properties contactExpandoMappings =
344                            LDAPSettingsUtil.getContactExpandoMappings(ldapServerId, companyId);
345    
346                    PropertiesUtil.merge(contactMappings, contactExpandoMappings);
347    
348                    PropertiesUtil.merge(userMappings, contactMappings);
349    
350                    String[] mappedUserAttributeIds = ArrayUtil.toStringArray(
351                            userMappings.values().toArray(new Object[userMappings.size()]));
352    
353                    return _getAttributes(
354                            ldapContext, fullDistinguishedName, mappedUserAttributeIds);
355            }
356    
357            public static List<SearchResult> getUsers(
358                            long companyId, LdapContext ldapContext, int maxResults,
359                            String baseDN, String userFilter)
360                    throws Exception {
361    
362                    return searchLDAP(
363                            companyId, ldapContext, maxResults, baseDN, userFilter, null);
364            }
365    
366            public static List<SearchResult> getUsers(
367                            long ldapServerId, long companyId, LdapContext ldapContext,
368                            int maxResults)
369                    throws Exception {
370    
371                    String postfix = LDAPSettingsUtil.getPropertyPostfix(ldapServerId);
372    
373                    String baseDN = PrefsPropsUtil.getString(
374                            companyId, PropsKeys.LDAP_BASE_DN + postfix);
375                    String userFilter = PrefsPropsUtil.getString(
376                            companyId, PropsKeys.LDAP_IMPORT_USER_SEARCH_FILTER + postfix);
377    
378                    return getUsers(companyId, ldapContext, maxResults, baseDN, userFilter);
379            }
380    
381            public static String getUsersDN(long ldapServerId, long companyId)
382                    throws Exception {
383    
384                    String postfix = LDAPSettingsUtil.getPropertyPostfix(ldapServerId);
385    
386                    return PrefsPropsUtil.getString(
387                            companyId, PropsKeys.LDAP_USERS_DN + postfix);
388            }
389    
390            public static boolean hasUser(
391                            long ldapServerId, long companyId, String screenName)
392                    throws Exception {
393    
394                    if (getUser(ldapServerId, companyId, screenName) != null) {
395                            return true;
396                    }
397                    else {
398                            return false;
399                    }
400            }
401    
402            public static List<SearchResult> searchLDAP(
403                            long companyId, LdapContext ldapContext, int maxResults,
404                            String baseDN, String filter, String[] attributeIds)
405                    throws Exception {
406    
407                    List<SearchResult> searchResults = new ArrayList<SearchResult>();
408    
409                    SearchControls cons = new SearchControls(
410                            SearchControls.SUBTREE_SCOPE, maxResults, 0, attributeIds, false,
411                            false);
412    
413                    try {
414                            byte[] cookie = new byte[0];
415    
416                            while (cookie != null) {
417                                    if (cookie.length == 0) {
418                                            ldapContext.setRequestControls(
419                                                    new Control[] {
420                                                            new PagedResultsControl(
421                                                                    PropsValues.LDAP_PAGE_SIZE, Control.CRITICAL)
422                                                    });
423                                    }
424                                    else {
425                                            ldapContext.setRequestControls(
426                                                    new Control[] {
427                                                            new PagedResultsControl(
428                                                                    PropsValues.LDAP_PAGE_SIZE, cookie,
429                                                                    Control.CRITICAL)
430                                                    });
431                                    }
432    
433                                    NamingEnumeration<SearchResult> enu = ldapContext.search(
434                                            baseDN, filter, cons);
435    
436                                    while (enu.hasMoreElements()) {
437                                            searchResults.add(enu.nextElement());
438                                    }
439    
440                                    enu.close();
441    
442                                    cookie = _getCookie(ldapContext.getResponseControls());
443                            }
444                    }
445                    catch (OperationNotSupportedException onse) {
446                            ldapContext.setRequestControls(null);
447    
448                            NamingEnumeration<SearchResult> enu = ldapContext.search(
449                                    baseDN, filter, cons);
450    
451                            while (enu.hasMoreElements()) {
452                                    searchResults.add(enu.nextElement());
453                            }
454    
455                            enu.close();
456                    }
457                    finally {
458                            ldapContext.setRequestControls(null);
459                    }
460    
461                    return searchResults;
462            }
463    
464            private static Attributes _getAttributes(
465                            LdapContext ldapContext, String fullDistinguishedName,
466                            String[] attributeIds)
467                    throws Exception {
468    
469                    Name fullDN = new CompositeName().add(fullDistinguishedName);
470    
471                    Attributes attributes = null;
472    
473                    String[] auditAttributeIds = {
474                            "creatorsName", "createTimestamp", "modifiersName",
475                            "modifyTimestamp"
476                    };
477    
478                    if (attributeIds == null) {
479    
480                            // Get complete listing of LDAP attributes (slow)
481    
482                            attributes = ldapContext.getAttributes(fullDN);
483    
484                            NamingEnumeration<? extends Attribute> enu =
485                                    ldapContext.getAttributes(fullDN, auditAttributeIds).getAll();
486    
487                            while (enu.hasMoreElements()) {
488                                    attributes.put(enu.nextElement());
489                            }
490    
491                            enu.close();
492                    }
493                    else {
494    
495                            // Get specified LDAP attributes
496    
497                            int attributeCount = attributeIds.length + auditAttributeIds.length;
498    
499                            String[] allAttributeIds = new String[attributeCount];
500    
501                            System.arraycopy(
502                                    attributeIds, 0, allAttributeIds, 0, attributeIds.length);
503                            System.arraycopy(
504                                    auditAttributeIds, 0, allAttributeIds, attributeIds.length,
505                                    auditAttributeIds.length);
506    
507                            attributes = ldapContext.getAttributes(fullDN, allAttributeIds);
508                    }
509    
510                    return attributes;
511            }
512    
513            private static byte[] _getCookie(Control[] controls) {
514                    if (controls == null) {
515                            return null;
516                    }
517    
518                    for (Control control : controls) {
519                            if (control instanceof PagedResultsResponseControl) {
520                                    PagedResultsResponseControl pagedResultsResponseControl =
521                                            (PagedResultsResponseControl)control;
522    
523                                    return pagedResultsResponseControl.getCookie();
524                            }
525                    }
526    
527                    return null;
528            }
529    
530            private static String _getNextRange(String attributeId) {
531                    String originalAttributeId = null;
532                    int start = 0;
533                    int end = 0;
534    
535                    int x = attributeId.indexOf(StringPool.SEMICOLON);
536    
537                    if (x < 0) {
538                            originalAttributeId = attributeId;
539                            end = PropsValues.LDAP_RANGE_SIZE - 1;
540                    }
541                    else {
542                            int y = attributeId.indexOf(StringPool.EQUAL, x);
543                            int z = attributeId.indexOf(StringPool.DASH, y);
544    
545                            originalAttributeId = attributeId.substring(0, x);
546                            start = GetterUtil.getInteger(attributeId.substring(y + 1, z));
547                            end = GetterUtil.getInteger(attributeId.substring(z + 1));
548    
549                            start += PropsValues.LDAP_RANGE_SIZE;
550                            end += PropsValues.LDAP_RANGE_SIZE;
551                    }
552    
553                    StringBundler sb = new StringBundler(6);
554    
555                    sb.append(originalAttributeId);
556                    sb.append(StringPool.SEMICOLON);
557                    sb.append("range=");
558                    sb.append(start);
559                    sb.append(StringPool.DASH);
560                    sb.append(end);
561    
562                    return sb.toString();
563            }
564    
565            private static Log _log = LogFactoryUtil.getLog(PortalLDAPUtil.class);
566    
567    }