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.PwdEncryptor;
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     */
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                                            StringBundler sb = new StringBundler(4);
193    
194                                            sb.append(StringPool.OPEN_CURLY_BRACE);
195                                            sb.append(algorithm);
196                                            sb.append(StringPool.CLOSE_CURLY_BRACE);
197                                            sb.append(
198                                                    PwdEncryptor.encrypt(
199                                                            algorithm, password, ldapPassword));
200    
201                                            encryptedPassword = sb.toString();
202                                    }
203    
204                                    if (ldapPassword.equals(encryptedPassword)) {
205                                            ldapAuthResult.setAuthenticated(true);
206                                    }
207                                    else {
208                                            ldapAuthResult.setAuthenticated(false);
209    
210                                            if (_log.isWarnEnabled()) {
211                                                    _log.warn(
212                                                            "Passwords do not match for userDN " + userDN);
213                                            }
214                                    }
215                            }
216                    }
217    
218                    return ldapAuthResult;
219            }
220    
221            protected int authenticate(
222                            long companyId, long ldapServerId, String emailAddress,
223                            String screenName, long userId, String password)
224                    throws Exception {
225    
226                    String postfix = LDAPSettingsUtil.getPropertyPostfix(ldapServerId);
227    
228                    LdapContext ldapContext = PortalLDAPUtil.getContext(
229                            ldapServerId, companyId);
230    
231                    if (ldapContext == null) {
232                            return FAILURE;
233                    }
234    
235                    NamingEnumeration<SearchResult> enu = null;
236    
237                    try {
238                            String baseDN = PrefsPropsUtil.getString(
239                                    companyId, PropsKeys.LDAP_BASE_DN + postfix);
240    
241                            //  Process LDAP auth search filter
242    
243                            String filter = LDAPSettingsUtil.getAuthSearchFilter(
244                                    ldapServerId, companyId, emailAddress, screenName,
245                                    String.valueOf(userId));
246    
247                            Properties userMappings = LDAPSettingsUtil.getUserMappings(
248                                    ldapServerId, companyId);
249    
250                            String userMappingsScreenName = GetterUtil.getString(
251                                    userMappings.getProperty("screenName")).toLowerCase();
252    
253                            SearchControls searchControls = new SearchControls(
254                                    SearchControls.SUBTREE_SCOPE, 1, 0,
255                                    new String[] {userMappingsScreenName}, false, false);
256    
257                            enu = ldapContext.search(baseDN, filter, searchControls);
258    
259                            if (enu.hasMoreElements()) {
260                                    if (_log.isDebugEnabled()) {
261                                            _log.debug("Search filter returned at least one result");
262                                    }
263    
264                                    SearchResult result = enu.nextElement();
265    
266                                    String fullUserDN = PortalLDAPUtil.getNameInNamespace(
267                                            ldapServerId, companyId, result);
268    
269                                    Attributes attributes = PortalLDAPUtil.getUserAttributes(
270                                            ldapServerId, companyId, ldapContext, fullUserDN);
271    
272                                    LDAPAuthResult ldapAuthResult = authenticate(
273                                            ldapContext, companyId, attributes, fullUserDN, password);
274    
275                                    // Process LDAP failure codes
276    
277                                    String errorMessage = ldapAuthResult.getErrorMessage();
278    
279                                    if (errorMessage != null) {
280                                            int pos = errorMessage.indexOf(
281                                                    PrefsPropsUtil.getString(
282                                                            companyId, PropsKeys.LDAP_ERROR_USER_LOCKOUT));
283    
284                                            if (pos != -1) {
285                                                    throw new UserLockoutException();
286                                            }
287    
288                                            pos = errorMessage.indexOf(
289                                                    PrefsPropsUtil.getString(
290                                                            companyId, PropsKeys.LDAP_ERROR_PASSWORD_EXPIRED));
291    
292                                            if (pos != -1) {
293                                                    throw new PasswordExpiredException();
294                                            }
295                                    }
296    
297                                    if (!ldapAuthResult.isAuthenticated()) {
298                                            return FAILURE;
299                                    }
300    
301                                    // Get user or create from LDAP
302    
303                                    User user = PortalLDAPImporterUtil.importLDAPUser(
304                                            ldapServerId, companyId, ldapContext, attributes, password);
305    
306                                    // Process LDAP success codes
307    
308                                    String resultCode = ldapAuthResult.getResponseControl();
309    
310                                    if (resultCode.equals(LDAPAuth.RESULT_PASSWORD_RESET)) {
311                                            UserLocalServiceUtil.updatePasswordReset(
312                                                    user.getUserId(), true);
313                                    }
314                            }
315                            else {
316                                    if (_log.isDebugEnabled()) {
317                                            _log.debug("Search filter did not return any results");
318                                    }
319    
320                                    return DNE;
321                            }
322                    }
323                    catch (Exception e) {
324                            if (e instanceof PasswordExpiredException ||
325                                    e instanceof UserLockoutException) {
326    
327                                    throw e;
328                            }
329    
330                            _log.error("Problem accessing LDAP server", e);
331    
332                            return FAILURE;
333                    }
334                    finally {
335                            if (enu != null) {
336                                    enu.close();
337                            }
338    
339                            if (ldapContext != null) {
340                                    ldapContext.close();
341                            }
342                    }
343    
344                    return SUCCESS;
345            }
346    
347            protected int authenticate(
348                            long companyId, String emailAddress, String screenName, long userId,
349                            String password)
350                    throws Exception {
351    
352                    if (!AuthSettingsUtil.isLDAPAuthEnabled(companyId)) {
353                            if (_log.isDebugEnabled()) {
354                                    _log.debug("Authenticator is not enabled");
355                            }
356    
357                            return SUCCESS;
358                    }
359    
360                    if (_log.isDebugEnabled()) {
361                            _log.debug("Authenticator is enabled");
362                    }
363    
364                    long[] ldapServerIds = StringUtil.split(
365                            PrefsPropsUtil.getString(companyId, "ldap.server.ids"), 0L);
366    
367                    for (long ldapServerId : ldapServerIds) {
368                            int result = authenticate(
369                                    companyId, ldapServerId, emailAddress, screenName, userId,
370                                    password);
371    
372                            if (result == SUCCESS) {
373                                    if (PrefsPropsUtil.getBoolean(
374                                                    companyId,
375                                                    PropsKeys.LDAP_IMPORT_USER_PASSWORD_ENABLED)) {
376    
377                                            return result;
378                                    }
379    
380                                    return Authenticator.SKIP_LIFERAY_CHECK;
381                            }
382                    }
383    
384                    for (int ldapServerId = 0;; ldapServerId++) {
385                            String postfix = LDAPSettingsUtil.getPropertyPostfix(ldapServerId);
386    
387                            String providerUrl = PrefsPropsUtil.getString(
388                                    companyId, PropsKeys.LDAP_BASE_PROVIDER_URL + postfix);
389    
390                            if (Validator.isNull(providerUrl)) {
391                                    break;
392                            }
393    
394                            int result = authenticate(
395                                    companyId, ldapServerId, emailAddress, screenName, userId,
396                                    password);
397    
398                            if (result == SUCCESS) {
399                                    if (PrefsPropsUtil.getBoolean(
400                                                    companyId,
401                                                    PropsKeys.LDAP_IMPORT_USER_PASSWORD_ENABLED)) {
402    
403                                            return result;
404                                    }
405    
406                                    return Authenticator.SKIP_LIFERAY_CHECK;
407                            }
408                    }
409    
410                    return authenticateRequired(
411                            companyId, userId, emailAddress, screenName, true, FAILURE);
412            }
413    
414            protected int authenticateOmniadmin(
415                            long companyId, String emailAddress, String screenName, long userId)
416                    throws Exception {
417    
418                    // Only allow omniadmin if Liferay password checking is enabled
419    
420                    if (PropsValues.AUTH_PIPELINE_ENABLE_LIFERAY_CHECK) {
421                            if (userId > 0) {
422                                    if (OmniadminUtil.isOmniadmin(userId)) {
423                                            return SUCCESS;
424                                    }
425                            }
426                            else if (Validator.isNotNull(emailAddress)) {
427                                    try {
428                                            User user = UserLocalServiceUtil.getUserByEmailAddress(
429                                                    companyId, emailAddress);
430    
431                                            if (OmniadminUtil.isOmniadmin(user.getUserId())) {
432                                                    return SUCCESS;
433                                            }
434                                    }
435                                    catch (NoSuchUserException nsue) {
436                                    }
437                            }
438                            else if (Validator.isNotNull(screenName)) {
439                                    try {
440                                            User user = UserLocalServiceUtil.getUserByScreenName(
441                                                    companyId, screenName);
442    
443                                            if (OmniadminUtil.isOmniadmin(user.getUserId())) {
444                                                    return SUCCESS;
445                                            }
446                                    }
447                                    catch (NoSuchUserException nsue) {
448                                    }
449                            }
450                    }
451    
452                    return FAILURE;
453            }
454    
455            protected int authenticateRequired(
456                            long companyId, long userId, String emailAddress, String screenName,
457                            boolean allowOmniadmin, int failureCode)
458                    throws Exception {
459    
460                    // Make exceptions for omniadmins so that if they break the LDAP
461                    // configuration, they can still login to fix the problem
462    
463                    if (allowOmniadmin &&
464                            (authenticateOmniadmin(
465                                    companyId, emailAddress, screenName, userId) == SUCCESS)) {
466    
467                            return SUCCESS;
468                    }
469    
470                    if (PrefsPropsUtil.getBoolean(
471                                    companyId, PropsKeys.LDAP_AUTH_REQUIRED)) {
472    
473                            return failureCode;
474                    }
475                    else {
476                            return SUCCESS;
477                    }
478            }
479    
480            private static Log _log = LogFactoryUtil.getLog(LDAPAuth.class);
481    
482    }