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