1   /**
2    * Copyright (c) 2000-2009 Liferay, Inc. All rights reserved.
3    *
4    *
5    *
6    *
7    * The contents of this file are subject to the terms of the Liferay Enterprise
8    * Subscription License ("License"). You may not use this file except in
9    * compliance with the License. You can obtain a copy of the License by
10   * contacting Liferay, Inc. See the License for the specific language governing
11   * permissions and limitations under the License, including but not limited to
12   * distribution rights of the Software.
13   *
14   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20   * SOFTWARE.
21   */
22  
23  package com.liferay.portal.security.auth;
24  
25  import com.liferay.portal.NoSuchUserException;
26  import com.liferay.portal.PasswordExpiredException;
27  import com.liferay.portal.UserLockoutException;
28  import com.liferay.portal.kernel.log.Log;
29  import com.liferay.portal.kernel.log.LogFactoryUtil;
30  import com.liferay.portal.kernel.util.PropsKeys;
31  import com.liferay.portal.kernel.util.StringPool;
32  import com.liferay.portal.kernel.util.StringUtil;
33  import com.liferay.portal.kernel.util.Validator;
34  import com.liferay.portal.model.User;
35  import com.liferay.portal.security.ldap.PortalLDAPUtil;
36  import com.liferay.portal.security.pwd.PwdEncryptor;
37  import com.liferay.portal.service.UserLocalServiceUtil;
38  import com.liferay.portal.util.PrefsPropsUtil;
39  import com.liferay.portal.util.PropsValues;
40  import com.liferay.portlet.admin.util.OmniadminUtil;
41  
42  import java.util.Hashtable;
43  import java.util.Map;
44  
45  import javax.naming.Context;
46  import javax.naming.NamingEnumeration;
47  import javax.naming.directory.Attribute;
48  import javax.naming.directory.Attributes;
49  import javax.naming.directory.SearchControls;
50  import javax.naming.directory.SearchResult;
51  import javax.naming.ldap.Control;
52  import javax.naming.ldap.InitialLdapContext;
53  import javax.naming.ldap.LdapContext;
54  
55  /**
56   * <a href="LDAPAuth.java.html"><b><i>View Source</i></b></a>
57   *
58   * @author Brian Wing Shun Chan
59   * @author Scott Lee
60   */
61  public class LDAPAuth implements Authenticator {
62  
63      public static final String AUTH_METHOD_BIND = "bind";
64  
65      public static final String AUTH_METHOD_PASSWORD_COMPARE =
66          "password-compare";
67  
68      public static final String RESULT_PASSWORD_EXP_WARNING =
69          "2.16.840.1.113730.3.4.5";
70  
71      public static final String RESULT_PASSWORD_RESET =
72          "2.16.840.1.113730.3.4.4";
73  
74      public int authenticateByEmailAddress(
75              long companyId, String emailAddress, String password,
76              Map<String, String[]> headerMap, Map<String, String[]> parameterMap)
77          throws AuthException {
78  
79          try {
80              return authenticate(
81                  companyId, emailAddress, StringPool.BLANK, 0, password);
82          }
83          catch (Exception e) {
84              _log.error(e, e);
85  
86              throw new AuthException(e);
87          }
88      }
89  
90      public int authenticateByScreenName(
91              long companyId, String screenName, String password,
92              Map<String, String[]> headerMap, Map<String, String[]> parameterMap)
93          throws AuthException {
94  
95          try {
96              return authenticate(
97                  companyId, StringPool.BLANK, screenName, 0, password);
98          }
99          catch (Exception e) {
100             _log.error(e, e);
101 
102             throw new AuthException(e);
103         }
104     }
105 
106     public int authenticateByUserId(
107             long companyId, long userId, String password,
108             Map<String, String[]> headerMap, Map<String, String[]> parameterMap)
109         throws AuthException {
110 
111         try {
112             return authenticate(
113                 companyId, StringPool.BLANK, StringPool.BLANK, userId,
114                 password);
115         }
116         catch (Exception e) {
117             _log.error(e, e);
118 
119             throw new AuthException(e);
120         }
121     }
122 
123     protected LDAPAuthResult authenticate(
124             LdapContext ctx, long companyId, Attributes attrs, String userDN,
125             String password)
126         throws Exception {
127 
128         LDAPAuthResult ldapAuthResult = new LDAPAuthResult();
129 
130         // Check passwords by either doing a comparison between the passwords or
131         // by binding to the LDAP server. If using LDAP password policies, bind
132         // auth method must be used in order to get the result control codes.
133 
134         String authMethod = PrefsPropsUtil.getString(
135             companyId, PropsKeys.LDAP_AUTH_METHOD);
136         InitialLdapContext innerCtx = null;
137 
138         if (authMethod.equals(AUTH_METHOD_BIND)) {
139             try {
140                 Hashtable<String, Object> env =
141                     (Hashtable<String, Object>)ctx.getEnvironment();
142 
143                 env.put(Context.SECURITY_PRINCIPAL, userDN);
144                 env.put(Context.SECURITY_CREDENTIALS, password);
145                 env.put(
146                     Context.REFERRAL,
147                     PrefsPropsUtil.getString(
148                         companyId, PropsKeys.LDAP_REFERRAL));
149 
150                 // Do not use pooling because principal changes
151 
152                 env.put("com.sun.jndi.ldap.connect.pool", "false");
153 
154                 innerCtx = new InitialLdapContext(env, null);
155 
156                 // Get LDAP bind results
157 
158                 Control[] responseControls =  innerCtx.getResponseControls();
159 
160                 ldapAuthResult.setAuthenticated(true);
161                 ldapAuthResult.setResponseControl(responseControls);
162             }
163             catch (Exception e) {
164                 if (_log.isDebugEnabled()) {
165                     _log.debug(
166                         "Failed to bind to the LDAP server with userDN "
167                             + userDN + " and password " + password);
168                 }
169 
170                 _log.error(
171                     "Failed to bind to the LDAP server: " + e.getMessage());
172 
173                 ldapAuthResult.setAuthenticated(false);
174                 ldapAuthResult.setErrorMessage(e.getMessage());
175             }
176             finally {
177                 if (innerCtx != null) {
178                     innerCtx.close();
179                 }
180             }
181         }
182         else if (authMethod.equals(AUTH_METHOD_PASSWORD_COMPARE)) {
183             Attribute userPassword = attrs.get("userPassword");
184 
185             if (userPassword != null) {
186                 String ldapPassword = new String((byte[])userPassword.get());
187 
188                 String encryptedPassword = password;
189 
190                 String algorithm = PrefsPropsUtil.getString(
191                     companyId,
192                     PropsKeys.LDAP_AUTH_PASSWORD_ENCRYPTION_ALGORITHM);
193 
194                 if (Validator.isNotNull(algorithm)) {
195                     encryptedPassword =
196                         "{" + algorithm + "}" +
197                             PwdEncryptor.encrypt(
198                                 algorithm, password, ldapPassword);
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 = PortalLDAPUtil.getPropertyPostfix(ldapServerId);
224 
225         LdapContext ctx = PortalLDAPUtil.getContext(ldapServerId, companyId);
226 
227         if (ctx == null) {
228             return FAILURE;
229         }
230 
231         try {
232             String baseDN = PrefsPropsUtil.getString(
233                 companyId, PropsKeys.LDAP_BASE_DN + postfix);
234 
235             //  Process LDAP auth search filter
236 
237             String filter = PortalLDAPUtil.getAuthSearchFilter(
238                 ldapServerId, companyId, emailAddress, screenName,
239                 String.valueOf(userId));
240 
241             SearchControls cons = new SearchControls(
242                 SearchControls.SUBTREE_SCOPE, 1, 0, null, false, false);
243 
244             NamingEnumeration<SearchResult> enu = ctx.search(
245                 baseDN, filter, cons);
246 
247             if (enu.hasMoreElements()) {
248                 if (_log.isDebugEnabled()) {
249                     _log.debug("Search filter returned at least one result");
250                 }
251 
252                 SearchResult result = enu.nextElement();
253 
254                 String fullUserDN = PortalLDAPUtil.getNameInNamespace(
255                     ldapServerId, companyId, result);
256 
257                 Attributes attrs = PortalLDAPUtil.getUserAttributes(
258                     ldapServerId, companyId, ctx, fullUserDN);
259 
260                 LDAPAuthResult ldapAuthResult = authenticate(
261                     ctx, companyId, attrs, fullUserDN, password);
262 
263                 // Process LDAP failure codes
264 
265                 String errorMessage = ldapAuthResult.getErrorMessage();
266 
267                 if (errorMessage != null) {
268                     if (errorMessage.indexOf(PrefsPropsUtil.getString(
269                             companyId, PropsKeys.LDAP_ERROR_USER_LOCKOUT))
270                                 != -1) {
271 
272                         throw new UserLockoutException();
273                     }
274                     else if (errorMessage.indexOf(PrefsPropsUtil.getString(
275                         companyId, PropsKeys.LDAP_ERROR_PASSWORD_EXPIRED))
276                             != -1) {
277 
278                         throw new PasswordExpiredException();
279                     }
280                 }
281 
282                 if (!ldapAuthResult.isAuthenticated()) {
283                     return FAILURE;
284                 }
285 
286                 // Get user or create from LDAP
287 
288                 User user = PortalLDAPUtil.importLDAPUser(
289                     ldapServerId, companyId, ctx, attrs, password, true);
290 
291                 // Process LDAP success codes
292 
293                 String resultCode = ldapAuthResult.getResponseControl();
294 
295                 if (resultCode.equals(LDAPAuth.RESULT_PASSWORD_RESET)) {
296                     UserLocalServiceUtil.updatePasswordReset(
297                         user.getUserId(), true);
298                 }
299                 else if (
300                     resultCode.equals(LDAPAuth.RESULT_PASSWORD_EXP_WARNING)) {
301 
302                     UserLocalServiceUtil.updatePasswordReset(
303                         user.getUserId(), true);
304                 }
305             }
306             else {
307                 if (_log.isDebugEnabled()) {
308                     _log.debug("Search filter did not return any results");
309                 }
310 
311                 return DNE;
312             }
313 
314             enu.close();
315         }
316         catch (Exception e) {
317             _log.error("Problem accessing LDAP server: " + e.getMessage());
318 
319             return FAILURE;
320         }
321         finally {
322             if (ctx != null) {
323                 ctx.close();
324             }
325         }
326 
327         return SUCCESS;
328     }
329 
330     protected int authenticate(
331             long companyId, String emailAddress, String screenName, long userId,
332             String password)
333         throws Exception {
334 
335         if (!PortalLDAPUtil.isAuthEnabled(companyId)) {
336             if (_log.isDebugEnabled()) {
337                 _log.debug("Authenticator is not enabled");
338             }
339 
340             return SUCCESS;
341         }
342 
343         if (_log.isDebugEnabled()) {
344             _log.debug("Authenticator is enabled");
345         }
346 
347         long[] ldapServerIds = StringUtil.split(
348             PrefsPropsUtil.getString(companyId, "ldap.server.ids"), 0L);
349 
350         if (ldapServerIds.length <= 0) {
351             ldapServerIds = new long[] {0};
352         }
353 
354         for (long ldapServerId : ldapServerIds) {
355             int result = authenticate(
356                 companyId, ldapServerId, emailAddress, screenName, userId,
357                 password);
358 
359             if (result == SUCCESS) {
360                 return result;
361             }
362         }
363 
364         return authenticateRequired(
365             companyId, userId, emailAddress, screenName, true, FAILURE);
366     }
367 
368     protected int authenticateOmniadmin(
369             long companyId, String emailAddress, String screenName, long userId)
370         throws Exception {
371 
372         // Only allow omniadmin if Liferay password checking is enabled
373 
374         if (PropsValues.AUTH_PIPELINE_ENABLE_LIFERAY_CHECK) {
375             if (userId > 0) {
376                 if (OmniadminUtil.isOmniadmin(userId)) {
377                     return SUCCESS;
378                 }
379             }
380             else if (Validator.isNotNull(emailAddress)) {
381                 try {
382                     User user = UserLocalServiceUtil.getUserByEmailAddress(
383                         companyId, emailAddress);
384 
385                     if (OmniadminUtil.isOmniadmin(user.getUserId())) {
386                         return SUCCESS;
387                     }
388                 }
389                 catch (NoSuchUserException nsue) {
390                 }
391             }
392             else if (Validator.isNotNull(screenName)) {
393                 try {
394                     User user = UserLocalServiceUtil.getUserByScreenName(
395                         companyId, screenName);
396 
397                     if (OmniadminUtil.isOmniadmin(user.getUserId())) {
398                         return SUCCESS;
399                     }
400                 }
401                 catch (NoSuchUserException nsue) {
402                 }
403             }
404         }
405 
406         return FAILURE;
407     }
408 
409     protected int authenticateRequired(
410             long companyId, long userId, String emailAddress, String screenName,
411             boolean allowOmniadmin, int failureCode)
412         throws Exception {
413 
414         // Make exceptions for omniadmins so that if they break the LDAP
415         // configuration, they can still login to fix the problem
416 
417         if (allowOmniadmin &&
418             (authenticateOmniadmin(
419                 companyId, emailAddress, screenName, userId) == SUCCESS)) {
420 
421             return SUCCESS;
422         }
423 
424         if (PrefsPropsUtil.getBoolean(
425                 companyId, PropsKeys.LDAP_AUTH_REQUIRED)) {
426 
427             return failureCode;
428         }
429         else {
430             return SUCCESS;
431         }
432     }
433 
434     private static Log _log = LogFactoryUtil.getLog(LDAPAuth.class);
435 
436 }