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