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