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