1   /**
2    * Copyright (c) 2000-2009 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   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
12   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
13   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
14   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
15   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
16   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
17   * SOFTWARE.
18   */
19  
20  package com.liferay.portal.security.auth;
21  
22  import com.liferay.portal.NoSuchUserException;
23  import com.liferay.portal.PasswordExpiredException;
24  import com.liferay.portal.UserLockoutException;
25  import com.liferay.portal.kernel.log.Log;
26  import com.liferay.portal.kernel.log.LogFactoryUtil;
27  import com.liferay.portal.kernel.util.StringPool;
28  import com.liferay.portal.kernel.util.Validator;
29  import com.liferay.portal.model.User;
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.PropsKeys;
35  import com.liferay.portal.util.PropsValues;
36  import com.liferay.portlet.admin.util.OmniadminUtil;
37  
38  import java.util.Hashtable;
39  import java.util.Map;
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   */
58  public class LDAPAuth implements Authenticator {
59  
60      public static final String AUTH_METHOD_BIND = "bind";
61  
62      public static final String AUTH_METHOD_PASSWORD_COMPARE =
63          "password-compare";
64  
65      public static final String RESULT_PASSWORD_RESET =
66          "2.16.840.1.113730.3.4.4";
67  
68      public static final String RESULT_PASSWORD_EXP_WARNING =
69          "2.16.840.1.113730.3.4.5";
70  
71      public int authenticateByEmailAddress(
72              long companyId, String emailAddress, String password,
73              Map<String, String[]> headerMap, Map<String, String[]> parameterMap)
74          throws AuthException {
75  
76          try {
77              return authenticate(
78                  companyId, emailAddress, StringPool.BLANK, 0, password);
79          }
80          catch (Exception e) {
81              _log.error(e, e);
82  
83              throw new AuthException(e);
84          }
85      }
86  
87      public int authenticateByScreenName(
88              long companyId, String screenName, String password,
89              Map<String, String[]> headerMap, Map<String, String[]> parameterMap)
90          throws AuthException {
91  
92          try {
93              return authenticate(
94                  companyId, StringPool.BLANK, screenName, 0, password);
95          }
96          catch (Exception e) {
97              _log.error(e, e);
98  
99              throw new AuthException(e);
100         }
101     }
102 
103     public int authenticateByUserId(
104             long companyId, long userId, String password,
105             Map<String, String[]> headerMap, Map<String, String[]> parameterMap)
106         throws AuthException {
107 
108         try {
109             return authenticate(
110                 companyId, StringPool.BLANK, StringPool.BLANK, userId,
111                 password);
112         }
113         catch (Exception e) {
114             _log.error(e, e);
115 
116             throw new AuthException(e);
117         }
118     }
119 
120     protected int authenticate(
121             long companyId, String emailAddress, String screenName, long userId,
122             String password)
123         throws Exception {
124 
125         if (!PortalLDAPUtil.isAuthEnabled(companyId)) {
126             if (_log.isDebugEnabled()) {
127                 _log.debug("Authenticator is not enabled");
128             }
129 
130             return SUCCESS;
131         }
132 
133         if (_log.isDebugEnabled()) {
134             _log.debug("Authenticator is enabled");
135         }
136 
137         // Make exceptions for omniadmins so that if they break the LDAP
138         // configuration, they can still login to fix the problem
139 
140         if (authenticateOmniadmin(
141                 companyId, emailAddress, screenName, userId) == SUCCESS) {
142 
143             return SUCCESS;
144         }
145 
146         String baseDN = PrefsPropsUtil.getString(
147             companyId, PropsKeys.LDAP_BASE_DN);
148 
149         LdapContext ctx = PortalLDAPUtil.getContext(companyId);
150 
151         if (ctx == null) {
152             return authenticateRequired(
153                 companyId, userId, emailAddress, FAILURE);
154         }
155 
156         //  Process LDAP auth search filter
157 
158         String filter = PortalLDAPUtil.getAuthSearchFilter(
159             companyId, emailAddress, screenName, String.valueOf(userId));
160 
161         try {
162             SearchControls cons = new SearchControls(
163                 SearchControls.SUBTREE_SCOPE, 1, 0, null, false, false);
164 
165             NamingEnumeration<SearchResult> enu = ctx.search(
166                 baseDN, filter, cons);
167 
168             if (enu.hasMoreElements()) {
169                 if (_log.isDebugEnabled()) {
170                     _log.debug("Search filter returned at least one result");
171                 }
172 
173                 SearchResult result = enu.nextElement();
174 
175                 String fullUserDN = PortalLDAPUtil.getNameInNamespace(
176                     companyId, result);
177 
178                 Attributes attrs = PortalLDAPUtil.getUserAttributes(
179                     companyId, ctx, fullUserDN);
180 
181                 LDAPAuthResult ldapAuthResult = authenticate(
182                     ctx, companyId, attrs, fullUserDN, password);
183 
184                 // Process LDAP failure codes
185 
186                 String errorMessage = ldapAuthResult.getErrorMessage();
187 
188                 if (errorMessage != null) {
189                     if (errorMessage.indexOf(PrefsPropsUtil.getString(
190                             companyId, PropsKeys.LDAP_ERROR_USER_LOCKOUT))
191                                 != -1) {
192 
193                         throw new UserLockoutException();
194                     }
195                     else if (errorMessage.indexOf(PrefsPropsUtil.getString(
196                         companyId, PropsKeys.LDAP_ERROR_PASSWORD_EXPIRED))
197                             != -1) {
198 
199                         throw new PasswordExpiredException();
200                     }
201                 }
202 
203                 if (!ldapAuthResult.isAuthenticated()) {
204                     return authenticateRequired(
205                         companyId, userId, emailAddress, FAILURE);
206                 }
207 
208                 // Get user or create from LDAP
209 
210                 User user = PortalLDAPUtil.importLDAPUser(
211                     companyId, ctx, attrs, password, true);
212 
213                 // Process LDAP success codes
214 
215                 String resultCode = ldapAuthResult.getResponseControl();
216 
217                 if (resultCode.equals(LDAPAuth.RESULT_PASSWORD_RESET)) {
218                     UserLocalServiceUtil.updatePasswordReset(
219                         user.getUserId(), true);
220                 }
221                 else if (
222                     resultCode.equals(LDAPAuth.RESULT_PASSWORD_EXP_WARNING)) {
223 
224                     UserLocalServiceUtil.updatePasswordReset(
225                         user.getUserId(), true);
226                 }
227             }
228             else {
229                 if (_log.isDebugEnabled()) {
230                     _log.debug("Search filter did not return any results");
231                 }
232 
233                 return authenticateRequired(
234                     companyId, userId, emailAddress, DNE);
235             }
236 
237             enu.close();
238         }
239         catch (Exception e) {
240             _log.error("Problem accessing LDAP server: " + e.getMessage());
241 
242             if (authenticateRequired(
243                     companyId, userId, emailAddress, FAILURE) == FAILURE) {
244 
245                 throw e;
246             }
247         }
248         finally {
249             if (ctx != null) {
250                 ctx.close();
251             }
252         }
253 
254         return SUCCESS;
255     }
256 
257     protected LDAPAuthResult authenticate(
258             LdapContext ctx, long companyId, Attributes attrs, String userDN,
259             String password)
260         throws Exception {
261 
262         LDAPAuthResult ldapAuthResult = new LDAPAuthResult();
263 
264         // Check passwords by either doing a comparison between the passwords or
265         // by binding to the LDAP server. If using LDAP password policies, bind
266         // auth method must be used in order to get the result control codes.
267 
268         String authMethod = PrefsPropsUtil.getString(
269             companyId, PropsKeys.LDAP_AUTH_METHOD);
270         InitialLdapContext innerCtx = null;
271 
272         if (authMethod.equals(AUTH_METHOD_BIND)) {
273             try {
274                 Hashtable<String, Object> env =
275                     (Hashtable<String, Object>)ctx.getEnvironment();
276 
277                 env.put(Context.SECURITY_PRINCIPAL, userDN);
278                 env.put(Context.SECURITY_CREDENTIALS, password);
279                 env.put(
280                     Context.REFERRAL,
281                     PrefsPropsUtil.getString(
282                         companyId, PropsKeys.LDAP_REFERRAL));
283 
284                 // Do not use pooling because principal changes
285 
286                 env.put("com.sun.jndi.ldap.connect.pool", "false");
287 
288                 innerCtx = new InitialLdapContext(env, null);
289 
290                 // Get LDAP bind results
291 
292                 Control[] responseControls =  innerCtx.getResponseControls();
293 
294                 ldapAuthResult.setAuthenticated(true);
295                 ldapAuthResult.setResponseControl(responseControls);
296             }
297             catch (Exception e) {
298                 if (_log.isDebugEnabled()) {
299                     _log.debug(
300                         "Failed to bind to the LDAP server with userDN "
301                             + userDN + " and password " + password);
302                 }
303 
304                 _log.error(
305                     "Failed to bind to the LDAP server: " + e.getMessage());
306 
307                 ldapAuthResult.setAuthenticated(false);
308                 ldapAuthResult.setErrorMessage(e.getMessage());
309             }
310             finally {
311                 if (innerCtx != null) {
312                     innerCtx.close();
313                 }
314             }
315         }
316         else if (authMethod.equals(AUTH_METHOD_PASSWORD_COMPARE)) {
317             Attribute userPassword = attrs.get("userPassword");
318 
319             if (userPassword != null) {
320                 String ldapPassword = new String((byte[])userPassword.get());
321 
322                 String encryptedPassword = password;
323 
324                 String algorithm = PrefsPropsUtil.getString(
325                     companyId,
326                     PropsKeys.LDAP_AUTH_PASSWORD_ENCRYPTION_ALGORITHM);
327 
328                 if (Validator.isNotNull(algorithm)) {
329                     encryptedPassword =
330                         "{" + algorithm + "}" +
331                             PwdEncryptor.encrypt(
332                                 algorithm, password, ldapPassword);
333                 }
334 
335                 if (ldapPassword.equals(encryptedPassword)) {
336                     ldapAuthResult.setAuthenticated(true);
337                 }
338                 else {
339                     ldapAuthResult.setAuthenticated(false);
340 
341                     if (_log.isWarnEnabled()) {
342                         _log.warn(
343                             "Passwords do not match for userDN " + userDN);
344                     }
345                 }
346             }
347         }
348 
349         return ldapAuthResult;
350     }
351 
352     protected int authenticateOmniadmin(
353             long companyId, String emailAddress, String screenName, long userId)
354         throws Exception {
355 
356         // Only allow omniadmin if Liferay password checking is enabled
357 
358         if (PropsValues.AUTH_PIPELINE_ENABLE_LIFERAY_CHECK) {
359             if (userId > 0) {
360                 if (OmniadminUtil.isOmniadmin(userId)) {
361                     return SUCCESS;
362                 }
363             }
364             else if (Validator.isNotNull(emailAddress)) {
365                 try {
366                     User user = UserLocalServiceUtil.getUserByEmailAddress(
367                         companyId, emailAddress);
368 
369                     if (OmniadminUtil.isOmniadmin(user.getUserId())) {
370                         return SUCCESS;
371                     }
372                 }
373                 catch (NoSuchUserException nsue) {
374                 }
375             }
376             else if (Validator.isNotNull(screenName)) {
377                 try {
378                     User user = UserLocalServiceUtil.getUserByScreenName(
379                         companyId, screenName);
380 
381                     if (OmniadminUtil.isOmniadmin(user.getUserId())) {
382                         return SUCCESS;
383                     }
384                 }
385                 catch (NoSuchUserException nsue) {
386                 }
387             }
388         }
389 
390         return FAILURE;
391     }
392 
393     protected int authenticateRequired(
394             long companyId, long userId, String emailAddress, int failureCode)
395         throws Exception {
396 
397         if (PrefsPropsUtil.getBoolean(
398                 companyId, PropsKeys.LDAP_AUTH_REQUIRED)) {
399 
400             return failureCode;
401         }
402         else {
403             return SUCCESS;
404         }
405     }
406 
407     private static Log _log = LogFactoryUtil.getLog(LDAPAuth.class);
408 
409 }