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