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                                                    e);
167                                    }
168    
169                                    ldapAuthResult.setAuthenticated(false);
170                                    ldapAuthResult.setErrorMessage(e.getMessage());
171                            }
172                            finally {
173                                    if (innerCtx != null) {
174                                            innerCtx.close();
175                                    }
176                            }
177                    }
178                    else if (authMethod.equals(AUTH_METHOD_PASSWORD_COMPARE)) {
179                            Attribute userPassword = attributes.get("userPassword");
180    
181                            if (userPassword != null) {
182                                    String ldapPassword = new String((byte[])userPassword.get());
183    
184                                    String encryptedPassword = password;
185    
186                                    String algorithm = PrefsPropsUtil.getString(
187                                            companyId,
188                                            PropsKeys.LDAP_AUTH_PASSWORD_ENCRYPTION_ALGORITHM);
189    
190                                    if (Validator.isNotNull(algorithm)) {
191                                            encryptedPassword = PasswordEncryptorUtil.encrypt(
192                                                    algorithm, password, ldapPassword);
193                                    }
194    
195                                    if (ldapPassword.equals(encryptedPassword)) {
196                                            ldapAuthResult.setAuthenticated(true);
197                                    }
198                                    else {
199                                            ldapAuthResult.setAuthenticated(false);
200    
201                                            if (_log.isDebugEnabled()) {
202                                                    _log.debug(
203                                                            "Passwords do not match for userDN " + userDN);
204                                            }
205                                    }
206                            }
207                    }
208    
209                    return ldapAuthResult;
210            }
211    
212            protected int authenticate(
213                            long ldapServerId, long companyId, String emailAddress,
214                            String screenName, long userId, String password)
215                    throws Exception {
216    
217                    String postfix = LDAPSettingsUtil.getPropertyPostfix(ldapServerId);
218    
219                    LdapContext ldapContext = PortalLDAPUtil.getContext(
220                            ldapServerId, companyId);
221    
222                    if (ldapContext == null) {
223                            return FAILURE;
224                    }
225    
226                    NamingEnumeration<SearchResult> enu = null;
227    
228                    try {
229                            String baseDN = PrefsPropsUtil.getString(
230                                    companyId, PropsKeys.LDAP_BASE_DN + postfix);
231    
232                            //  Process LDAP auth search filter
233    
234                            String filter = LDAPSettingsUtil.getAuthSearchFilter(
235                                    ldapServerId, companyId, emailAddress, screenName,
236                                    String.valueOf(userId));
237    
238                            Properties userMappings = LDAPSettingsUtil.getUserMappings(
239                                    ldapServerId, companyId);
240    
241                            String userMappingsScreenName = GetterUtil.getString(
242                                    userMappings.getProperty("screenName"));
243    
244                            userMappingsScreenName = StringUtil.toLowerCase(
245                                    userMappingsScreenName);
246    
247                            SearchControls searchControls = new SearchControls(
248                                    SearchControls.SUBTREE_SCOPE, 1, 0,
249                                    new String[] {userMappingsScreenName}, false, false);
250    
251                            enu = ldapContext.search(baseDN, filter, searchControls);
252    
253                            if (enu.hasMoreElements()) {
254                                    if (_log.isDebugEnabled()) {
255                                            _log.debug("Search filter returned at least one result");
256                                    }
257    
258                                    SearchResult result = enu.nextElement();
259    
260                                    String fullUserDN = PortalLDAPUtil.getNameInNamespace(
261                                            ldapServerId, companyId, result);
262    
263                                    Attributes attributes = PortalLDAPUtil.getUserAttributes(
264                                            ldapServerId, companyId, ldapContext, fullUserDN);
265    
266                                    LDAPAuthResult ldapAuthResult = authenticate(
267                                            ldapContext, companyId, attributes, fullUserDN, password);
268    
269                                    // Process LDAP failure codes
270    
271                                    String errorMessage = ldapAuthResult.getErrorMessage();
272    
273                                    if (errorMessage != null) {
274                                            int pos = errorMessage.indexOf(
275                                                    PrefsPropsUtil.getString(
276                                                            companyId, PropsKeys.LDAP_ERROR_USER_LOCKOUT));
277    
278                                            if (pos != -1) {
279                                                    throw new UserLockoutException();
280                                            }
281    
282                                            pos = errorMessage.indexOf(
283                                                    PrefsPropsUtil.getString(
284                                                            companyId, PropsKeys.LDAP_ERROR_PASSWORD_EXPIRED));
285    
286                                            if (pos != -1) {
287                                                    throw new PasswordExpiredException();
288                                            }
289                                    }
290    
291                                    if (!ldapAuthResult.isAuthenticated()) {
292                                            return FAILURE;
293                                    }
294    
295                                    // Get user or create from LDAP
296    
297                                    User user = PortalLDAPImporterUtil.importLDAPUser(
298                                            ldapServerId, companyId, ldapContext, attributes, password);
299    
300                                    // Process LDAP success codes
301    
302                                    String resultCode = ldapAuthResult.getResponseControl();
303    
304                                    if (resultCode.equals(LDAPAuth.RESULT_PASSWORD_RESET)) {
305                                            UserLocalServiceUtil.updatePasswordReset(
306                                                    user.getUserId(), true);
307                                    }
308                            }
309                            else {
310                                    if (_log.isDebugEnabled()) {
311                                            _log.debug("Search filter did not return any results");
312                                    }
313    
314                                    return DNE;
315                            }
316                    }
317                    catch (Exception e) {
318                            if (e instanceof PasswordExpiredException ||
319                                    e instanceof UserLockoutException) {
320    
321                                    throw e;
322                            }
323    
324                            _log.error("Problem accessing LDAP server", e);
325    
326                            return FAILURE;
327                    }
328                    finally {
329                            if (enu != null) {
330                                    enu.close();
331                            }
332    
333                            if (ldapContext != null) {
334                                    ldapContext.close();
335                            }
336                    }
337    
338                    return SUCCESS;
339            }
340    
341            protected int authenticate(
342                            long companyId, String emailAddress, String screenName, long userId,
343                            String password)
344                    throws Exception {
345    
346                    if (!AuthSettingsUtil.isLDAPAuthEnabled(companyId)) {
347                            if (_log.isDebugEnabled()) {
348                                    _log.debug("Authenticator is not enabled");
349                            }
350    
351                            return SUCCESS;
352                    }
353    
354                    if (_log.isDebugEnabled()) {
355                            _log.debug("Authenticator is enabled");
356                    }
357    
358                    int preferredLDAPServerResult = authenticateAgainstPreferredLDAPServer(
359                            companyId, emailAddress, screenName, userId, password);
360    
361                    if (preferredLDAPServerResult == SUCCESS) {
362                            if (PrefsPropsUtil.getBoolean(
363                                            companyId, PropsKeys.LDAP_IMPORT_USER_PASSWORD_ENABLED)) {
364    
365                                    return preferredLDAPServerResult;
366                            }
367    
368                            return Authenticator.SKIP_LIFERAY_CHECK;
369                    }
370    
371                    long[] ldapServerIds = StringUtil.split(
372                            PrefsPropsUtil.getString(companyId, "ldap.server.ids"), 0L);
373    
374                    for (long ldapServerId : ldapServerIds) {
375                            int result = authenticate(
376                                    ldapServerId, companyId, emailAddress, screenName, userId,
377                                    password);
378    
379                            if (result == SUCCESS) {
380                                    if (PrefsPropsUtil.getBoolean(
381                                                    companyId,
382                                                    PropsKeys.LDAP_IMPORT_USER_PASSWORD_ENABLED)) {
383    
384                                            return result;
385                                    }
386    
387                                    return Authenticator.SKIP_LIFERAY_CHECK;
388                            }
389                    }
390    
391                    for (int ldapServerId = 0;; ldapServerId++) {
392                            String postfix = LDAPSettingsUtil.getPropertyPostfix(ldapServerId);
393    
394                            String providerUrl = PrefsPropsUtil.getString(
395                                    companyId, PropsKeys.LDAP_BASE_PROVIDER_URL + postfix);
396    
397                            if (Validator.isNull(providerUrl)) {
398                                    break;
399                            }
400    
401                            int result = authenticate(
402                                    ldapServerId, companyId, emailAddress, screenName, userId,
403                                    password);
404    
405                            if (result == SUCCESS) {
406                                    if (PrefsPropsUtil.getBoolean(
407                                                    companyId,
408                                                    PropsKeys.LDAP_IMPORT_USER_PASSWORD_ENABLED)) {
409    
410                                            return result;
411                                    }
412    
413                                    return Authenticator.SKIP_LIFERAY_CHECK;
414                            }
415                    }
416    
417                    return authenticateRequired(
418                            companyId, userId, emailAddress, screenName, true, FAILURE);
419            }
420    
421            protected int authenticateAgainstPreferredLDAPServer(
422                            long companyId, String emailAddress, String screenName, long userId,
423                            String password)
424                    throws Exception {
425    
426                    int result = DNE;
427    
428                    User user = null;
429    
430                    try {
431                            if (userId > 0) {
432                                    user = UserLocalServiceUtil.getUserById(companyId, userId);
433                            }
434                            else if (Validator.isNotNull(emailAddress)) {
435                                    user = UserLocalServiceUtil.getUserByEmailAddress(
436                                            companyId, emailAddress);
437                            }
438                            else if (Validator.isNotNull(screenName)) {
439                                    user = UserLocalServiceUtil.getUserByScreenName(
440                                            companyId, screenName);
441                            }
442                            else {
443                                    if (_log.isDebugEnabled()) {
444                                            _log.debug("Unable to get preferred LDAP server");
445                                    }
446    
447                                    return result;
448                            }
449                    }
450                    catch (NoSuchUserException nsue) {
451                            if (_log.isDebugEnabled()) {
452                                    _log.debug("Unable to get preferred LDAP server", nsue);
453                            }
454    
455                            return result;
456                    }
457    
458                    long ldapServerId = user.getLdapServerId();
459    
460                    if (ldapServerId < 0) {
461                            return result;
462                    }
463    
464                    String postfix = LDAPSettingsUtil.getPropertyPostfix(ldapServerId);
465    
466                    String providerUrl = PrefsPropsUtil.getString(
467                            user.getCompanyId(), PropsKeys.LDAP_BASE_PROVIDER_URL + postfix);
468    
469                    if (Validator.isNull(providerUrl)) {
470                            return result;
471                    }
472    
473                    if (_log.isDebugEnabled()) {
474                            _log.debug(
475                                    "Using LDAP server ID " + ldapServerId +
476                                            " to authenticate user " + user.getUserId());
477                    }
478    
479                    result = authenticate(
480                            ldapServerId, companyId, emailAddress, screenName, userId,
481                            password);
482    
483                    return result;
484            }
485    
486            protected int authenticateOmniadmin(
487                            long companyId, String emailAddress, String screenName, long userId)
488                    throws Exception {
489    
490                    // Only allow omniadmin if Liferay password checking is enabled
491    
492                    if (!PropsValues.AUTH_PIPELINE_ENABLE_LIFERAY_CHECK) {
493                            return FAILURE;
494                    }
495    
496                    if (userId > 0) {
497                            if (OmniadminUtil.isOmniadmin(userId)) {
498                                    return SUCCESS;
499                            }
500                    }
501                    else if (Validator.isNotNull(emailAddress)) {
502                            User user = UserLocalServiceUtil.fetchUserByEmailAddress(
503                                    companyId, emailAddress);
504    
505                            if (user != null) {
506                                    if (OmniadminUtil.isOmniadmin(user)) {
507                                            return SUCCESS;
508                                    }
509                            }
510                    }
511                    else if (Validator.isNotNull(screenName)) {
512                            User user = UserLocalServiceUtil.fetchUserByScreenName(
513                                    companyId, screenName);
514    
515                            if (user != null) {
516                                    if (OmniadminUtil.isOmniadmin(user)) {
517                                            return SUCCESS;
518                                    }
519                            }
520                    }
521    
522                    return FAILURE;
523            }
524    
525            protected int authenticateRequired(
526                            long companyId, long userId, String emailAddress, String screenName,
527                            boolean allowOmniadmin, int failureCode)
528                    throws Exception {
529    
530                    // Make exceptions for omniadmins so that if they break the LDAP
531                    // configuration, they can still login to fix the problem
532    
533                    if (allowOmniadmin &&
534                            (authenticateOmniadmin(
535                                    companyId, emailAddress, screenName, userId) == SUCCESS)) {
536    
537                            return SUCCESS;
538                    }
539    
540                    if (PrefsPropsUtil.getBoolean(
541                                    companyId, PropsKeys.LDAP_AUTH_REQUIRED)) {
542    
543                            return failureCode;
544                    }
545                    else {
546                            return SUCCESS;
547                    }
548            }
549    
550            private static Log _log = LogFactoryUtil.getLog(LDAPAuth.class);
551    
552    }