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