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 
314                 ctx = new InitialLdapContext(env, null);
315 
316                 // Get LDAP bind results
317 
318                 Control[] responseControls =  ctx.getResponseControls();
319 
320                 ldapAuthResult.setAuthenticated(true);
321                 ldapAuthResult.setResponseControl(responseControls);
322             }
323             catch (Exception e) {
324                 _log.error(
325                     "Failed to bind to the LDAP server with userDN " + userDN +
326                         " and password " + password + ": " + e.getMessage());
327 
328                 ldapAuthResult.setAuthenticated(false);
329                 ldapAuthResult.setErrorMessage(e.getMessage());
330             }
331         }
332         else if (authMethod.equals(AUTH_METHOD_PASSWORD_COMPARE)) {
333             if (userPassword != null) {
334                 String ldapPassword = new String((byte[])userPassword.get());
335 
336                 String encryptedPassword = password;
337 
338                 String algorithm = PrefsPropsUtil.getString(
339                     companyId,
340                     PropsUtil.LDAP_AUTH_PASSWORD_ENCRYPTION_ALGORITHM);
341 
342                 if (Validator.isNotNull(algorithm)) {
343                     encryptedPassword =
344                         "{" + algorithm + "}" +
345                             PwdEncryptor.encrypt(
346                                 algorithm, password, ldapPassword);
347                 }
348 
349                 if (ldapPassword.equals(encryptedPassword)) {
350                     ldapAuthResult.setAuthenticated(true);
351                 }
352                 else {
353                     ldapAuthResult.setAuthenticated(false);
354 
355                     _log.error(
356                         "LDAP password " + ldapPassword +
357                             " does not match with given password " +
358                                 encryptedPassword + " for user id " + userId);
359                 }
360             }
361         }
362 
363         return ldapAuthResult;
364     }
365 
366     protected int authenticateOmniadmin(
367             long companyId, String emailAddress, long userId)
368         throws Exception {
369 
370         // Only allow omniadmin if Liferay password checking is enabled
371 
372         if (GetterUtil.getBoolean(PropsUtil.get(
373                 PropsUtil.AUTH_PIPELINE_ENABLE_LIFERAY_CHECK))) {
374 
375             if (userId > 0) {
376                 if (OmniadminUtil.isOmniadmin(userId)) {
377                     return SUCCESS;
378                 }
379             }
380             else if (Validator.isNotNull(emailAddress)) {
381                 try {
382                     User user = UserLocalServiceUtil.getUserByEmailAddress(
383                         companyId, emailAddress);
384 
385                     if (OmniadminUtil.isOmniadmin(user.getUserId())) {
386                         return SUCCESS;
387                     }
388                 }
389                 catch (NoSuchUserException nsue) {
390                 }
391             }
392         }
393 
394         return FAILURE;
395     }
396 
397     protected int authenticateRequired(
398             long companyId, long userId, String emailAddress, int failureCode)
399         throws Exception {
400 
401         if (PrefsPropsUtil.getBoolean(
402                 companyId, PropsUtil.LDAP_AUTH_REQUIRED)) {
403 
404             return failureCode;
405         }
406         else {
407             return SUCCESS;
408         }
409     }
410 
411     private static Log _log = LogFactoryUtil.getLog(LDAPAuth.class);
412 
413 }