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