View Javadoc

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.impl;
18  
19  import java.sql.Timestamp;
20  import java.util.ArrayList;
21  import java.util.Collection;
22  import java.util.Collections;
23  import java.util.Comparator;
24  import java.util.Date;
25  import java.util.Iterator;
26  
27  import org.apache.jetspeed.security.PasswordAlreadyUsedException;
28  import org.apache.jetspeed.security.SecurityException;
29  import org.apache.jetspeed.security.om.InternalCredential;
30  import org.apache.jetspeed.security.om.InternalUserPrincipal;
31  import org.apache.jetspeed.security.om.impl.InternalCredentialImpl;
32  
33  /***
34   * <p>
35   * Maintains a configurable FIFO stack of used password credentials for a principal.
36   * It also requires a unique password (with regards to the values currently in the stack) when 
37   * a password is changed directly by the user itself.</p>
38   * <p>
39   * The historical passwords are maintained as {@link InternalCredential} instances with as {@link InternalCredential#getClassname() classname}
40   *  value {@link #HISTORICAL_PASSWORD_CREDENTIAL} to distinguish them from the current password credential.</p>
41   * <p>
42   * <em>Implementation Note:</em><br>
43   * When a new password is about to be saved, a new <em>copy</em> of the current credential is saved as
44   * a historic password credential. This means that the current password credential <em>instance</em>,
45   * and thus also its {@link InternalCredential#getCredentialId() key}, remains the same.</p>
46   * <p>
47   * 
48   * @author <a href="mailto:ate@douma.nu">Ate Douma</a>
49   * @version $Id$
50   */
51  public class PasswordHistoryInterceptor extends AbstractInternalPasswordCredentialInterceptorImpl
52  {
53      private int historySize;
54      
55      /***
56       * Value used for {@link InternalCredential#getClassname()} to distinguish from current password credentials
57       */
58      public static final String HISTORICAL_PASSWORD_CREDENTIAL = "org.apache.jetspeed.security.spi.impl.HistoricalPasswordCredentialImpl";
59      
60      private static final Comparator internalCredentialCreationDateComparator =
61          new Comparator()
62          {
63              public int compare(Object obj1, Object obj2)
64              {
65                  return ((InternalCredential)obj2).getCreationDate().compareTo(((InternalCredential)obj1).getCreationDate());
66              }
67          };
68      
69      /***
70       * @param historySize stack size maintained for historical passwords
71       */
72      public PasswordHistoryInterceptor(int historySize)
73      {
74          this.historySize = historySize;
75      }
76      
77      /***
78       * @see org.apache.jetspeed.security.spi.InternalPasswordCredentialInterceptor#beforeSetPassword(org.apache.jetspeed.security.om.InternalUserPrincipal, java.util.Collection, java.lang.String, org.apache.jetspeed.security.om.InternalCredential, java.lang.String, boolean)
79       */
80      public void beforeSetPassword(InternalUserPrincipal internalUser, Collection credentials, String userName,
81              InternalCredential credential, String password, boolean authenticated) throws SecurityException
82      {
83          Collection internalCredentials = internalUser.getCredentials();
84          ArrayList historicalPasswordCredentials = new ArrayList();
85          if ( internalCredentials != null )
86          {
87              InternalCredential currCredential;
88              Iterator iter = internalCredentials.iterator();
89              
90              while (iter.hasNext())
91              {
92                  currCredential = (InternalCredential) iter.next();
93                  if (currCredential.getType() == InternalCredential.PRIVATE )
94                  {
95                      if ((null != currCredential.getClassname())
96                              && (currCredential.getClassname().equals(HISTORICAL_PASSWORD_CREDENTIAL)))
97                      {
98                          historicalPasswordCredentials.add(currCredential);
99                      }
100                 }
101             }
102         }
103         if (historicalPasswordCredentials.size() > 1)
104         {
105             Collections.sort(historicalPasswordCredentials,internalCredentialCreationDateComparator);
106         }
107         
108         int historyCount = historicalPasswordCredentials.size();
109         InternalCredential historicalPasswordCredential;
110         if ( authenticated )
111         {
112             // check password already used
113             for ( int i = 0; i < historyCount && i < historySize; i++ )
114             {
115                 historicalPasswordCredential = (InternalCredential)historicalPasswordCredentials.get(i);
116                 if ( historicalPasswordCredential.getValue() != null &&
117                         historicalPasswordCredential.getValue().equals(password) )
118                 {
119                     throw new PasswordAlreadyUsedException();
120                 }
121             }
122         }
123 
124         for ( int i = historySize-1; i < historyCount; i++ )
125         {
126             credentials.remove(historicalPasswordCredentials.get(i));
127         }
128         historicalPasswordCredential = new InternalCredentialImpl(credential,HISTORICAL_PASSWORD_CREDENTIAL);
129         credentials.add(historicalPasswordCredential);
130         
131         // fake update to current InternalCredential as being an insert of a new one
132         credential.setCreationDate(new Timestamp(new Date().getTime()));
133     }
134 }