1   /**
2    * Copyright (c) 2000-2008 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.PropsUtil;
38  import com.liferay.portal.util.PropsValues;
39  import com.liferay.portlet.admin.util.OmniadminUtil;
40  
41  import java.util.Map;
42  import java.util.Properties;
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, Map headerMap,
76              Map 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, Map headerMap,
92              Map 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, Map headerMap,
108             Map 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(companyId, emailAddress, userId) == SUCCESS) {
144             return SUCCESS;
145         }
146 
147         String baseDN = PrefsPropsUtil.getString(
148             companyId, PropsUtil.LDAP_BASE_DN);
149 
150         LdapContext ctx = PortalLDAPUtil.getContext(companyId);
151 
152         if (ctx == null) {
153             return authenticateRequired(
154                 companyId, userId, emailAddress, FAILURE);
155         }
156 
157         //  Process LDAP auth search filter
158 
159         String filter = PortalLDAPUtil.getAuthSearchFilter(
160             companyId, emailAddress, screenName, String.valueOf(userId));
161 
162         try {
163             SearchControls cons = new SearchControls(
164                 SearchControls.SUBTREE_SCOPE, 1, 0, null, false, false);
165 
166             NamingEnumeration enu = ctx.search(baseDN, filter, cons);
167 
168             if (enu.hasMore()) {
169                 if (_log.isDebugEnabled()) {
170                     _log.debug("Search filter returned at least one result");
171                 }
172 
173                 SearchResult result = (SearchResult)enu.next();
174 
175                 String fullUserDN = PortalLDAPUtil.getNameInNamespace(
176                     companyId, result);
177 
178                 Attributes attrs = PortalLDAPUtil.getAttributes(
179                     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, PropsUtil.LDAP_ERROR_USER_LOCKOUT))
191                                 != -1) {
192 
193                         throw new UserLockoutException();
194                     }
195                     else if (errorMessage.indexOf(PrefsPropsUtil.getString(
196                         companyId, PropsUtil.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         catch (Exception e) {
238             _log.error("Problem accessing LDAP server: " + e.getMessage());
239 
240             if (authenticateRequired(
241                     companyId, userId, emailAddress, FAILURE) == FAILURE) {
242 
243                 throw e;
244             }
245         }
246         finally {
247             ctx.close();
248         }
249 
250         return SUCCESS;
251     }
252 
253     protected LDAPAuthResult authenticate(
254             LdapContext ctx, long companyId, Attributes attrs, String userDN,
255             String password)
256         throws Exception {
257 
258         LDAPAuthResult ldapAuthResult = new LDAPAuthResult();
259 
260         // Check passwords by either doing a comparison between the passwords or
261         // by binding to the LDAP server. If using LDAP password policies, bind
262         // auth method must be used in order to get the result control codes.
263 
264         String authMethod = PrefsPropsUtil.getString(
265             companyId, PropsUtil.LDAP_AUTH_METHOD);
266 
267         if (authMethod.equals(AUTH_METHOD_BIND)) {
268             try {
269                 Properties env = (Properties)ctx.getEnvironment();
270 
271                 env.put(Context.SECURITY_PRINCIPAL, userDN);
272                 env.put(Context.SECURITY_CREDENTIALS, password);
273                 env.put(Context.REFERRAL, "follow");
274 
275                 ctx = new InitialLdapContext(env, null);
276 
277                 // Get LDAP bind results
278 
279                 Control[] responseControls =  ctx.getResponseControls();
280 
281                 ldapAuthResult.setAuthenticated(true);
282                 ldapAuthResult.setResponseControl(responseControls);
283             }
284             catch (Exception e) {
285                 _log.error(
286                     "Failed to bind to the LDAP server with userDN " + userDN +
287                         " and password " + password + ": " + e.getMessage());
288 
289                 ldapAuthResult.setAuthenticated(false);
290                 ldapAuthResult.setErrorMessage(e.getMessage());
291             }
292         }
293         else if (authMethod.equals(AUTH_METHOD_PASSWORD_COMPARE)) {
294             Attribute userPassword = attrs.get("userPassword");
295 
296             if (userPassword != null) {
297                 String ldapPassword = new String((byte[])userPassword.get());
298 
299                 String encryptedPassword = password;
300 
301                 String algorithm = PrefsPropsUtil.getString(
302                     companyId,
303                     PropsUtil.LDAP_AUTH_PASSWORD_ENCRYPTION_ALGORITHM);
304 
305                 if (Validator.isNotNull(algorithm)) {
306                     encryptedPassword =
307                         "{" + algorithm + "}" +
308                             PwdEncryptor.encrypt(
309                                 algorithm, password, ldapPassword);
310                 }
311 
312                 if (ldapPassword.equals(encryptedPassword)) {
313                     ldapAuthResult.setAuthenticated(true);
314                 }
315                 else {
316                     ldapAuthResult.setAuthenticated(false);
317 
318                     _log.error(
319                         "LDAP password " + ldapPassword +
320                             " does not match with given password " +
321                                 encryptedPassword + " for userDN " + userDN);
322                 }
323             }
324         }
325 
326         return ldapAuthResult;
327     }
328 
329     protected int authenticateOmniadmin(
330             long companyId, String emailAddress, long userId)
331         throws Exception {
332 
333         // Only allow omniadmin if Liferay password checking is enabled
334 
335         if (PropsValues.AUTH_PIPELINE_ENABLE_LIFERAY_CHECK) {
336             if (userId > 0) {
337                 if (OmniadminUtil.isOmniadmin(userId)) {
338                     return SUCCESS;
339                 }
340             }
341             else if (Validator.isNotNull(emailAddress)) {
342                 try {
343                     User user = UserLocalServiceUtil.getUserByEmailAddress(
344                         companyId, emailAddress);
345 
346                     if (OmniadminUtil.isOmniadmin(user.getUserId())) {
347                         return SUCCESS;
348                     }
349                 }
350                 catch (NoSuchUserException nsue) {
351                 }
352             }
353         }
354 
355         return FAILURE;
356     }
357 
358     protected int authenticateRequired(
359             long companyId, long userId, String emailAddress, int failureCode)
360         throws Exception {
361 
362         if (PrefsPropsUtil.getBoolean(
363                 companyId, PropsUtil.LDAP_AUTH_REQUIRED)) {
364 
365             return failureCode;
366         }
367         else {
368             return SUCCESS;
369         }
370     }
371 
372     private static Log _log = LogFactoryUtil.getLog(LDAPAuth.class);
373 
374 }