001    /**
002     * Copyright (c) 2000-2012 Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
014    
015    package com.liferay.portal.security.auth;
016    
017    import com.liferay.portal.NoSuchUserException;
018    import com.liferay.portal.PasswordExpiredException;
019    import com.liferay.portal.UserLockoutException;
020    import com.liferay.portal.kernel.log.Log;
021    import com.liferay.portal.kernel.log.LogFactoryUtil;
022    import com.liferay.portal.kernel.util.GetterUtil;
023    import com.liferay.portal.kernel.util.PropsKeys;
024    import com.liferay.portal.kernel.util.StringPool;
025    import com.liferay.portal.kernel.util.StringUtil;
026    import com.liferay.portal.kernel.util.Validator;
027    import com.liferay.portal.model.User;
028    import com.liferay.portal.security.ldap.LDAPSettingsUtil;
029    import com.liferay.portal.security.ldap.PortalLDAPImporterUtil;
030    import com.liferay.portal.security.ldap.PortalLDAPUtil;
031    import com.liferay.portal.security.pwd.PwdEncryptor;
032    import com.liferay.portal.service.UserLocalServiceUtil;
033    import com.liferay.portal.util.PrefsPropsUtil;
034    import com.liferay.portal.util.PropsValues;
035    import com.liferay.portlet.admin.util.OmniadminUtil;
036    
037    import java.util.Hashtable;
038    import java.util.Map;
039    import java.util.Properties;
040    
041    import javax.naming.Context;
042    import javax.naming.NamingEnumeration;
043    import javax.naming.directory.Attribute;
044    import javax.naming.directory.Attributes;
045    import javax.naming.directory.SearchControls;
046    import javax.naming.directory.SearchResult;
047    import javax.naming.ldap.Control;
048    import javax.naming.ldap.InitialLdapContext;
049    import javax.naming.ldap.LdapContext;
050    
051    /**
052     * @author Brian Wing Shun Chan
053     * @author Scott Lee
054     */
055    public class LDAPAuth implements Authenticator {
056    
057            public static final String AUTH_METHOD_BIND = "bind";
058    
059            public static final String AUTH_METHOD_PASSWORD_COMPARE =
060                    "password-compare";
061    
062            public static final String RESULT_PASSWORD_EXP_WARNING =
063                    "2.16.840.1.113730.3.4.5";
064    
065            public static final String RESULT_PASSWORD_RESET =
066                    "2.16.840.1.113730.3.4.4";
067    
068            public int authenticateByEmailAddress(
069                            long companyId, String emailAddress, String password,
070                            Map<String, String[]> headerMap, Map<String, String[]> parameterMap)
071                    throws AuthException {
072    
073                    try {
074                            return authenticate(
075                                    companyId, emailAddress, StringPool.BLANK, 0, password);
076                    }
077                    catch (Exception e) {
078                            _log.error(e, e);
079    
080                            throw new AuthException(e);
081                    }
082            }
083    
084            public int authenticateByScreenName(
085                            long companyId, String screenName, String password,
086                            Map<String, String[]> headerMap, Map<String, String[]> parameterMap)
087                    throws AuthException {
088    
089                    try {
090                            return authenticate(
091                                    companyId, StringPool.BLANK, screenName, 0, password);
092                    }
093                    catch (Exception e) {
094                            _log.error(e, e);
095    
096                            throw new AuthException(e);
097                    }
098            }
099    
100            public int authenticateByUserId(
101                            long companyId, long userId, String password,
102                            Map<String, String[]> headerMap, Map<String, String[]> parameterMap)
103                    throws AuthException {
104    
105                    try {
106                            return authenticate(
107                                    companyId, StringPool.BLANK, StringPool.BLANK, userId,
108                                    password);
109                    }
110                    catch (Exception e) {
111                            _log.error(e, e);
112    
113                            throw new AuthException(e);
114                    }
115            }
116    
117            protected LDAPAuthResult authenticate(
118                            LdapContext ctx, long companyId, Attributes attributes,
119                            String userDN, String password)
120                    throws Exception {
121    
122                    LDAPAuthResult ldapAuthResult = new LDAPAuthResult();
123    
124                    // Check passwords by either doing a comparison between the passwords or
125                    // by binding to the LDAP server. If using LDAP password policies, bind
126                    // auth method must be used in order to get the result control codes.
127    
128                    String authMethod = PrefsPropsUtil.getString(
129                            companyId, PropsKeys.LDAP_AUTH_METHOD);
130                    InitialLdapContext innerCtx = null;
131    
132                    if (authMethod.equals(AUTH_METHOD_BIND)) {
133                            try {
134                                    Hashtable<String, Object> env =
135                                            (Hashtable<String, Object>)ctx.getEnvironment();
136    
137                                    env.put(Context.SECURITY_PRINCIPAL, userDN);
138                                    env.put(Context.SECURITY_CREDENTIALS, password);
139                                    env.put(
140                                            Context.REFERRAL,
141                                            PrefsPropsUtil.getString(
142                                                    companyId, PropsKeys.LDAP_REFERRAL));
143    
144                                    // Do not use pooling because principal changes
145    
146                                    env.put("com.sun.jndi.ldap.connect.pool", "false");
147    
148                                    innerCtx = new InitialLdapContext(env, null);
149    
150                                    // Get LDAP bind results
151    
152                                    Control[] responseControls = innerCtx.getResponseControls();
153    
154                                    ldapAuthResult.setAuthenticated(true);
155                                    ldapAuthResult.setResponseControl(responseControls);
156                            }
157                            catch (Exception e) {
158                                    if (_log.isDebugEnabled()) {
159                                            _log.debug(
160                                                    "Failed to bind to the LDAP server with userDN "
161                                                            + userDN + " and password " + password);
162                                    }
163    
164                                    _log.error("Failed to bind to the LDAP server", e);
165    
166                                    ldapAuthResult.setAuthenticated(false);
167                                    ldapAuthResult.setErrorMessage(e.getMessage());
168                            }
169                            finally {
170                                    if (innerCtx != null) {
171                                            innerCtx.close();
172                                    }
173                            }
174                    }
175                    else if (authMethod.equals(AUTH_METHOD_PASSWORD_COMPARE)) {
176                            Attribute userPassword = attributes.get("userPassword");
177    
178                            if (userPassword != null) {
179                                    String ldapPassword = new String((byte[])userPassword.get());
180    
181                                    String encryptedPassword = password;
182    
183                                    String algorithm = PrefsPropsUtil.getString(
184                                            companyId,
185                                            PropsKeys.LDAP_AUTH_PASSWORD_ENCRYPTION_ALGORITHM);
186    
187                                    if (Validator.isNotNull(algorithm)) {
188                                            encryptedPassword =
189                                                    "{" + algorithm + "}" +
190                                                            PwdEncryptor.encrypt(
191                                                                    algorithm, password, ldapPassword);
192                                    }
193    
194                                    if (ldapPassword.equals(encryptedPassword)) {
195                                            ldapAuthResult.setAuthenticated(true);
196                                    }
197                                    else {
198                                            ldapAuthResult.setAuthenticated(false);
199    
200                                            if (_log.isWarnEnabled()) {
201                                                    _log.warn(
202                                                            "Passwords do not match for userDN " + userDN);
203                                            }
204                                    }
205                            }
206                    }
207    
208                    return ldapAuthResult;
209            }
210    
211            protected int authenticate(
212                            long companyId, long ldapServerId, String emailAddress,
213                            String screenName, long userId, String password)
214                    throws Exception {
215    
216                    String postfix = LDAPSettingsUtil.getPropertyPostfix(ldapServerId);
217    
218                    LdapContext ldapContext = PortalLDAPUtil.getContext(
219                            ldapServerId, companyId);
220    
221                    if (ldapContext == null) {
222                            return FAILURE;
223                    }
224    
225                    try {
226                            String baseDN = PrefsPropsUtil.getString(
227                                    companyId, PropsKeys.LDAP_BASE_DN + postfix);
228    
229                            //  Process LDAP auth search filter
230    
231                            String filter = LDAPSettingsUtil.getAuthSearchFilter(
232                                    ldapServerId, companyId, emailAddress, screenName,
233                                    String.valueOf(userId));
234    
235                            Properties userMappings = LDAPSettingsUtil.getUserMappings(
236                                    ldapServerId, companyId);
237    
238                            String userMappingsScreenName = GetterUtil.getString(
239                                    userMappings.getProperty("screenName")).toLowerCase();
240    
241                            SearchControls searchControls = new SearchControls(
242                                    SearchControls.SUBTREE_SCOPE, 1, 0,
243                                    new String[] {userMappingsScreenName}, false, false);
244    
245                            NamingEnumeration<SearchResult> enu = ldapContext.search(
246                                    baseDN, filter, searchControls);
247    
248                            if (enu.hasMoreElements()) {
249                                    if (_log.isDebugEnabled()) {
250                                            _log.debug("Search filter returned at least one result");
251                                    }
252    
253                                    SearchResult result = enu.nextElement();
254    
255                                    String fullUserDN = PortalLDAPUtil.getNameInNamespace(
256                                            ldapServerId, companyId, result);
257    
258                                    Attributes attributes = PortalLDAPUtil.getUserAttributes(
259                                            ldapServerId, companyId, ldapContext, fullUserDN);
260    
261                                    LDAPAuthResult ldapAuthResult = null;
262    
263                                    if (PropsValues.LDAP_IMPORT_USER_PASSWORD_ENABLED) {
264                                            ldapAuthResult = authenticate(
265                                                    ldapContext, companyId, attributes, fullUserDN,
266                                                    password);
267    
268                                            // Process LDAP failure codes
269    
270                                            String errorMessage = ldapAuthResult.getErrorMessage();
271    
272                                            if (errorMessage != null) {
273                                                    if (errorMessage.indexOf(PrefsPropsUtil.getString(
274                                                                    companyId, PropsKeys.LDAP_ERROR_USER_LOCKOUT))
275                                                                            != -1) {
276    
277                                                            throw new UserLockoutException();
278                                                    }
279                                                    else if (errorMessage.indexOf(PrefsPropsUtil.getString(
280                                                            companyId, PropsKeys.LDAP_ERROR_PASSWORD_EXPIRED))
281                                                                    != -1) {
282    
283                                                            throw new PasswordExpiredException();
284                                                    }
285                                            }
286    
287                                            if (!ldapAuthResult.isAuthenticated() &&
288                                                    PropsValues.LDAP_IMPORT_USER_PASSWORD_ENABLED) {
289    
290                                                    return FAILURE;
291                                            }
292                                    }
293    
294                                    // Get user or create from LDAP
295    
296                                    User user = PortalLDAPImporterUtil.importLDAPUser(
297                                            ldapServerId, companyId, ldapContext, attributes, password);
298    
299                                    // Process LDAP success codes
300    
301                                    if (ldapAuthResult != null) {
302                                            String resultCode = ldapAuthResult.getResponseControl();
303    
304                                            if (resultCode.equals(LDAPAuth.RESULT_PASSWORD_RESET)) {
305                                                    UserLocalServiceUtil.updatePasswordReset(
306                                                            user.getUserId(), true);
307                                            }
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                            if (e instanceof PasswordExpiredException ||
322                                    e instanceof UserLockoutException) {
323    
324                                    throw e;
325                            }
326    
327                            _log.error("Problem accessing LDAP server", e);
328    
329                            return FAILURE;
330                    }
331                    finally {
332                            if (ldapContext != null) {
333                                    ldapContext.close();
334                            }
335                    }
336    
337                    return SUCCESS;
338            }
339    
340            protected int authenticate(
341                            long companyId, String emailAddress, String screenName, long userId,
342                            String password)
343                    throws Exception {
344    
345                    if (!AuthSettingsUtil.isLDAPAuthEnabled(companyId)) {
346                            if (_log.isDebugEnabled()) {
347                                    _log.debug("Authenticator is not enabled");
348                            }
349    
350                            return SUCCESS;
351                    }
352    
353                    if (_log.isDebugEnabled()) {
354                            _log.debug("Authenticator is enabled");
355                    }
356    
357                    long[] ldapServerIds = StringUtil.split(
358                            PrefsPropsUtil.getString(companyId, "ldap.server.ids"), 0L);
359    
360                    for (long ldapServerId : ldapServerIds) {
361                            int result = authenticate(
362                                    companyId, ldapServerId, emailAddress, screenName, userId,
363                                    password);
364    
365                            if (result == SUCCESS) {
366                                    return result;
367                            }
368                    }
369    
370                    for (int ldapServerId = 0;; ldapServerId++) {
371                            String postfix = LDAPSettingsUtil.getPropertyPostfix(ldapServerId);
372    
373                            String providerUrl = PrefsPropsUtil.getString(
374                                    companyId, PropsKeys.LDAP_BASE_PROVIDER_URL + postfix);
375    
376                            if (Validator.isNull(providerUrl)) {
377                                    break;
378                            }
379    
380                            int result = authenticate(
381                                    companyId, ldapServerId, emailAddress, screenName, userId,
382                                    password);
383    
384                            if (result == SUCCESS) {
385                                    return result;
386                            }
387                    }
388    
389                    return authenticateRequired(
390                            companyId, userId, emailAddress, screenName, true, FAILURE);
391            }
392    
393            protected int authenticateOmniadmin(
394                            long companyId, String emailAddress, String screenName, long userId)
395                    throws Exception {
396    
397                    // Only allow omniadmin if Liferay password checking is enabled
398    
399                    if (PropsValues.AUTH_PIPELINE_ENABLE_LIFERAY_CHECK) {
400                            if (userId > 0) {
401                                    if (OmniadminUtil.isOmniadmin(userId)) {
402                                            return SUCCESS;
403                                    }
404                            }
405                            else if (Validator.isNotNull(emailAddress)) {
406                                    try {
407                                            User user = UserLocalServiceUtil.getUserByEmailAddress(
408                                                    companyId, emailAddress);
409    
410                                            if (OmniadminUtil.isOmniadmin(user.getUserId())) {
411                                                    return SUCCESS;
412                                            }
413                                    }
414                                    catch (NoSuchUserException nsue) {
415                                    }
416                            }
417                            else if (Validator.isNotNull(screenName)) {
418                                    try {
419                                            User user = UserLocalServiceUtil.getUserByScreenName(
420                                                    companyId, screenName);
421    
422                                            if (OmniadminUtil.isOmniadmin(user.getUserId())) {
423                                                    return SUCCESS;
424                                            }
425                                    }
426                                    catch (NoSuchUserException nsue) {
427                                    }
428                            }
429                    }
430    
431                    return FAILURE;
432            }
433    
434            protected int authenticateRequired(
435                            long companyId, long userId, String emailAddress, String screenName,
436                            boolean allowOmniadmin, int failureCode)
437                    throws Exception {
438    
439                    // Make exceptions for omniadmins so that if they break the LDAP
440                    // configuration, they can still login to fix the problem
441    
442                    if (allowOmniadmin &&
443                            (authenticateOmniadmin(
444                                    companyId, emailAddress, screenName, userId) == SUCCESS)) {
445    
446                            return SUCCESS;
447                    }
448    
449                    if (PrefsPropsUtil.getBoolean(
450                                    companyId, PropsKeys.LDAP_AUTH_REQUIRED)) {
451    
452                            return failureCode;
453                    }
454                    else {
455                            return SUCCESS;
456                    }
457            }
458    
459            private static Log _log = LogFactoryUtil.getLog(LDAPAuth.class);
460    
461    }