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