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(
165                     "Failed to bind to the LDAP server: " + e.getMessage());
166 
167                 ldapAuthResult.setAuthenticated(false);
168                 ldapAuthResult.setErrorMessage(e.getMessage());
169             }
170             finally {
171                 if (innerCtx != null) {
172                     innerCtx.close();
173                 }
174             }
175         }
176         else if (authMethod.equals(AUTH_METHOD_PASSWORD_COMPARE)) {
177             Attribute userPassword = attrs.get("userPassword");
178 
179             if (userPassword != null) {
180                 String ldapPassword = new String((byte[])userPassword.get());
181 
182                 String encryptedPassword = password;
183 
184                 String algorithm = PrefsPropsUtil.getString(
185                     companyId,
186                     PropsKeys.LDAP_AUTH_PASSWORD_ENCRYPTION_ALGORITHM);
187 
188                 if (Validator.isNotNull(algorithm)) {
189                     encryptedPassword =
190                         "{" + algorithm + "}" +
191                             PwdEncryptor.encrypt(
192                                 algorithm, password, ldapPassword);
193                 }
194 
195                 if (ldapPassword.equals(encryptedPassword)) {
196                     ldapAuthResult.setAuthenticated(true);
197                 }
198                 else {
199                     ldapAuthResult.setAuthenticated(false);
200 
201                     if (_log.isWarnEnabled()) {
202                         _log.warn(
203                             "Passwords do not match for userDN " + userDN);
204                     }
205                 }
206             }
207         }
208 
209         return ldapAuthResult;
210     }
211 
212     protected int authenticate(
213             long companyId, long ldapServerId, String emailAddress,
214             String screenName, long userId, String password)
215         throws Exception {
216 
217         String postfix = LDAPSettingsUtil.getPropertyPostfix(ldapServerId);
218 
219         LdapContext ctx = PortalLDAPUtil.getContext(ldapServerId, companyId);
220 
221         if (ctx == null) {
222             return FAILURE;
223         }
224 
225         try {
226             String baseDN = PrefsPropsUtil.getString(
227                 companyId, PropsKeys.LDAP_BASE_DN + postfix);
228 
229             //  Process LDAP auth search filter
230 
231             String filter = LDAPSettingsUtil.getAuthSearchFilter(
232                 ldapServerId, companyId, emailAddress, screenName,
233                 String.valueOf(userId));
234 
235             SearchControls cons = new SearchControls(
236                 SearchControls.SUBTREE_SCOPE, 1, 0, null, false, false);
237 
238             NamingEnumeration<SearchResult> enu = ctx.search(
239                 baseDN, filter, cons);
240 
241             if (enu.hasMoreElements()) {
242                 if (_log.isDebugEnabled()) {
243                     _log.debug("Search filter returned at least one result");
244                 }
245 
246                 SearchResult result = enu.nextElement();
247 
248                 String fullUserDN = PortalLDAPUtil.getNameInNamespace(
249                     ldapServerId, companyId, result);
250 
251                 Attributes attrs = PortalLDAPUtil.getUserAttributes(
252                     ldapServerId, companyId, ctx, fullUserDN);
253 
254                 LDAPAuthResult ldapAuthResult = authenticate(
255                     ctx, companyId, attrs, fullUserDN, password);
256 
257                 // Process LDAP failure codes
258 
259                 String errorMessage = ldapAuthResult.getErrorMessage();
260 
261                 if (errorMessage != null) {
262                     if (errorMessage.indexOf(PrefsPropsUtil.getString(
263                             companyId, PropsKeys.LDAP_ERROR_USER_LOCKOUT))
264                                 != -1) {
265 
266                         throw new UserLockoutException();
267                     }
268                     else if (errorMessage.indexOf(PrefsPropsUtil.getString(
269                         companyId, PropsKeys.LDAP_ERROR_PASSWORD_EXPIRED))
270                             != -1) {
271 
272                         throw new PasswordExpiredException();
273                     }
274                 }
275 
276                 if (!ldapAuthResult.isAuthenticated()) {
277                     return FAILURE;
278                 }
279 
280                 // Get user or create from LDAP
281 
282                 User user = PortalLDAPImporterUtil.importLDAPUser(
283                     ldapServerId, companyId, ctx, attrs, password, true);
284 
285                 // Process LDAP success codes
286 
287                 String resultCode = ldapAuthResult.getResponseControl();
288 
289                 if (resultCode.equals(LDAPAuth.RESULT_PASSWORD_RESET)) {
290                     UserLocalServiceUtil.updatePasswordReset(
291                         user.getUserId(), true);
292                 }
293                 else if (
294                     resultCode.equals(LDAPAuth.RESULT_PASSWORD_EXP_WARNING)) {
295 
296                     UserLocalServiceUtil.updatePasswordReset(
297                         user.getUserId(), true);
298                 }
299             }
300             else {
301                 if (_log.isDebugEnabled()) {
302                     _log.debug("Search filter did not return any results");
303                 }
304 
305                 return DNE;
306             }
307 
308             enu.close();
309         }
310         catch (Exception e) {
311             _log.error("Problem accessing LDAP server", e);
312 
313             return FAILURE;
314         }
315         finally {
316             if (ctx != null) {
317                 ctx.close();
318             }
319         }
320 
321         return SUCCESS;
322     }
323 
324     protected int authenticate(
325             long companyId, String emailAddress, String screenName, long userId,
326             String password)
327         throws Exception {
328 
329         if (!LDAPSettingsUtil.isAuthEnabled(companyId)) {
330             if (_log.isDebugEnabled()) {
331                 _log.debug("Authenticator is not enabled");
332             }
333 
334             return SUCCESS;
335         }
336 
337         if (_log.isDebugEnabled()) {
338             _log.debug("Authenticator is enabled");
339         }
340 
341         long[] ldapServerIds = StringUtil.split(
342             PrefsPropsUtil.getString(companyId, "ldap.server.ids"), 0L);
343 
344         if (ldapServerIds.length <= 0) {
345             ldapServerIds = new long[] {0};
346         }
347 
348         for (long ldapServerId : ldapServerIds) {
349             int result = authenticate(
350                 companyId, ldapServerId, emailAddress, screenName, userId,
351                 password);
352 
353             if (result == SUCCESS) {
354                 return result;
355             }
356         }
357 
358         return authenticateRequired(
359             companyId, userId, emailAddress, screenName, true, FAILURE);
360     }
361 
362     protected int authenticateOmniadmin(
363             long companyId, String emailAddress, String screenName, long userId)
364         throws Exception {
365 
366         // Only allow omniadmin if Liferay password checking is enabled
367 
368         if (PropsValues.AUTH_PIPELINE_ENABLE_LIFERAY_CHECK) {
369             if (userId > 0) {
370                 if (OmniadminUtil.isOmniadmin(userId)) {
371                     return SUCCESS;
372                 }
373             }
374             else if (Validator.isNotNull(emailAddress)) {
375                 try {
376                     User user = UserLocalServiceUtil.getUserByEmailAddress(
377                         companyId, emailAddress);
378 
379                     if (OmniadminUtil.isOmniadmin(user.getUserId())) {
380                         return SUCCESS;
381                     }
382                 }
383                 catch (NoSuchUserException nsue) {
384                 }
385             }
386             else if (Validator.isNotNull(screenName)) {
387                 try {
388                     User user = UserLocalServiceUtil.getUserByScreenName(
389                         companyId, screenName);
390 
391                     if (OmniadminUtil.isOmniadmin(user.getUserId())) {
392                         return SUCCESS;
393                     }
394                 }
395                 catch (NoSuchUserException nsue) {
396                 }
397             }
398         }
399 
400         return FAILURE;
401     }
402 
403     protected int authenticateRequired(
404             long companyId, long userId, String emailAddress, String screenName,
405             boolean allowOmniadmin, int failureCode)
406         throws Exception {
407 
408         // Make exceptions for omniadmins so that if they break the LDAP
409         // configuration, they can still login to fix the problem
410 
411         if (allowOmniadmin &&
412             (authenticateOmniadmin(
413                 companyId, emailAddress, screenName, userId) == SUCCESS)) {
414 
415             return SUCCESS;
416         }
417 
418         if (PrefsPropsUtil.getBoolean(
419                 companyId, PropsKeys.LDAP_AUTH_REQUIRED)) {
420 
421             return failureCode;
422         }
423         else {
424             return SUCCESS;
425         }
426     }
427 
428     private static Log _log = LogFactoryUtil.getLog(LDAPAuth.class);
429 
430 }