1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.jetspeed.security.spi.ldap;
18  
19  import java.util.HashMap;
20  import java.util.Iterator;
21  import java.util.Map;
22  
23  import javax.naming.NamingException;
24  
25  import org.apache.commons.logging.Log;
26  import org.apache.commons.logging.LogFactory;
27  import org.apache.jetspeed.security.SecurityException;
28  import org.apache.jetspeed.security.spi.impl.ldap.LdapUserCredentialDao;
29  
30  /***
31   * <p>
32   * Test the {@link LdapUserCredentialDao}.
33   * </p>
34   * 
35   * @author <a href="mailto:mike.long@dataline.com">Mike Long </a>, <a href="mailto:dlestrat@apache.org">David Le Strat</a>
36   *  
37   */
38  public class TestLdapUserCredentialDao extends AbstractLdapTest
39  {
40      /*** Configuration for the number of threads performing login. */
41      private static int NUMBER_OF_LOGIN_THREADS = 5;
42  
43      /*** Configuration for the number of login per thread. */
44      private static int NUMBER_OF_LOGINS_PER_THREAD = 10;
45  
46      /*** Map of login threads. */
47      private static Map loginThreads = new HashMap();
48  
49      /*** The logger. */
50      private static final Log log = LogFactory.getLog(TestLdapUserCredentialDao.class);
51  
52      /***
53       * @see org.apache.jetspeed.security.spi.ldap.AbstractLdapTest#setUp()
54       */
55      protected void setUp() throws Exception
56      {
57          super.setUp();
58          LdapDataHelper.seedUserData(uid1, password);
59      }
60      
61      /***
62       * @see org.apache.jetspeed.security.spi.ldap.AbstractLdapTest#tearDown()
63       */
64      protected void tearDown() throws Exception
65      {
66          super.tearDown();
67          LdapDataHelper.removeUserData(uid1);
68      }
69  
70      /***
71       * <p>
72       * Test <code>authenticate</code> with correct login.
73       * </p>
74       * 
75       * @throws Exception An {@link Exception}.
76       */
77      public void testGoodLogin() throws Exception
78      {
79          assertTrue("The login failed for user.", ldapCredDao.authenticate(uid1, password));
80      }
81      
82      /***
83       * <p>
84       * Test regular expression to match any of the following characters: ([{\^$|)?*+.
85       * </p>
86       * 
87       * @throws Exception
88       */
89      public void testRegexForValidateUid() throws Exception
90      {
91          String pattern = ".*//(.*|.*//[.*|.*//{.*|.*////.*|.*//^.*|.*//$.*|.*//|.*|.*//).*|.*//?.*|.*//*.*|.*//+.*|.*//..*";
92          String s = "abcde";
93          assertFalse(s.matches(pattern));
94          s = "ba(cde";
95          assertTrue(s.matches(pattern));
96          s = "ba[cde";
97          assertTrue(s.matches(pattern));
98          s = "ba{cde";
99          assertTrue(s.matches(pattern));
100         s = "ba//cde";
101         assertTrue(s.matches(pattern));
102         s = "ba^cde";
103         assertTrue(s.matches(pattern));
104         s = "ba$cde";
105         assertTrue(s.matches(pattern));
106         s = "ba|cde";
107         assertTrue(s.matches(pattern));
108         s = "ba)cde";
109         assertTrue(s.matches(pattern));
110         s = "ba?cde";
111         assertTrue(s.matches(pattern));
112         s = "ba*cde";
113         assertTrue(s.matches(pattern));
114         s = "ba+cde";
115         assertTrue(s.matches(pattern));
116         s = "ba.cde";
117         assertTrue(s.matches(pattern));
118     }
119 
120     /***
121      * <p>
122      * Test that the uid does not contain any of the following character:
123      * <code>([{\^$|)?*+.</code>
124      * </p>
125      * 
126      * @throws Exception An {@link Exception}.
127      */
128     public void testRegularExpessionInUid() throws Exception
129     {
130         // ([{\^$|)?*+.
131         verifyRegularExpressionFails("(");
132         verifyRegularExpressionFails("[");
133         verifyRegularExpressionFails("{");
134         verifyRegularExpressionFails("//");
135         verifyRegularExpressionFails("^");
136         verifyRegularExpressionFails("$");
137         verifyRegularExpressionFails("|");
138         verifyRegularExpressionFails(")");
139         verifyRegularExpressionFails("?");
140         verifyRegularExpressionFails("*");
141         verifyRegularExpressionFails("+");
142         verifyRegularExpressionFails(".");
143     }
144 
145     /***
146      * <p>
147      * Test <code>authenticate</code> with incorrect character in uid.
148      * </p>
149      * 
150      * @throws Exception An {@link Exception}.
151      */
152     private void verifyRegularExpressionFails(String metaCharacter) throws Exception
153     {
154         try
155         {
156             ldapCredDao.authenticate(uid1 + metaCharacter, password);
157             fail("Should have thrown an SecurityException because the uid contained a regular expression meta-character.");
158         }
159         catch (Exception e)
160         {
161             assertTrue(
162                     "Should have thrown an SecurityException  because the uid contained a regular expression meta-character.",
163                     e instanceof SecurityException);
164         }
165     }
166 
167     /***
168      * <p>
169      * Test <code>authenticate</code> with no password.
170      * </p>
171      * 
172      * @throws Exception An {@link Exception}.
173      */
174     public void testCannotAuthenticateWithNoPassword() throws Exception
175     {
176         try
177         {
178             ldapCredDao.authenticate(uid1, "");
179             fail("Should have thrown an SecurityException.");
180         }
181         catch (Exception e)
182         {
183             log.debug(e);
184             assertTrue("Should have thrown an SecurityException. Instead it threw:" + e.getClass().getName(),
185                     e instanceof SecurityException);
186         }
187 
188         try
189         {
190             ldapCredDao.authenticate(uid1, null);
191             fail("Should have thrown an SecurityException.");
192         }
193         catch (Exception e)
194         {
195             assertTrue("Should have thrown an SecurityException." + e, e instanceof SecurityException);
196         }
197     }
198 
199     /***
200      * <p>
201      * Test <code>authenticate</code> with bad uid.
202      * </p>
203      * 
204      * @throws Exception An {@link Exception}.
205      */
206     public void testBadUID() throws Exception
207     {
208 
209         try
210         {
211             ldapCredDao.authenticate(uid1 + "123", password);
212             fail("Should have thrown an exception for a non-existant user.");
213         }
214         catch (Exception e)
215         {
216             assertTrue("Should have thrown a SecurityException for a non-existant user.",
217                     e instanceof SecurityException);
218         }
219 
220     }
221 
222     /***
223      * <p>
224      * Test <code>authenticate</code> with bad password.
225      * </p>
226      * 
227      * @throws Exception An {@link Exception}.
228      */
229     public void testBadPassword() throws Exception
230     {
231         assertFalse("Should not have authenticated with bad password.", ldapCredDao.authenticate(uid1, password + "123"));
232     }
233 
234     /***
235      * <p>
236      * Test <code>authenticate</code> with concurrent logins.
237      * </p>
238      * 
239      * @throws Exception An {@link Exception}.
240      */
241     public void testConcurrentLogins() throws Exception
242     {
243         for (int i = 0; i < NUMBER_OF_LOGIN_THREADS; i++)
244         {
245             LoginThread thread = new LoginThread();
246 
247             thread.start();
248         }
249 
250         Thread.sleep(6000);
251         assertTrue("Not all login threads completed.", loginThreads.size() == NUMBER_OF_LOGIN_THREADS);
252         assertTrue("Not all login threads successfully ran all their logins().", allLoginThreadsCompletedTheirLogins());
253         assertFalse("An exception was thrown by a login thread. This means there is a concurrency problem.",
254                 exceptionThrownByLogin());
255     }
256 
257     /***
258      * <p>
259      * Gets the exception thrown by the login operation.
260      * </p>
261      */
262     private boolean exceptionThrownByLogin()
263     {
264         boolean exceptionThrown = false;
265         Iterator loginThreadStatuses = loginThreads.values().iterator();
266 
267         while (loginThreadStatuses.hasNext())
268         {
269             LoginThreadStatus status = (LoginThreadStatus) loginThreadStatuses.next();
270 
271             if (status.isSomeExceptionThrown())
272             {
273                 exceptionThrown = true;
274             }
275         }
276 
277         return exceptionThrown;
278     }
279 
280     /***
281      * <p>
282      * Whether all login thread completed their login.
283      * </p>
284      */
285     private boolean allLoginThreadsCompletedTheirLogins()
286     {
287         boolean allThreadsCompletedTheirLogins = true;
288         Iterator loginThreadStatuses = loginThreads.values().iterator();
289 
290         while (loginThreadStatuses.hasNext())
291         {
292             LoginThreadStatus status = (LoginThreadStatus) loginThreadStatuses.next();
293 
294             if (status.getNumberOfSuccessfulLogins() < NUMBER_OF_LOGINS_PER_THREAD)
295             {
296                 allThreadsCompletedTheirLogins = false;
297             }
298         }
299 
300         return allThreadsCompletedTheirLogins;
301     }
302 
303     /***
304      * <p>
305      * Login threads.
306      * </p>
307      */
308     private class LoginThread extends Thread
309     {
310         /*** The login thread status. */
311         private LoginThreadStatus status = new LoginThreadStatus();
312 
313         /*** The {@link LdapUserCredentialDao}. */
314         private LdapUserCredentialDao threadLdap;
315 
316         public LoginThread() throws NamingException, SecurityException
317         {
318             threadLdap = ldapCredDao;
319         }
320 
321         /***
322          * @see java.lang.Runnable#run()
323          */
324         public void run()
325         {
326             for (int i = 0; i < NUMBER_OF_LOGINS_PER_THREAD; i++)
327             {
328                 try
329                 {
330                     assertTrue("The login failed for user.", threadLdap.authenticate(uid1, password));
331                     status.incrementNumberOfSuccessfulLogins();
332                 }
333                 catch (Exception e)
334                 {
335                     status.setSomeExceptionThrown(true);
336                 }
337             }
338 
339             TestLdapUserCredentialDao.loginThreads.put(this, status);
340         }
341     }
342 }
343 
344 /***
345  * <p>
346  * The Login thread status.
347  * </p>
348  */
349 
350 class LoginThreadStatus
351 {
352     private int numberOfSuccessfulLogins;
353 
354     private boolean someExceptionThrown;
355 
356     void incrementNumberOfSuccessfulLogins()
357     {
358         this.numberOfSuccessfulLogins++;
359     }
360 
361     int getNumberOfSuccessfulLogins()
362     {
363         return numberOfSuccessfulLogins;
364     }
365 
366     void setSomeExceptionThrown(boolean someExceptionThrown)
367     {
368         this.someExceptionThrown = someExceptionThrown;
369     }
370 
371     boolean isSomeExceptionThrown()
372     {
373         return someExceptionThrown;
374     }
375 }