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