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