1   /**
2    * Copyright (c) 2000-2010 Liferay, Inc. All rights reserved.
3    *
4    * The contents of this file are subject to the terms of the Liferay Enterprise
5    * Subscription License ("License"). You may not use this file except in
6    * compliance with the License. You can obtain a copy of the License by
7    * contacting Liferay, Inc. See the License for the specific language governing
8    * permissions and limitations under the License, including but not limited to
9    * distribution rights of the Software.
10   *
11   *
12   * 
13   */
14  
15  package com.liferay.portal.security.ldap;
16  
17  import com.liferay.portal.NoSuchUserException;
18  import com.liferay.portal.NoSuchUserGroupException;
19  import com.liferay.portal.SystemException;
20  import com.liferay.portal.UserScreenNameException;
21  import com.liferay.portal.kernel.log.Log;
22  import com.liferay.portal.kernel.log.LogFactoryUtil;
23  import com.liferay.portal.kernel.log.LogUtil;
24  import com.liferay.portal.kernel.util.ArrayUtil;
25  import com.liferay.portal.kernel.util.CalendarFactoryUtil;
26  import com.liferay.portal.kernel.util.DateFormatFactoryUtil;
27  import com.liferay.portal.kernel.util.GetterUtil;
28  import com.liferay.portal.kernel.util.PropertiesUtil;
29  import com.liferay.portal.kernel.util.PropsKeys;
30  import com.liferay.portal.kernel.util.StringPool;
31  import com.liferay.portal.kernel.util.StringUtil;
32  import com.liferay.portal.kernel.util.Validator;
33  import com.liferay.portal.model.Company;
34  import com.liferay.portal.model.CompanyConstants;
35  import com.liferay.portal.model.Contact;
36  import com.liferay.portal.model.ContactConstants;
37  import com.liferay.portal.model.User;
38  import com.liferay.portal.model.UserGroup;
39  import com.liferay.portal.security.auth.ScreenNameGenerator;
40  import com.liferay.portal.security.auth.ScreenNameGeneratorFactory;
41  import com.liferay.portal.service.CompanyLocalServiceUtil;
42  import com.liferay.portal.service.UserGroupLocalServiceUtil;
43  import com.liferay.portal.service.UserLocalServiceUtil;
44  import com.liferay.portal.util.PrefsPropsUtil;
45  import com.liferay.portal.util.PropsValues;
46  import com.liferay.util.ldap.LDAPUtil;
47  import com.liferay.util.ldap.Modifications;
48  
49  import java.text.DateFormat;
50  import java.text.ParseException;
51  
52  import java.util.ArrayList;
53  import java.util.Calendar;
54  import java.util.Date;
55  import java.util.List;
56  import java.util.Locale;
57  import java.util.Properties;
58  
59  import javax.naming.Binding;
60  import javax.naming.CompositeName;
61  import javax.naming.Context;
62  import javax.naming.Name;
63  import javax.naming.NameNotFoundException;
64  import javax.naming.NamingEnumeration;
65  import javax.naming.OperationNotSupportedException;
66  import javax.naming.directory.Attribute;
67  import javax.naming.directory.Attributes;
68  import javax.naming.directory.ModificationItem;
69  import javax.naming.directory.SearchControls;
70  import javax.naming.directory.SearchResult;
71  import javax.naming.ldap.Control;
72  import javax.naming.ldap.InitialLdapContext;
73  import javax.naming.ldap.LdapContext;
74  import javax.naming.ldap.PagedResultsControl;
75  import javax.naming.ldap.PagedResultsResponseControl;
76  
77  /**
78   * <a href="PortalLDAPUtil.java.html"><b><i>View Source</i></b></a>
79   *
80   * @author Michael Young
81   * @author Brian Wing Shun Chan
82   * @author Jerry Niu
83   * @author Scott Lee
84   * @author Hervé Ménage
85   * @author Samuel Kong
86   * @author Wesley Gong
87   */
88  public class PortalLDAPUtil {
89  
90      public static final String IMPORT_BY_GROUP = "group";
91  
92      public static final String IMPORT_BY_USER = "user";
93  
94      public static void exportToLDAP(Contact contact) throws Exception {
95          long companyId = contact.getCompanyId();
96  
97          if (!isAuthEnabled(companyId) || !isExportEnabled(companyId)) {
98              return;
99          }
100 
101         LdapContext ctx = getContext(companyId);
102 
103         try {
104             if (ctx == null) {
105                 return;
106             }
107 
108             User user = UserLocalServiceUtil.getUserByContactId(
109                 contact.getContactId());
110 
111             Properties userMappings = getUserMappings(companyId);
112             Binding binding = getUser(
113                 contact.getCompanyId(), user.getScreenName());
114             Name name = new CompositeName();
115 
116             if (binding == null) {
117 
118                 // Create new user in LDAP
119 
120                 _getDNName(companyId, user, userMappings, name);
121 
122                 LDAPUser ldapUser = (LDAPUser)Class.forName(
123                     PropsValues.LDAP_USER_IMPL).newInstance();
124 
125                 ldapUser.setUser(user);
126 
127                 ctx.bind(name, ldapUser);
128             }
129             else {
130 
131                 // Modify existing LDAP user record
132 
133                 name.add(getNameInNamespace(companyId, binding));
134 
135                 Modifications mods = Modifications.getInstance();
136 
137                 mods.addItem(
138                     userMappings.getProperty("firstName"),
139                     contact.getFirstName());
140 
141                 String middleNameMapping = userMappings.getProperty(
142                     "middleName");
143 
144                 if (Validator.isNotNull(middleNameMapping)) {
145                     mods.addItem(middleNameMapping, contact.getMiddleName());
146                 }
147 
148                 mods.addItem(
149                     userMappings.getProperty("lastName"),
150                     contact.getLastName());
151 
152                 String fullNameMapping = userMappings.getProperty("fullName");
153 
154                 if (Validator.isNotNull(fullNameMapping)) {
155                     mods.addItem(fullNameMapping, contact.getFullName());
156                 }
157 
158                 String jobTitleMapping = userMappings.getProperty("jobTitle");
159 
160                 if (Validator.isNotNull(jobTitleMapping)) {
161                     mods.addItem(jobTitleMapping, contact.getJobTitle());
162                 }
163 
164                 ModificationItem[] modItems = mods.getItems();
165 
166                 ctx.modifyAttributes(name, modItems);
167             }
168         }
169         catch (Exception e) {
170             throw e;
171         }
172         finally {
173             if (ctx != null) {
174                 ctx.close();
175             }
176         }
177     }
178 
179     public static void exportToLDAP(User user) throws Exception {
180         long companyId = user.getCompanyId();
181 
182         if (!isAuthEnabled(companyId) || !isExportEnabled(companyId)) {
183             return;
184         }
185 
186         LdapContext ctx = getContext(companyId);
187 
188         try {
189             if (ctx == null) {
190                 return;
191             }
192 
193             Properties userMappings = getUserMappings(companyId);
194             Binding binding = getUser(
195                 user.getCompanyId(), user.getScreenName());
196             Name name = new CompositeName();
197 
198             if (binding == null) {
199 
200                 // Create new user in LDAP
201 
202                 _getDNName(companyId, user, userMappings, name);
203 
204                 LDAPUser ldapUser = (LDAPUser)Class.forName(
205                     PropsValues.LDAP_USER_IMPL).newInstance();
206 
207                 ldapUser.setUser(user);
208 
209                 ctx.bind(name, ldapUser);
210 
211                 binding = getUser(user.getCompanyId(), user.getScreenName());
212 
213                 name = new CompositeName();
214             }
215 
216             // Modify existing LDAP user record
217 
218             name.add(getNameInNamespace(companyId, binding));
219 
220             Modifications mods = Modifications.getInstance();
221 
222             mods.addItem(
223                 userMappings.getProperty("firstName"), user.getFirstName());
224 
225             String middleNameMapping = userMappings.getProperty(
226                 "middleName");
227 
228             if (Validator.isNotNull(middleNameMapping)) {
229                 mods.addItem(middleNameMapping, user.getMiddleName());
230             }
231 
232             mods.addItem(
233                 userMappings.getProperty("lastName"), user.getLastName());
234 
235             String fullNameMapping = userMappings.getProperty("fullName");
236 
237             if (Validator.isNotNull(fullNameMapping)) {
238                 mods.addItem(fullNameMapping, user.getFullName());
239             }
240 
241             if (user.isPasswordModified() &&
242                 Validator.isNotNull(user.getPasswordUnencrypted())) {
243 
244                 mods.addItem(
245                     userMappings.getProperty("password"),
246                     user.getPasswordUnencrypted());
247             }
248 
249             mods.addItem(
250                 userMappings.getProperty("emailAddress"),
251                 user.getEmailAddress());
252 
253             String jobTitleMapping = userMappings.getProperty("jobTitle");
254 
255             if (Validator.isNotNull(jobTitleMapping)) {
256                 mods.addItem(jobTitleMapping, user.getJobTitle());
257             }
258 
259             ModificationItem[] modItems = mods.getItems();
260 
261             ctx.modifyAttributes(name, modItems);
262         }
263         catch (Exception e) {
264             _log.error(e, e);
265         }
266         finally {
267             if (ctx != null) {
268                 ctx.close();
269             }
270         }
271     }
272 
273     public static String getAuthSearchFilter(
274             long companyId, String emailAddress, String screenName,
275             String userId)
276         throws SystemException {
277 
278         String filter = PrefsPropsUtil.getString(
279             companyId, PropsKeys.LDAP_AUTH_SEARCH_FILTER);
280 
281         if (_log.isDebugEnabled()) {
282             _log.debug("Search filter before transformation " + filter);
283         }
284 
285         filter = StringUtil.replace(
286             filter,
287             new String[] {
288                 "@company_id@", "@email_address@", "@screen_name@", "@user_id@"
289             },
290             new String[] {
291                 String.valueOf(companyId), emailAddress, screenName,
292                 userId
293             });
294 
295         if (_log.isDebugEnabled()) {
296             _log.debug("Search filter after transformation " + filter);
297         }
298 
299         return filter;
300     }
301 
302     public static LdapContext getContext(long companyId) throws Exception {
303         String baseProviderURL = PrefsPropsUtil.getString(
304             companyId, PropsKeys.LDAP_BASE_PROVIDER_URL);
305         String pricipal = PrefsPropsUtil.getString(
306             companyId, PropsKeys.LDAP_SECURITY_PRINCIPAL);
307         String credentials = PrefsPropsUtil.getString(
308             companyId, PropsKeys.LDAP_SECURITY_CREDENTIALS);
309 
310         return getContext(companyId, baseProviderURL, pricipal, credentials);
311     }
312 
313     public static LdapContext getContext(
314             long companyId, String providerURL, String pricipal,
315             String credentials)
316         throws Exception {
317 
318         Properties env = new Properties();
319 
320         env.put(
321             Context.INITIAL_CONTEXT_FACTORY,
322             PrefsPropsUtil.getString(
323                 companyId, PropsKeys.LDAP_FACTORY_INITIAL));
324         env.put(Context.PROVIDER_URL, providerURL);
325         env.put(Context.SECURITY_PRINCIPAL, pricipal);
326         env.put(Context.SECURITY_CREDENTIALS, credentials);
327         env.put(
328             Context.REFERRAL,
329             PrefsPropsUtil.getString(companyId, PropsKeys.LDAP_REFERRAL));
330 
331         // Enable pooling
332 
333         env.put("com.sun.jndi.ldap.connect.pool", "true");
334         env.put("com.sun.jndi.ldap.connect.pool.maxsize","50");
335         env.put("com.sun.jndi.ldap.connect.pool.timeout", "10000");
336 
337         LogUtil.debug(_log, env);
338 
339         LdapContext ctx = null;
340 
341         try {
342             ctx = new InitialLdapContext(env, null);
343         }
344         catch (Exception e) {
345             if (_log.isWarnEnabled()) {
346                 _log.warn("Failed to bind to the LDAP server");
347             }
348 
349             if (_log.isDebugEnabled()) {
350                 _log.debug(e);
351             }
352         }
353 
354         return ctx;
355     }
356 
357     public static Attributes getGroupAttributes(
358             long companyId, LdapContext ctx, String fullDistinguishedName)
359         throws Exception {
360 
361         return getGroupAttributes(companyId, ctx, fullDistinguishedName, false);
362     }
363 
364     public static Attributes getGroupAttributes(
365             long companyId, LdapContext ctx, String fullDistinguishedName,
366             boolean includeReferenceAttributes)
367         throws Exception {
368 
369         Properties groupMappings = getGroupMappings(companyId);
370 
371         List<String> mappedGroupAttributeIds = new ArrayList<String>();
372 
373         mappedGroupAttributeIds.add(groupMappings.getProperty("groupName"));
374         mappedGroupAttributeIds.add(groupMappings.getProperty("description"));
375 
376         if (includeReferenceAttributes) {
377             mappedGroupAttributeIds.add(groupMappings.getProperty("user"));
378         }
379 
380         return _getAttributes(
381             ctx, fullDistinguishedName,
382             mappedGroupAttributeIds.toArray(new String[0]));
383     }
384 
385     public static Properties getGroupMappings(long companyId)
386         throws Exception {
387 
388         Properties groupMappings = PropertiesUtil.load(
389             PrefsPropsUtil.getString(companyId, PropsKeys.LDAP_GROUP_MAPPINGS));
390 
391         LogUtil.debug(_log, groupMappings);
392 
393         return groupMappings;
394     }
395 
396     public static List<SearchResult> getGroups(
397             long companyId, LdapContext ctx, int maxResults)
398         throws Exception {
399 
400         String baseDN = PrefsPropsUtil.getString(
401             companyId, PropsKeys.LDAP_BASE_DN);
402         String groupFilter = PrefsPropsUtil.getString(
403             companyId, PropsKeys.LDAP_IMPORT_GROUP_SEARCH_FILTER);
404 
405         return getGroups(companyId, ctx, maxResults, baseDN, groupFilter);
406     }
407 
408     public static List<SearchResult> getGroups(
409             long companyId, LdapContext ctx, int maxResults, String baseDN,
410             String groupFilter)
411         throws Exception {
412 
413         return _searchLDAP(
414             companyId, ctx, maxResults, baseDN, groupFilter, null);
415     }
416 
417     public static Attribute getMultivaluedAttribute(
418             long companyId, LdapContext ctx, String baseDN, String filter,
419             Attribute attribute)
420         throws Exception {
421 
422         if (attribute.size() > 0) {
423             return attribute;
424         }
425 
426         String[] attributeIds = {_getNextRange(attribute.getID())};
427 
428         while (true) {
429             List<SearchResult> results = _searchLDAP(
430                 companyId, ctx, 0, baseDN, filter, attributeIds);
431 
432             if (results.size() != 1) {
433                 break;
434             }
435 
436             SearchResult result = results.get(0);
437 
438             Attributes attributes = result.getAttributes();
439 
440             if (attributes.size() != 1) {
441                 break;
442             }
443 
444             NamingEnumeration<? extends Attribute> enu = attributes.getAll();
445 
446             if (!enu.hasMoreElements()) {
447                 break;
448             }
449 
450             Attribute curAttribute = enu.nextElement();
451 
452             for (int i = 0; i < curAttribute.size(); i++) {
453                 attribute.add(curAttribute.get(i));
454             }
455 
456             if (StringUtil.endsWith(curAttribute.getID(), StringPool.STAR) ||
457                 (curAttribute.size() < PropsValues.LDAP_RANGE_SIZE)) {
458 
459                 break;
460             }
461 
462             attributeIds[0] = _getNextRange(attributeIds[0]);
463         }
464 
465         return attribute;
466     }
467 
468     public static String getNameInNamespace(long companyId, Binding binding)
469         throws Exception {
470 
471         String baseDN = PrefsPropsUtil.getString(
472             companyId, PropsKeys.LDAP_BASE_DN);
473 
474         String name = binding.getName();
475 
476         if (name.startsWith(StringPool.QUOTE) &&
477             name.endsWith(StringPool.QUOTE)) {
478 
479             name = name.substring(1, name.length() - 1);
480         }
481 
482         if (Validator.isNull(baseDN)) {
483             return name.toString();
484         }
485         else {
486             StringBuilder sb = new StringBuilder();
487 
488             sb.append(name);
489             sb.append(StringPool.COMMA);
490             sb.append(baseDN);
491 
492             return sb.toString();
493         }
494     }
495 
496     public static Binding getUser(long companyId, String screenName)
497         throws Exception {
498 
499         LdapContext ctx = getContext(companyId);
500 
501         NamingEnumeration<SearchResult> enu = null;
502 
503         try {
504             if (ctx == null) {
505                 return null;
506             }
507 
508             String baseDN = PrefsPropsUtil.getString(
509                 companyId, PropsKeys.LDAP_BASE_DN);
510 
511             Properties userMappings = getUserMappings(companyId);
512 
513             StringBuilder filter = new StringBuilder();
514 
515             filter.append(StringPool.OPEN_PARENTHESIS);
516             filter.append(userMappings.getProperty("screenName"));
517             filter.append(StringPool.EQUAL);
518             filter.append(screenName);
519             filter.append(StringPool.CLOSE_PARENTHESIS);
520 
521             SearchControls cons = new SearchControls(
522                 SearchControls.SUBTREE_SCOPE, 1, 0, null, false, false);
523 
524             enu = ctx.search(baseDN, filter.toString(), cons);
525         }
526         catch (Exception e) {
527             throw e;
528         }
529         finally {
530             if (ctx != null) {
531                 ctx.close();
532             }
533         }
534 
535         if (enu.hasMoreElements()) {
536             Binding binding = enu.nextElement();
537 
538             enu.close();
539 
540             return binding;
541         }
542         else {
543             return null;
544         }
545     }
546 
547     public static Attributes getUserAttributes(
548             long companyId, LdapContext ctx, String fullDistinguishedName)
549         throws Exception {
550 
551         Properties userMappings = getUserMappings(companyId);
552 
553         String[] mappedUserAttributeIds = {
554             userMappings.getProperty("screenName"),
555             userMappings.getProperty("emailAddress"),
556             userMappings.getProperty("fullName"),
557             userMappings.getProperty("firstName"),
558             userMappings.getProperty("middleName"),
559             userMappings.getProperty("lastName"),
560             userMappings.getProperty("jobTitle"),
561             userMappings.getProperty("group")
562         };
563 
564         return _getAttributes(
565             ctx, fullDistinguishedName, mappedUserAttributeIds);
566     }
567 
568     public static Properties getUserMappings(long companyId) throws Exception {
569         Properties userMappings = PropertiesUtil.load(
570             PrefsPropsUtil.getString(companyId, PropsKeys.LDAP_USER_MAPPINGS));
571 
572         LogUtil.debug(_log, userMappings);
573 
574         return userMappings;
575     }
576 
577     public static List<SearchResult> getUsers(
578             long companyId, LdapContext ctx, int maxResults)
579         throws Exception {
580 
581         String baseDN = PrefsPropsUtil.getString(
582             companyId, PropsKeys.LDAP_BASE_DN);
583         String userFilter = PrefsPropsUtil.getString(
584             companyId, PropsKeys.LDAP_IMPORT_USER_SEARCH_FILTER);
585 
586         return getUsers(companyId, ctx, maxResults, baseDN, userFilter);
587     }
588 
589     public static List<SearchResult> getUsers(
590             long companyId, LdapContext ctx, int maxResults, String baseDN,
591             String userFilter)
592         throws Exception {
593 
594         return _searchLDAP(
595             companyId, ctx, maxResults, baseDN, userFilter, null);
596     }
597 
598     public static String getUsersDN(long companyId) throws Exception {
599         return PrefsPropsUtil.getString(companyId, PropsKeys.LDAP_USERS_DN);
600     }
601 
602     public static boolean hasUser(long companyId, String screenName)
603         throws Exception {
604 
605         if (getUser(companyId, screenName) != null) {
606             return true;
607         }
608         else {
609             return false;
610         }
611     }
612 
613     public static void importFromLDAP() throws Exception {
614         List<Company> companies = CompanyLocalServiceUtil.getCompanies(false);
615 
616         for (Company company : companies) {
617             importFromLDAP(company.getCompanyId());
618         }
619     }
620 
621     public static void importFromLDAP(long companyId) throws Exception {
622         if (!isImportEnabled(companyId)) {
623             return;
624         }
625 
626         LdapContext ctx = getContext(companyId);
627 
628         if (ctx == null) {
629             return;
630         }
631 
632         try {
633             String importMethod = PrefsPropsUtil.getString(
634                 companyId, PropsKeys.LDAP_IMPORT_METHOD);
635 
636             if (importMethod.equals(IMPORT_BY_USER)) {
637                 List<SearchResult> results = getUsers(companyId, ctx, 0);
638 
639                 // Loop through all LDAP users
640 
641                 for (SearchResult result : results) {
642                     Attributes attributes = getUserAttributes(
643                         companyId, ctx, getNameInNamespace(companyId, result));
644 
645                     try {
646                         importLDAPUser(
647                             companyId, ctx, attributes, StringPool.BLANK, true);
648                     }
649                     catch (Exception e) {
650                         _log.error("Unable to import user " + result, e);
651                     }
652                 }
653             }
654             else if (importMethod.equals(IMPORT_BY_GROUP)) {
655                 List<SearchResult> results = getGroups(companyId, ctx, 0);
656 
657                 // Loop through all LDAP groups
658 
659                 for (SearchResult result : results) {
660                     Attributes attributes = getGroupAttributes(
661                         companyId, ctx, getNameInNamespace(companyId, result),
662                         true);
663 
664                     importLDAPGroup(companyId, ctx, attributes, true);
665                 }
666             }
667         }
668         catch (Exception e) {
669             _log.error("Error importing LDAP users and groups", e);
670         }
671         finally {
672             if (ctx != null) {
673                 ctx.close();
674             }
675         }
676     }
677 
678     public static UserGroup importLDAPGroup(
679             long companyId, LdapContext ctx, Attributes attributes,
680             boolean importGroupMembership)
681         throws Exception {
682 
683         AttributesTransformer attributesTransformer =
684             AttributesTransformerFactory.getInstance();
685 
686         attributes = attributesTransformer.transformGroup(attributes);
687 
688         Properties groupMappings = getGroupMappings(companyId);
689 
690         LogUtil.debug(_log, groupMappings);
691 
692         String groupName = LDAPUtil.getAttributeValue(
693             attributes, groupMappings.getProperty("groupName")).toLowerCase();
694         String description = LDAPUtil.getAttributeValue(
695             attributes, groupMappings.getProperty("description"));
696 
697         // Get or create user group
698 
699         UserGroup userGroup = null;
700 
701         try {
702             userGroup = UserGroupLocalServiceUtil.getUserGroup(
703                 companyId, groupName);
704 
705             UserGroupLocalServiceUtil.updateUserGroup(
706                 companyId, userGroup.getUserGroupId(), groupName, description);
707         }
708         catch (NoSuchUserGroupException nsuge) {
709             if (_log.isDebugEnabled()) {
710                 _log.debug("Adding user group to portal " + groupName);
711             }
712 
713             long defaultUserId = UserLocalServiceUtil.getDefaultUserId(
714                 companyId);
715 
716             try {
717                 userGroup = UserGroupLocalServiceUtil.addUserGroup(
718                     defaultUserId, companyId, groupName, description);
719             }
720             catch (Exception e) {
721                 if (_log.isWarnEnabled()) {
722                     _log.warn("Could not create user group " + groupName);
723                 }
724 
725                 if (_log.isDebugEnabled()) {
726                     _log.debug(e, e);
727                 }
728             }
729         }
730 
731         // Import users and membership
732 
733         if (importGroupMembership && (userGroup != null)) {
734             Attribute attribute = attributes.get(
735                 groupMappings.getProperty("user"));
736 
737             if (attribute != null) {
738                 String baseDN = PrefsPropsUtil.getString(
739                     companyId, PropsKeys.LDAP_BASE_DN);
740 
741                 StringBuilder sb = new StringBuilder();
742 
743                 sb.append("(&");
744                 sb.append(
745                     PrefsPropsUtil.getString(
746                         companyId, PropsKeys.LDAP_IMPORT_GROUP_SEARCH_FILTER));
747                 sb.append("(");
748                 sb.append(groupMappings.getProperty("groupName"));
749                 sb.append("=");
750                 sb.append(
751                     LDAPUtil.getAttributeValue(
752                         attributes, groupMappings.getProperty("groupName")));
753                 sb.append("))");
754 
755                 attribute = getMultivaluedAttribute(
756                     companyId, ctx, baseDN, sb.toString(), attribute);
757 
758                 _importUsersAndMembershipFromLDAPGroup(
759                     companyId, ctx, userGroup.getUserGroupId(), attribute);
760             }
761         }
762 
763         return userGroup;
764     }
765 
766     public static User importLDAPUser(
767             long companyId, LdapContext ctx, Attributes attributes,
768             String password, boolean importGroupMembership)
769         throws Exception {
770 
771         LDAPUserTransactionThreadLocal.setOriginatesFromLDAP(true);
772 
773         try {
774             return _importLDAPUser(
775                 companyId, ctx, attributes, password, importGroupMembership);
776         }
777         finally {
778             LDAPUserTransactionThreadLocal.setOriginatesFromLDAP(false);
779         }
780     }
781 
782     public static boolean isAuthEnabled(long companyId) throws SystemException {
783         if (PrefsPropsUtil.getBoolean(
784                 companyId, PropsKeys.LDAP_AUTH_ENABLED,
785                 PropsValues.LDAP_AUTH_ENABLED)) {
786 
787             return true;
788         }
789         else {
790             return false;
791         }
792     }
793 
794     public static boolean isExportEnabled(long companyId)
795         throws SystemException {
796 
797         if (PrefsPropsUtil.getBoolean(
798                 companyId, PropsKeys.LDAP_EXPORT_ENABLED,
799                 PropsValues.LDAP_EXPORT_ENABLED)) {
800 
801             return true;
802         }
803         else {
804             return false;
805         }
806     }
807 
808     public static boolean isImportEnabled(long companyId)
809         throws SystemException {
810 
811         if (PrefsPropsUtil.getBoolean(
812                 companyId, PropsKeys.LDAP_IMPORT_ENABLED,
813                 PropsValues.LDAP_IMPORT_ENABLED)) {
814 
815             return true;
816         }
817         else {
818             return false;
819         }
820     }
821 
822     public static boolean isImportOnStartup(long companyId)
823         throws SystemException {
824 
825         if (PrefsPropsUtil.getBoolean(
826                 companyId, PropsKeys.LDAP_IMPORT_ON_STARTUP)) {
827 
828             return true;
829         }
830         else {
831             return false;
832         }
833     }
834 
835     public static boolean isNtlmEnabled(long companyId)
836         throws SystemException {
837 
838         if (!isAuthEnabled(companyId)) {
839             return false;
840         }
841 
842         if (PrefsPropsUtil.getBoolean(
843                 companyId, PropsKeys.NTLM_AUTH_ENABLED,
844                 PropsValues.NTLM_AUTH_ENABLED)) {
845 
846             return true;
847         }
848         else {
849             return false;
850         }
851     }
852 
853     public static boolean isPasswordPolicyEnabled(long companyId)
854         throws SystemException {
855 
856         if (PrefsPropsUtil.getBoolean(
857                 companyId, PropsKeys.LDAP_PASSWORD_POLICY_ENABLED,
858                 PropsValues.LDAP_PASSWORD_POLICY_ENABLED)) {
859 
860             return true;
861         }
862         else {
863             return false;
864         }
865     }
866 
867     public static boolean isSiteMinderEnabled(long companyId)
868         throws SystemException {
869 
870         if (!isAuthEnabled(companyId)) {
871             return false;
872         }
873 
874         if (PrefsPropsUtil.getBoolean(
875                 companyId, PropsKeys.SITEMINDER_AUTH_ENABLED,
876                 PropsValues.SITEMINDER_AUTH_ENABLED)) {
877 
878             return true;
879         }
880         else {
881             return false;
882         }
883     }
884 
885     private static Attributes _getAttributes(
886             LdapContext ctx, String fullDistinguishedName,
887             String[] attributeIds)
888         throws Exception {
889 
890         Name fullDN = new CompositeName().add(fullDistinguishedName);
891 
892         Attributes attributes = null;
893 
894         String[] auditAttributeIds = {
895             "creatorsName", "createTimestamp", "modifiersName",
896             "modifyTimestamp"
897         };
898 
899         if (attributeIds == null) {
900 
901             // Get complete listing of LDAP attributes (slow)
902 
903             attributes = ctx.getAttributes(fullDN);
904 
905             NamingEnumeration<? extends Attribute> enu = ctx.getAttributes(
906                 fullDN, auditAttributeIds).getAll();
907 
908             while (enu.hasMoreElements()) {
909                 attributes.put(enu.nextElement());
910             }
911 
912             enu.close();
913         }
914         else {
915 
916             // Get specified LDAP attributes
917 
918             int attributeCount = attributeIds.length + auditAttributeIds.length;
919 
920             String[] allAttributeIds = new String[attributeCount];
921 
922             System.arraycopy(
923                 attributeIds, 0, allAttributeIds, 0, attributeIds.length);
924             System.arraycopy(
925                 auditAttributeIds, 0, allAttributeIds, attributeIds.length,
926                 auditAttributeIds.length);
927 
928             attributes = ctx.getAttributes(fullDN, allAttributeIds);
929         }
930 
931         return attributes;
932     }
933 
934     private static byte[] _getCookie(Control[] controls) {
935         if (controls == null) {
936             return null;
937         }
938 
939         for (Control control : controls) {
940             if (control instanceof PagedResultsResponseControl) {
941                 PagedResultsResponseControl pagedResultsResponseControl =
942                     (PagedResultsResponseControl)control;
943 
944                 return pagedResultsResponseControl.getCookie();
945             }
946         }
947 
948         return null;
949     }
950 
951     private static void _getDNName(
952             long companyId, User user, Properties userMappings, Name name)
953         throws Exception {
954 
955         // Generate full DN based on user DN
956 
957         StringBuilder sb = new StringBuilder();
958 
959         sb.append(userMappings.getProperty("screenName"));
960         sb.append(StringPool.EQUAL);
961         sb.append(user.getScreenName());
962         sb.append(StringPool.COMMA);
963         sb.append(getUsersDN(companyId));
964 
965         name.add(sb.toString());
966     }
967 
968     private static String _getNextRange(String attributeId) {
969         String originalAttributeId = null;
970         int start = 0;
971         int end = 0;
972 
973         int x = attributeId.indexOf(StringPool.SEMICOLON);
974 
975         if (x < 0) {
976             originalAttributeId = attributeId;
977             end = PropsValues.LDAP_RANGE_SIZE - 1;
978         }
979         else {
980             int y = attributeId.indexOf(StringPool.EQUAL, x);
981             int z = attributeId.indexOf(StringPool.DASH, y);
982 
983             originalAttributeId = attributeId.substring(0, x);
984             start = GetterUtil.getInteger(attributeId.substring(y + 1, z));
985             end = GetterUtil.getInteger(attributeId.substring(z + 1));
986 
987             start += PropsValues.LDAP_RANGE_SIZE;
988             end += PropsValues.LDAP_RANGE_SIZE;
989         }
990 
991         StringBuilder sb = new StringBuilder();
992 
993         sb.append(originalAttributeId);
994         sb.append(StringPool.SEMICOLON);
995         sb.append("range=");
996         sb.append(start);
997         sb.append(StringPool.DASH);
998         sb.append(end);
999 
1000        return sb.toString();
1001    }
1002
1003    private static void _importGroupsAndMembershipFromLDAPUser(
1004            long companyId, LdapContext ctx, long userId, Attribute attr)
1005        throws Exception {
1006
1007        List<Long> newUserGroupIds = new ArrayList<Long>(attr.size());
1008
1009        for (int i = 0; i < attr.size(); i++) {
1010
1011            // Find group in LDAP
1012
1013            String fullGroupDN = (String)attr.get(i);
1014
1015            Attributes groupAttributes = null;
1016
1017            try {
1018                groupAttributes = getGroupAttributes(
1019                    companyId, ctx, fullGroupDN);
1020            }
1021            catch (NameNotFoundException nnfe) {
1022                _log.error(
1023                    "LDAP group not found with fullGroupDN " + fullGroupDN);
1024
1025                _log.error(nnfe, nnfe);
1026
1027                continue;
1028            }
1029
1030            UserGroup userGroup = importLDAPGroup(
1031                companyId, ctx, groupAttributes, false);
1032
1033            // Add user to user group
1034
1035            if (userGroup != null) {
1036                if (_log.isDebugEnabled()) {
1037                    _log.debug(
1038                        "Adding " + userId + " to group " +
1039                            userGroup.getUserGroupId());
1040                }
1041
1042                newUserGroupIds.add(userGroup.getUserGroupId());
1043            }
1044        }
1045
1046        UserGroupLocalServiceUtil.setUserUserGroups(
1047            userId,
1048            ArrayUtil.toArray(
1049                newUserGroupIds.toArray(new Long[newUserGroupIds.size()])));
1050    }
1051
1052    private static User _importLDAPUser(
1053            long companyId, LdapContext ctx, Attributes attributes,
1054            String password, boolean importGroupMembership)
1055        throws Exception {
1056
1057        AttributesTransformer attributesTransformer =
1058            AttributesTransformerFactory.getInstance();
1059
1060        attributes = attributesTransformer.transformUser(attributes);
1061
1062        Properties userMappings = getUserMappings(companyId);
1063
1064        LogUtil.debug(_log, userMappings);
1065
1066        User defaultUser = UserLocalServiceUtil.getDefaultUser(companyId);
1067
1068        boolean autoPassword = false;
1069        boolean updatePassword = true;
1070
1071        if (password.equals(StringPool.BLANK)) {
1072            autoPassword = true;
1073            updatePassword = false;
1074        }
1075
1076        long creatorUserId = 0;
1077        boolean passwordReset = false;
1078        boolean autoScreenName = false;
1079        String screenName = LDAPUtil.getAttributeValue(
1080            attributes, userMappings.getProperty("screenName")).toLowerCase();
1081        String emailAddress = LDAPUtil.getAttributeValue(
1082            attributes, userMappings.getProperty("emailAddress"));
1083        Locale locale = defaultUser.getLocale();
1084        String firstName = LDAPUtil.getAttributeValue(
1085            attributes, userMappings.getProperty("firstName"));
1086        String middleName = LDAPUtil.getAttributeValue(
1087            attributes, userMappings.getProperty("middleName"));
1088        String lastName = LDAPUtil.getAttributeValue(
1089            attributes, userMappings.getProperty("lastName"));
1090
1091        if (Validator.isNull(firstName) || Validator.isNull(lastName)) {
1092            String fullName = LDAPUtil.getAttributeValue(
1093                attributes, userMappings.getProperty("fullName"));
1094
1095            String[] names = LDAPUtil.splitFullName(fullName);
1096
1097            firstName = names[0];
1098            middleName = names[1];
1099            lastName = names[2];
1100        }
1101
1102        int prefixId = 0;
1103        int suffixId = 0;
1104        boolean male = true;
1105        int birthdayMonth = Calendar.JANUARY;
1106        int birthdayDay = 1;
1107        int birthdayYear = 1970;
1108        String jobTitle = LDAPUtil.getAttributeValue(
1109            attributes, userMappings.getProperty("jobTitle"));
1110        long[] organizationIds = new long[0];
1111        boolean sendEmail = false;
1112
1113        if (_log.isDebugEnabled()) {
1114            _log.debug(
1115                "Screen name " + screenName + " and email address " +
1116                    emailAddress);
1117        }
1118
1119        if (Validator.isNull(screenName) &&
1120            !PrefsPropsUtil.getBoolean(
1121                companyId, PropsKeys.USERS_SCREEN_NAME_ALWAYS_AUTOGENERATE)) {
1122
1123            throw new UserScreenNameException(
1124                "Screen name cannot be null for " +
1125                    ContactConstants.getFullName(
1126                        firstName, middleName, lastName));
1127        }
1128
1129        if (Validator.isNull(emailAddress)) {
1130            if (_log.isWarnEnabled()) {
1131                _log.warn(
1132                    "Cannot add user because screen name and email address " +
1133                        "are required");
1134            }
1135
1136            return null;
1137        }
1138
1139        User user = null;
1140
1141        try {
1142
1143            // Find corresponding portal user
1144
1145            String authType = PrefsPropsUtil.getString(
1146                companyId, PropsKeys.COMPANY_SECURITY_AUTH_TYPE,
1147                PropsValues.COMPANY_SECURITY_AUTH_TYPE);
1148
1149            if (authType.equals(CompanyConstants.AUTH_TYPE_SN)) {
1150                user = UserLocalServiceUtil.getUserByScreenName(
1151                    companyId, screenName);
1152            }
1153            else {
1154                user = UserLocalServiceUtil.getUserByEmailAddress(
1155                    companyId, emailAddress);
1156            }
1157
1158            // Skip if is default user
1159
1160            if (user.isDefaultUser()) {
1161                return user;
1162            }
1163
1164            // User already exists in the Liferay database. Skip import if user
1165            // fields have been already synced, if import is part of a scheduled
1166            // import, or if the LDAP entry has never been modified.
1167
1168            Date ldapUserModifiedDate = null;
1169
1170            String modifiedDate = LDAPUtil.getAttributeValue(
1171                attributes, "modifyTimestamp");
1172
1173            try {
1174                if (Validator.isNull(modifiedDate)) {
1175                    if (_log.isInfoEnabled()) {
1176                        _log.info(
1177                            "LDAP entry never modified, skipping user " +
1178                                user.getEmailAddress());
1179                    }
1180
1181                    return user;
1182                }
1183                else {
1184                    DateFormat dateFormat =
1185                        DateFormatFactoryUtil.getSimpleDateFormat(
1186                            "yyyyMMddHHmmss");
1187
1188                    ldapUserModifiedDate = dateFormat.parse(modifiedDate);
1189                }
1190
1191                if (ldapUserModifiedDate.equals(user.getModifiedDate()) &&
1192                    autoPassword) {
1193
1194                    if (_log.isDebugEnabled()) {
1195                        _log.debug(
1196                            "User is already syncronized, skipping user " +
1197                                user.getEmailAddress());
1198                    }
1199
1200                    return user;
1201                }
1202            }
1203            catch (ParseException pe) {
1204                if (_log.isDebugEnabled()) {
1205                    _log.debug(
1206                        "Unable to parse LDAP modify timestamp " +
1207                            modifiedDate);
1208                }
1209
1210                _log.debug(pe, pe);
1211            }
1212
1213            // LPS-443
1214
1215            if (Validator.isNull(screenName)) {
1216                autoScreenName = true;
1217            }
1218
1219            if (autoScreenName) {
1220                ScreenNameGenerator screenNameGenerator =
1221                    ScreenNameGeneratorFactory.getInstance();
1222
1223                screenName = screenNameGenerator.generate(
1224                    companyId, user.getUserId(), emailAddress);
1225            }
1226
1227            Contact contact = user.getContact();
1228
1229            Calendar birthdayCal = CalendarFactoryUtil.getCalendar();
1230
1231            birthdayCal.setTime(contact.getBirthday());
1232
1233            birthdayMonth = birthdayCal.get(Calendar.MONTH);
1234            birthdayDay = birthdayCal.get(Calendar.DATE);
1235            birthdayYear = birthdayCal.get(Calendar.YEAR);
1236
1237            // User exists so update user information
1238
1239            if (updatePassword) {
1240                user = UserLocalServiceUtil.updatePassword(
1241                    user.getUserId(), password, password, passwordReset, true);
1242            }
1243
1244            user = UserLocalServiceUtil.updateUser(
1245                user.getUserId(), password, user.isPasswordReset(), screenName,
1246                emailAddress, user.getLanguageId(), user.getTimeZoneId(),
1247                user.getGreeting(), user.getComments(), firstName, middleName,
1248                lastName, contact.getPrefixId(), contact.getSuffixId(),
1249                contact.getMale(), birthdayMonth, birthdayDay, birthdayYear,
1250                contact.getSmsSn(), contact.getAimSn(), contact.getFacebookSn(),
1251                contact.getIcqSn(), contact.getJabberSn(), contact.getMsnSn(),
1252                contact.getMySpaceSn(), contact.getSkypeSn(),
1253                contact.getTwitterSn(), contact.getYmSn(), jobTitle,
1254                user.getOrganizationIds());
1255
1256            if (ldapUserModifiedDate != null) {
1257                UserLocalServiceUtil.updateModifiedDate(
1258                    user.getUserId(), ldapUserModifiedDate);
1259            }
1260        }
1261        catch (NoSuchUserException nsue) {
1262
1263            // User does not exist so create
1264
1265        }
1266        catch (Exception e) {
1267            _log.error(
1268                "Error updating user with screen name " + screenName +
1269                    " and email address " + emailAddress,
1270                e);
1271
1272            return null;
1273        }
1274
1275        if (user == null) {
1276            try {
1277                if (_log.isDebugEnabled()) {
1278                    _log.debug("Adding user to portal " + emailAddress);
1279                }
1280
1281                user = UserLocalServiceUtil.addUser(
1282                    creatorUserId, companyId, autoPassword, password, password,
1283                    autoScreenName, screenName, emailAddress, locale, firstName,
1284                    middleName, lastName, prefixId, suffixId, male,
1285                    birthdayMonth, birthdayDay, birthdayYear, jobTitle,
1286                    organizationIds, sendEmail);
1287            }
1288            catch (Exception e) {
1289                _log.error(
1290                    "Problem adding user with screen name " + screenName +
1291                        " and email address " + emailAddress,
1292                    e);
1293            }
1294        }
1295
1296        // Import user groups and membership
1297
1298        if (importGroupMembership && (user != null)) {
1299            String userMappingsGroup = userMappings.getProperty("group");
1300
1301            if (userMappingsGroup != null) {
1302                Attribute attribute = attributes.get(userMappingsGroup);
1303
1304                if (attribute != null) {
1305                    attribute.clear();
1306
1307                    Properties groupMappings = getGroupMappings(companyId);
1308
1309                    String baseDN = PrefsPropsUtil.getString(
1310                        companyId, PropsKeys.LDAP_BASE_DN);
1311
1312                    Binding binding = getUser(companyId, screenName);
1313
1314                    String fullUserDN = getNameInNamespace(companyId, binding);
1315
1316                    StringBuilder sb = new StringBuilder();
1317
1318                    sb.append(StringPool.OPEN_PARENTHESIS);
1319                    sb.append(StringPool.AMPERSAND);
1320                    sb.append(
1321                        PrefsPropsUtil.getString(
1322                            companyId,
1323                            PropsKeys.LDAP_IMPORT_GROUP_SEARCH_FILTER));
1324                    sb.append(StringPool.OPEN_PARENTHESIS);
1325                    sb.append(groupMappings.getProperty("user"));
1326                    sb.append(StringPool.EQUAL);
1327                    sb.append(fullUserDN);
1328                    sb.append(StringPool.CLOSE_PARENTHESIS);
1329                    sb.append(StringPool.CLOSE_PARENTHESIS);
1330
1331                    List<SearchResult> results = _searchLDAP(
1332                        companyId, ctx, 0, baseDN, sb.toString(), null);
1333
1334                    for (SearchResult result : results) {
1335                        String fullGroupDN = getNameInNamespace(
1336                            companyId, result);
1337
1338                        attribute.add(fullGroupDN);
1339                    }
1340
1341                    _importGroupsAndMembershipFromLDAPUser(
1342                        companyId, ctx, user.getUserId(), attribute);
1343                }
1344            }
1345        }
1346
1347        return user;
1348    }
1349
1350    private static void _importUsersAndMembershipFromLDAPGroup(
1351            long companyId, LdapContext ctx, long userGroupId, Attribute attr)
1352        throws Exception {
1353
1354        List<Long> newUserIds = new ArrayList<Long>(attr.size());
1355
1356        for (int i = 0; i < attr.size(); i++) {
1357
1358            // Find user in LDAP
1359
1360            String fullUserDN = (String)attr.get(i);
1361
1362            Attributes userAttributes = null;
1363
1364            try {
1365                userAttributes = getUserAttributes(companyId, ctx, fullUserDN);
1366            }
1367            catch (NameNotFoundException nnfe) {
1368                _log.error("LDAP user not found with fullUserDN " + fullUserDN);
1369
1370                _log.error(nnfe, nnfe);
1371
1372                continue;
1373            }
1374
1375            User user = importLDAPUser(
1376                companyId, ctx, userAttributes, StringPool.BLANK, false);
1377
1378            // Add user to user group
1379
1380            if (user != null) {
1381                if (_log.isDebugEnabled()) {
1382                    _log.debug(
1383                        "Adding " + user.getUserId() + " to group " +
1384                            userGroupId);
1385                }
1386
1387                newUserIds.add(user.getUserId());
1388            }
1389        }
1390
1391        UserLocalServiceUtil.setUserGroupUsers(
1392            userGroupId,
1393            ArrayUtil.toArray(newUserIds.toArray(new Long[newUserIds.size()])));
1394    }
1395
1396    private static List<SearchResult> _searchLDAP(
1397            long companyId, LdapContext ctx, int maxResults, String baseDN,
1398            String filter, String[] attributeIds)
1399        throws Exception {
1400
1401        List<SearchResult> results = new ArrayList<SearchResult>();
1402
1403        SearchControls cons = new SearchControls(
1404            SearchControls.SUBTREE_SCOPE, maxResults, 0, attributeIds, false,
1405            false);
1406
1407        try {
1408            byte[] cookie = new byte[0];
1409
1410            while (cookie != null) {
1411                if (cookie.length == 0) {
1412                    ctx.setRequestControls(
1413                        new Control[] {
1414                            new PagedResultsControl(
1415                                PropsValues.LDAP_PAGE_SIZE, Control.CRITICAL)
1416                        });
1417                }
1418                else {
1419                    ctx.setRequestControls(
1420                        new Control[] {
1421                            new PagedResultsControl(
1422                                PropsValues.LDAP_PAGE_SIZE, cookie,
1423                                Control.CRITICAL)
1424                        });
1425                }
1426
1427                NamingEnumeration<SearchResult> enu = ctx.search(
1428                    baseDN, filter, cons);
1429
1430                while (enu.hasMoreElements()) {
1431                    results.add(enu.nextElement());
1432                }
1433
1434                enu.close();
1435
1436                cookie = _getCookie(ctx.getResponseControls());
1437            }
1438        }
1439        catch (OperationNotSupportedException onse) {
1440            ctx.setRequestControls(null);
1441
1442            NamingEnumeration<SearchResult> enu = ctx.search(
1443                baseDN, filter, cons);
1444
1445            while (enu.hasMoreElements()) {
1446                results.add(enu.nextElement());
1447            }
1448
1449            enu.close();
1450        }
1451        finally {
1452            ctx.setRequestControls(null);
1453        }
1454
1455        return results;
1456    }
1457
1458    private static Log _log = LogFactoryUtil.getLog(PortalLDAPUtil.class);
1459
1460}