1   /**
2    * Copyright (c) 2000-2010 Liferay, Inc. All rights reserved.
3    *
4    * This library is free software; you can redistribute it and/or modify it under
5    * the terms of the GNU Lesser General Public License as published by the Free
6    * Software Foundation; either version 2.1 of the License, or (at your option)
7    * any later version.
8    *
9    * This library is distributed in the hope that it will be useful, but WITHOUT
10   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11   * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
12   * details.
13   */
14  
15  package com.liferay.portal.security.auth;
16  
17  import com.liferay.portal.NoSuchUserException;
18  import com.liferay.portal.PasswordExpiredException;
19  import com.liferay.portal.UserLockoutException;
20  import com.liferay.portal.kernel.log.Log;
21  import com.liferay.portal.kernel.log.LogFactoryUtil;
22  import com.liferay.portal.kernel.util.PropsKeys;
23  import com.liferay.portal.kernel.util.StringPool;
24  import com.liferay.portal.kernel.util.StringUtil;
25  import com.liferay.portal.kernel.util.Validator;
26  import com.liferay.portal.model.User;
27  import com.liferay.portal.security.ldap.LDAPSettingsUtil;
28  import com.liferay.portal.security.ldap.PortalLDAPImporterUtil;
29  import com.liferay.portal.security.ldap.PortalLDAPUtil;
30  import com.liferay.portal.security.pwd.PwdEncryptor;
31  import com.liferay.portal.service.UserLocalServiceUtil;
32  import com.liferay.portal.util.PrefsPropsUtil;
33  import com.liferay.portal.util.PropsValues;
34  import com.liferay.portlet.admin.util.OmniadminUtil;
35  
36  import java.util.Hashtable;
37  import java.util.Map;
38  
39  import javax.naming.Context;
40  import javax.naming.NamingEnumeration;
41  import javax.naming.directory.Attribute;
42  import javax.naming.directory.Attributes;
43  import javax.naming.directory.SearchControls;
44  import javax.naming.directory.SearchResult;
45  import javax.naming.ldap.Control;
46  import javax.naming.ldap.InitialLdapContext;
47  import javax.naming.ldap.LdapContext;
48  
49  /**
50   * <a href="LDAPAuth.java.html"><b><i>View Source</i></b></a>
51   *
52   * @author Brian Wing Shun Chan
53   * @author Scott Lee
54   */
55  public class LDAPAuth implements Authenticator {
56  
57      public static final String AUTH_METHOD_BIND = "bind";
58  
59      public static final String AUTH_METHOD_PASSWORD_COMPARE =
60          "password-compare";
61  
62      public static final String RESULT_PASSWORD_EXP_WARNING =
63          "2.16.840.1.113730.3.4.5";
64  
65      public static final String RESULT_PASSWORD_RESET =
66          "2.16.840.1.113730.3.4.4";
67  
68      public int authenticateByEmailAddress(
69              long companyId, String emailAddress, String password,
70              Map<String, String[]> headerMap, Map<String, String[]> parameterMap)
71          throws AuthException {
72  
73          try {
74              return authenticate(
75                  companyId, emailAddress, StringPool.BLANK, 0, password);
76          }
77          catch (Exception e) {
78              _log.error(e, e);
79  
80              throw new AuthException(e);
81          }
82      }
83  
84      public int authenticateByScreenName(
85              long companyId, String screenName, String password,
86              Map<String, String[]> headerMap, Map<String, String[]> parameterMap)
87          throws AuthException {
88  
89          try {
90              return authenticate(
91                  companyId, StringPool.BLANK, screenName, 0, password);
92          }
93          catch (Exception e) {
94              _log.error(e, e);
95  
96              throw new AuthException(e);
97          }
98      }
99  
100     public int authenticateByUserId(
101             long companyId, long userId, String password,
102             Map<String, String[]> headerMap, Map<String, String[]> parameterMap)
103         throws AuthException {
104 
105         try {
106             return authenticate(
107                 companyId, StringPool.BLANK, StringPool.BLANK, userId,
108                 password);
109         }
110         catch (Exception e) {
111             _log.error(e, e);
112 
113             throw new AuthException(e);
114         }
115     }
116 
117     protected LDAPAuthResult authenticate(
118             LdapContext ctx, long companyId, Attributes attrs, String userDN,
119             String password)
120         throws Exception {
121 
122         LDAPAuthResult ldapAuthResult = new LDAPAuthResult();
123 
124         // Check passwords by either doing a comparison between the passwords or
125         // by binding to the LDAP server. If using LDAP password policies, bind
126         // auth method must be used in order to get the result control codes.
127 
128         String authMethod = PrefsPropsUtil.getString(
129             companyId, PropsKeys.LDAP_AUTH_METHOD);
130         InitialLdapContext innerCtx = null;
131 
132         if (authMethod.equals(AUTH_METHOD_BIND)) {
133             try {
134                 Hashtable<String, Object> env =
135                     (Hashtable<String, Object>)ctx.getEnvironment();
136 
137                 env.put(Context.SECURITY_PRINCIPAL, userDN);
138                 env.put(Context.SECURITY_CREDENTIALS, password);
139                 env.put(
140                     Context.REFERRAL,
141                     PrefsPropsUtil.getString(
142                         companyId, PropsKeys.LDAP_REFERRAL));
143 
144                 // Do not use pooling because principal changes
145 
146                 env.put("com.sun.jndi.ldap.connect.pool", "false");
147 
148                 innerCtx = new InitialLdapContext(env, null);
149 
150                 // Get LDAP bind results
151 
152                 Control[] responseControls =  innerCtx.getResponseControls();
153 
154                 ldapAuthResult.setAuthenticated(true);
155                 ldapAuthResult.setResponseControl(responseControls);
156             }
157             catch (Exception e) {
158                 if (_log.isDebugEnabled()) {
159                     _log.debug(
160                         "Failed to bind to the LDAP server with userDN "
161                             + userDN + " and password " + password);
162                 }
163 
164                 _log.error("Failed to bind to the LDAP server", e);
165 
166                 ldapAuthResult.setAuthenticated(false);
167                 ldapAuthResult.setErrorMessage(e.getMessage());
168             }
169             finally {
170                 if (innerCtx != null) {
171                     innerCtx.close();
172                 }
173             }
174         }
175         else if (authMethod.equals(AUTH_METHOD_PASSWORD_COMPARE)) {
176             Attribute userPassword = attrs.get("userPassword");
177 
178             if (userPassword != null) {
179                 String ldapPassword = new String((byte[])userPassword.get());
180 
181                 String encryptedPassword = password;
182 
183                 String algorithm = PrefsPropsUtil.getString(
184                     companyId,
185                     PropsKeys.LDAP_AUTH_PASSWORD_ENCRYPTION_ALGORITHM);
186 
187                 if (Validator.isNotNull(algorithm)) {
188                     encryptedPassword =
189                         "{" + algorithm + "}" +
190                             PwdEncryptor.encrypt(
191                                 algorithm, password, ldapPassword);
192                 }
193 
194                 if (ldapPassword.equals(encryptedPassword)) {
195                     ldapAuthResult.setAuthenticated(true);
196                 }
197                 else {
198                     ldapAuthResult.setAuthenticated(false);
199 
200                     if (_log.isWarnEnabled()) {
201                         _log.warn(
202                             "Passwords do not match for userDN " + userDN);
203                     }
204                 }
205             }
206         }
207 
208         return ldapAuthResult;
209     }
210 
211     protected int authenticate(
212             long companyId, long ldapServerId, String emailAddress,
213             String screenName, long userId, String password)
214         throws Exception {
215 
216         String postfix = LDAPSettingsUtil.getPropertyPostfix(ldapServerId);
217 
218         LdapContext ctx = PortalLDAPUtil.getContext(ldapServerId, companyId);
219 
220         if (ctx == null) {
221             return FAILURE;
222         }
223 
224         try {
225             String baseDN = PrefsPropsUtil.getString(
226                 companyId, PropsKeys.LDAP_BASE_DN + postfix);
227 
228             //  Process LDAP auth search filter
229 
230             String filter = LDAPSettingsUtil.getAuthSearchFilter(
231                 ldapServerId, companyId, emailAddress, screenName,
232                 String.valueOf(userId));
233 
234             SearchControls cons = new SearchControls(
235                 SearchControls.SUBTREE_SCOPE, 1, 0, null, false, false);
236 
237             NamingEnumeration<SearchResult> enu = ctx.search(
238                 baseDN, filter, cons);
239 
240             if (enu.hasMoreElements()) {
241                 if (_log.isDebugEnabled()) {
242                     _log.debug("Search filter returned at least one result");
243                 }
244 
245                 SearchResult result = enu.nextElement();
246 
247                 String fullUserDN = PortalLDAPUtil.getNameInNamespace(
248                     ldapServerId, companyId, result);
249 
250                 Attributes attrs = PortalLDAPUtil.getUserAttributes(
251                     ldapServerId, companyId, ctx, fullUserDN);
252 
253                 LDAPAuthResult ldapAuthResult = authenticate(
254                     ctx, companyId, attrs, fullUserDN, password);
255 
256                 // Process LDAP failure codes
257 
258                 String errorMessage = ldapAuthResult.getErrorMessage();
259 
260                 if (errorMessage != null) {
261                     if (errorMessage.indexOf(PrefsPropsUtil.getString(
262                             companyId, PropsKeys.LDAP_ERROR_USER_LOCKOUT))
263                                 != -1) {
264 
265                         throw new UserLockoutException();
266                     }
267                     else if (errorMessage.indexOf(PrefsPropsUtil.getString(
268                         companyId, PropsKeys.LDAP_ERROR_PASSWORD_EXPIRED))
269                             != -1) {
270 
271                         throw new PasswordExpiredException();
272                     }
273                 }
274 
275                 if (!ldapAuthResult.isAuthenticated()) {
276                     return FAILURE;
277                 }
278 
279                 // Get user or create from LDAP
280 
281                 User user = PortalLDAPImporterUtil.importLDAPUser(
282                     ldapServerId, companyId, ctx, attrs, password);
283 
284                 // Process LDAP success codes
285 
286                 String resultCode = ldapAuthResult.getResponseControl();
287 
288                 if (resultCode.equals(LDAPAuth.RESULT_PASSWORD_RESET)) {
289                     UserLocalServiceUtil.updatePasswordReset(
290                         user.getUserId(), true);
291                 }
292                 else if (
293                     resultCode.equals(LDAPAuth.RESULT_PASSWORD_EXP_WARNING)) {
294 
295                     UserLocalServiceUtil.updatePasswordReset(
296                         user.getUserId(), true);
297                 }
298             }
299             else {
300                 if (_log.isDebugEnabled()) {
301                     _log.debug("Search filter did not return any results");
302                 }
303 
304                 return DNE;
305             }
306 
307             enu.close();
308         }
309         catch (Exception e) {
310             _log.error("Problem accessing LDAP server", e);
311 
312             return FAILURE;
313         }
314         finally {
315             if (ctx != null) {
316                 ctx.close();
317             }
318         }
319 
320         return SUCCESS;
321     }
322 
323     protected int authenticate(
324             long companyId, String emailAddress, String screenName, long userId,
325             String password)
326         throws Exception {
327 
328         if (!LDAPSettingsUtil.isAuthEnabled(companyId)) {
329             if (_log.isDebugEnabled()) {
330                 _log.debug("Authenticator is not enabled");
331             }
332 
333             return SUCCESS;
334         }
335 
336         if (_log.isDebugEnabled()) {
337             _log.debug("Authenticator is enabled");
338         }
339 
340         long[] ldapServerIds = StringUtil.split(
341             PrefsPropsUtil.getString(companyId, "ldap.server.ids"), 0L);
342 
343         if (ldapServerIds.length <= 0) {
344             ldapServerIds = new long[] {0};
345         }
346 
347         for (long ldapServerId : ldapServerIds) {
348             int result = authenticate(
349                 companyId, ldapServerId, emailAddress, screenName, userId,
350                 password);
351 
352             if (result == SUCCESS) {
353                 return result;
354             }
355         }
356 
357         return authenticateRequired(
358             companyId, userId, emailAddress, screenName, true, FAILURE);
359     }
360 
361     protected int authenticateOmniadmin(
362             long companyId, String emailAddress, String screenName, long userId)
363         throws Exception {
364 
365         // Only allow omniadmin if Liferay password checking is enabled
366 
367         if (PropsValues.AUTH_PIPELINE_ENABLE_LIFERAY_CHECK) {
368             if (userId > 0) {
369                 if (OmniadminUtil.isOmniadmin(userId)) {
370                     return SUCCESS;
371                 }
372             }
373             else if (Validator.isNotNull(emailAddress)) {
374                 try {
375                     User user = UserLocalServiceUtil.getUserByEmailAddress(
376                         companyId, emailAddress);
377 
378                     if (OmniadminUtil.isOmniadmin(user.getUserId())) {
379                         return SUCCESS;
380                     }
381                 }
382                 catch (NoSuchUserException nsue) {
383                 }
384             }
385             else if (Validator.isNotNull(screenName)) {
386                 try {
387                     User user = UserLocalServiceUtil.getUserByScreenName(
388                         companyId, screenName);
389 
390                     if (OmniadminUtil.isOmniadmin(user.getUserId())) {
391                         return SUCCESS;
392                     }
393                 }
394                 catch (NoSuchUserException nsue) {
395                 }
396             }
397         }
398 
399         return FAILURE;
400     }
401 
402     protected int authenticateRequired(
403             long companyId, long userId, String emailAddress, String screenName,
404             boolean allowOmniadmin, int failureCode)
405         throws Exception {
406 
407         // Make exceptions for omniadmins so that if they break the LDAP
408         // configuration, they can still login to fix the problem
409 
410         if (allowOmniadmin &&
411             (authenticateOmniadmin(
412                 companyId, emailAddress, screenName, userId) == SUCCESS)) {
413 
414             return SUCCESS;
415         }
416 
417         if (PrefsPropsUtil.getBoolean(
418                 companyId, PropsKeys.LDAP_AUTH_REQUIRED)) {
419 
420             return failureCode;
421         }
422         else {
423             return SUCCESS;
424         }
425     }
426 
427     private static Log _log = LogFactoryUtil.getLog(LDAPAuth.class);
428 
429 }