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