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