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